Objective-C dynamic properties at runtime?

There are at least two ways to do this.

Subscripting

Use objectForKeyedSubscript: and setObject:forKeyedSubscript:

 @property (nonatomic,strong) NSMutableDictionary *properties;

 - (id)objectForKeyedSubscript:(id)key {
      return [[self properties] valueForKey:[NSString stringWithFormat:@"%@",key]];
 }

 - (void)setObject:(id)object forKeyedSubscript:(id <NSCopying>)key {
      [[self properties] setValue:object forKey:[NSString stringWithFormat:@"%@",key]];
 }

 Person *p = [Person new];
 p[@"name"] = @"Jon";
 NSLog(@"%@",p[@"name"]);

resolveInstanceMethod:

This is the objc_sendMsg executed by the runtime for all methods:

objc_sendMsg

If you look at the bottom, you have the opportunity to resolveInstanceMethod:, which lets you redirect the method call to one of your choosing. To answer your question, you need to write a generic getter and setter that looks-up a value on a dictionary ivar:

// generic getter
static id propertyIMP(id self, SEL _cmd) {
    return [[self properties] valueForKey:NSStringFromSelector(_cmd)];
}


// generic setter
static void setPropertyIMP(id self, SEL _cmd, id aValue) {

    id value = [aValue copy];
    NSMutableString *key = [NSStringFromSelector(_cmd) mutableCopy];

    // delete "set" and ":" and lowercase first letter
    [key deleteCharactersInRange:NSMakeRange(0, 3)];
    [key deleteCharactersInRange:NSMakeRange([key length] - 1, 1)];
    NSString *firstChar = [key substringToIndex:1];
    [key replaceCharactersInRange:NSMakeRange(0, 1) withString:[firstChar lowercaseString]];

    [[self properties] setValue:value forKey:key];
}

And then implement resolveInstanceMethod: to add the requested method to the class.

+ (BOOL)resolveInstanceMethod:(SEL)aSEL {
    if ([NSStringFromSelector(aSEL) hasPrefix:@"set"]) {
        class_addMethod([self class], aSEL, (IMP)setPropertyIMP, "v@:@");
    } else {
        class_addMethod([self class], aSEL,(IMP)propertyIMP, "@@:");
    }
    return YES;
}

You could also do it returning a NSMethodSignature for the method, which is then wrapped in a NSInvocation and passed to forwardInvocation:, but adding the method is faster.

Here is a gist that runs in CodeRunner. It doesn’t handle myClass["anyProperty"] calls.

Leave a Comment