Objective-C dynamic properties at runtime?

后端 未结 3 2040
一整个雨季
一整个雨季 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: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 )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.

提交回复
热议问题