Iphone: Replace functions using reflection

175 views Asked by At

I have a small function which I want to rewrite, so that function is valid for every class. At the moment I have 10 of the same functions which all work same but every function is for another class. I know, that I have to do it with reflections, but I am not so sure how to do it. I already read this link: http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ObjCRuntimeRef/Reference/reference.html

The functions I am talking about are:

-(NSCountedSet *)MissionGetReferecedNested:(id)modelObject
{
    setOfObjects = [[NSCountedSet alloc]initWithArray:modelObject.MissionSectionList];
    return setOfObjects;
}
-(NSCountedSet *)MissionGetSectionReferecedNested:(id)modelObject
{
    setOfObjects = [[NSCountedSet alloc]initWithArray:modelObject.DamageAccountList];
    return setOfObjects;
}

MissionSectionList and DamageAccountList are both NSMutableArrays from two different classes. Is it possible to see if a class consists a NSMutableArray and if yes then it should call the .... modelObject.MyMutableArray?

4

There are 4 answers

1
Abhi Beckert On BEST ANSWER

You can use reflection like this:

- (NSCountedSet *)MissionGet:(id)modelObject
{
    SEL propertySelector = NULL;

    if ([modelObject respondsToSelector:@selector(MissionSectionList)]) {
        propertySelector = @selector(MissionSectionList);
    } else if ([modelObject respondsToSelector:@selector(DamageAccountList)]) {
        propertySelector = @selector(DamageAccountList);
    }

    if (!propertySelector) {
      [NSException raise:@"Invalid modelObject value" format:@"Model object %@ does not contain any recognised selectors", modelObject];
    }

    return [[NSCountedSet alloc] initWithArray:[modelObject performSelector:propertySelector]];
}

But a more common technique among cocoa programmers would be:

- (NSCountedSet *)MissionGet:(id <MyCustomProtocol>)modelObject
{
    return [[NSCountedSet alloc] initWithArray:[modelObject missionArray]];
}

Where you would accept any object which confirms to the protocol MyCustomProtocol. The protocol is defined in a header files somewhere, using:

@protocol MyCustomProtocol

@property (readonly) NSArray *missionArray;

@end

And then in each of your classes, declare it as implementing the protocol:

@interface MissionSectionListClass <MyCustomProtocol>

And add a method implementation:

@implementation MissionSectionListClass <MyCustomProtocol>

- (NSArray *)missionArray
{
    return self.MissionSectionList;
}

@end

Using protocols is a bit more code, but it's the "right" way to go. It allows you to add support for new classes, without any change to your MissiongGet... method.

More info about protocols: http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocProtocols.html

0
DeFlo On

I think I have to go with the runtime type editing.(http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ObjCRuntimeRef/Reference/reference.html)

The idea with the protocols was good but there I have to change a lot of things in the classes.(which is not possible/allowed) for me. My intension was only to change the functions so that I have only one function for all classes.

I think with the runtime type editing I can check what classes and attributes I have (?) Am I right? Did somebody already work with runtime type editing?

2
Coolant On

EDIT : Cleared all my answer to this :

I think it's not possible to check if a class has a member variable of specified type. You can only check if a class has a specified method.

So, in this case it will be best if you make all your NSMutableArray list the same name, and then create a declared property for this list, and then do a respondsToSelector in your ...GetReferencedNested method.

So, for example, in all of your class create this property :

@property (nonatomic, retain) NSMutableArray * list;

and then in the ..MissionGetReferencedNested method :

if ([modelObject respondsToSelector:@selector(list)])
    ...

Correct me if i'm wrong...

0
k1th On

In terms of style I'd also follow Abhi's suggestion.

But if you really want to inspect a class that you are stuck with and, for example build a NSCountedSet with the first NSMutableArray variable you can find, you could do it like this:

#import "Utilities.h"
#import <Foundation/Foundation.h>
#import <objc/objc-runtime.h>

@implementation Utilities

+ (NSCountedSet*)initCountedSetWithFirstArrayinObject:(id)someObject {

    unsigned int c;

    Ivar *ivar_arr = class_copyIvarList([someObject class], &c);

    for (unsigned int i = 0; i < c; i++) {
        if ([@"@\"NSMutableArray\"" isEqualToString:
             [NSString stringWithCString:ivar_getTypeEncoding(ivar_arr[i]) encoding:NSUTF8StringEncoding]
             ]) {
             return [[NSCountedSet alloc] initWithArray:object_getIvar(someObject, ivar_arr[i])];
        }
    }

    return nil;
}

@end

Of course this has very limited real world use because it depends on you knowing that the first array will be the one you're interested in.