首页 > 解决方案 > Objective-C 等价于 Python 的 __getattr__ / __setattr__ 方法

问题描述

是否有 NSObject 的默认方法来模拟类似于 Python 的getattr / setattr的东西?我想获取和设置只有成员名称的实例的成员。

我想完成这样的事情:

/* someUnknownInstance is defined elsewhere, and has an instance variable named "x", with an initial value of 5 */
...
id myInstance = someUnknownInstance;  /* myInstance.x = 5 */
NSNumber myInstanceVariable = [myInstance getAttr:@"x"];  /* myInstanceVariable = 5 */
[myInstance setAttr:@"x" value:(myInstanceVariable + 1)];  /* myInstance.x = 6 */

标签: objective-cobjective-c-runtime

解决方案


The semi easy way..

Class _class_ = [myInstance class];
objc_property_t property = class_getProperty(_class_, "x");
// property is NULL when there is no such property
if (property) {
    // because ObjC has internal members name scheme, leading '_' = "_x"
    const char * publicname = property_getName(property);
    // the public property name
    NSString *publicKey = [NSString stringWithCString:publicname encoding:NSASCIIStringEncoding];
    // getter
    int i = [[myInstance valueForKey:publicKey] intValue];
    // setter
    [myInstance setValue:@(i+1) forKey:publicKey];
}

The crazy way.. lots of code for just adding + 1 to property "x".

#import <objc/runtime.h>
#import <string.h>

static void* getIvarPointer(id object, char const *propertyname) {
    Ivar ivar = class_getInstanceVariable(object_getClass(object), propertyname);
    if (!ivar) return 0;
    return (uint8_t*)(__bridge void*)object + ivar_getOffset(ivar);
}

-(void)goCrazyWithObjcRuntime:(id)myInstance {
    
    /// search class of id for specific property name and add 1

    Class _class = [myInstance class];
    
    objc_property_t property = class_getProperty(_class, "x");
    
    if (property) {
        
        Boolean isInt = false;
        const char * attr = property_getAttributes(property);
        
        // lets iterate the attributes of the property
        char *attribs = strdup(attr);
        char *pt = strtok(attribs,",");
        while (pt != NULL) {
             if (strcmp(pt, "N" ) == 0) printf("nonatomic ");
             else if (strcmp(pt, "Ti") == 0) {
                 printf("integer ");
                 isInt = true;
             }
             else if (strcmp(pt, "R" ) == 0) printf("readonly ");
             else if (strcmp(pt, "C" ) == 0) printf("copy ");
             else printf("other property description:%s", pt);
            pt = strtok(NULL, ",");
        }
        
        // the public propertyname
        const char * name = property_getName(property);
        NSLog(@"public propertyname:'%s' with attributes:'%s'",name, attr);
        
        // transform public property name back to internal class member
        char objcInternalPrefix[4] = "_";
        char *backingPropertyName = strncat(objcInternalPrefix, name, strlen(name));
        
        // getter, catch the Ivar
        int *ptr = getIvarPointer(myInstance, backingPropertyName);
        NSLog(@"x=%d",*ptr);
        
        // setter, beware math without type check here
        // Get a pointer to the instance variable.  Returns NULL if not found.
        int *intPtr = getIvarPointer(myInstance, backingPropertyName);
        if (intPtr) {
            // Now, add 1.
            if (isInt) {
                *intPtr = *intPtr + 1;
            }
        }
        
    }
}

推荐阅读