Objective-C dynamic properties at runtime?

后端 未结 3 2005
一整个雨季
一整个雨季 2020-12-02 17:56

Is it possible to create an Objective-C class that can have an arbitrary number of dynamic properties at runtime?

I want to be able to call mySpecialClass.anyP

相关标签:
3条回答
  • 2020-12-02 18:12

    You're asking different things. If you want to be able to use the bracket syntax mySpecialClass[@"anyProperty"] on instances of your class, it is very easy. Just implement the methods:

     - (id)objectForKeyedSubscript:(id)key
     {
          return ###something based on the key argument###
     }
    
     - (void)setObject:(id)object forKeyedSubscript:(id <NSCopying>)key
     {
          ###set something with object based on key####
     }
    

    It will be called everytime you use the bracket syntax in your source code.

    Otherwise if you want to create properties at runtime, there are different ways to proceed, take a look at NSObject's forwardInvocation: method, or look at the Objective-C Runtime Reference for functions to dynamically alter a class...

    0 讨论(0)
  • 2020-12-02 18:19

    Guillaume is right. forwardInvocation: is the way to go. This answer gives some more details: method_missing-like functionality in objective-c (i.e. dynamic delegation at run time)

    This has even more details: Equivalent of Ruby method_missing in Objective C / iOS

    And these are some other lesser known Obj-C features that might help you: Hidden features of Objective-C

    Enjoy!

    0 讨论(0)
  • 2020-12-02 18:28

    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.

    0 讨论(0)
提交回复
热议问题