views:

64

answers:

4

Suppose (for the sake of argument) that I have a view class which contains an NSDictionary. I want a whole bunch of properties, all of which access the members of that dictionary.

For example, I want @property NSString* title and @property NSString* author.

For each one of these properties, the implementation is the same: for the getter, call [dictionary objectForKey:propertyName];, and for the setter do the same with setObject:forKey:.

It would take loads of time and use loads of copy-and-paste code to write all those methods. Is there a way to generate them all automatically, like Core Data does with @dynamic properties for NSManagedObject subclasses? To be clear, I only want this means of access for properties I define in the header, not just any arbitrary key.

I've come across valueForUndefinedKey: as part of key value coding, which could handle the getters, but I'm not entirely sure whether this is the best way to go.

I need these to be explicit properties so I can bind to them in Interface Builder: I eventually plan to write an IB palette for this view.

(BTW, I know my example of using an NSDictionary to store these is a bit contrived. I'm actually writing a subclass of WebView and the properties will refer to the IDs of HTML elements, but that's not important for the logic of my question!)

A: 

sounds like you should should be using a class instead of a dictionary. you're getting close to implementing by hand what the language is trying to give you.

mariachi
Yeah, the dictionary thing was a contrived example. I'm actually using this to make a view for an HTML document that you can connect to with bindings. You're right that if that's all I was doing, it'd be a bad plan :)
Amorya
A: 

You can use a mix of your suggested options:

  • use the @dynamic keyword
  • overwrite valueForKey: and setValue:forKey: to access the dictionary
  • use the objective-c reflection API's method class_getProperty and check it for nil. If it's not nil your class has such a property. It doesn't if it is.
  • then call the super method in the cases where no such property exists.

I hope this helps. Might seem a bit hacky (using reflection) but actually this is a very flexible and also absolutely "legal" solution to the problem...

PS: the coredata way is possible but would be total overkill in your case...

Max Seelemann
I'll add the class_getProperty bit to my solution, I think. Thanks.
Amorya
+2  A: 

Befriend a Macro? This may not be 100% correct.

#define propertyForKey(key, type) \
    - (void) set##key: (type) key; \
    - (type) key;

#define synthesizeForKey(key, type) \
    - (void) set##key: (type) key \
    { \
         [dictionary setObject];// or whatever \
    } \
    - (type) key { return [dictionary objectForKey: key]; }
Stephen Furlani
+2  A: 

I managed to solve this myself after pouring over the objective-c runtime documentation.

I implemented this class method:

+ (BOOL) resolveInstanceMethod:(SEL)aSEL
{
    NSString *method = NSStringFromSelector(aSEL);

    if ([method hasPrefix:@"set"])
    {
        class_addMethod([self class], aSEL, (IMP) accessorSetter, "v@:@");
        return YES;
    }
    else
    {
        class_addMethod([self class], aSEL, (IMP) accessorGetter, "@@:");
        return YES;
    }
    return [super resolveInstanceMethod:aSEL];
}

Followed by a pair of C functions:

NSString* accessorGetter(id self, SEL _cmd)
{
    NSString *method = NSStringFromSelector(_cmd);
    // Return the value of whatever key based on the method name
}

void accessorSetter(id self, SEL _cmd, NSString* newValue)
{
    NSString *method = NSStringFromSelector(_cmd);

    // remove set
    NSString *anID = [[[method stringByReplacingCharactersInRange:NSMakeRange(0, 3) withString:@""] lowercaseString] stringByReplacingOccurrencesOfString:@":" withString:@""];

    // Set value of the key anID to newValue
}

Since this code tries to implement any method that is called on the class and not already implemented, it'll cause problems if someone tries calling something you're note expecting. I plan to add some sanity checking, to make sure the names match up with what I'm expecting.

Amorya