开发者

Writing my own @dynamic properties in Cocoa

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 开发者_StackOverflowimplementation 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!)


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.


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...


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]; }


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.


There is a nice blog with example code with more robust checks on dynamic properties at https://tobias-kraentzer.de/2013/05/15/dynamic-properties-in-objective-c/ also a very nice SO answer at Objective-C dynamic properties at runtime?.

Couple of points on the answer. Probably want to declare an @property in the interface to allow typeahead also to declare the properties as dynamic in the implementation.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜