ReactiveCocoa 学习笔记二十二(RACEXTRuntimeExtensions)

RACEXTRuntimeExtensions

RACEXTRuntimeExtensions 中定义了两个函数用来进行运行时的操作,并且声明了一个结构体用来描述属性的特征。

typedef enum {
    rac_propertyMemoryManagementPolicyAssign = 0,
    rac_propertyMemoryManagementPolicyRetain,
    rac_propertyMemoryManagementPolicyCopy
} rac_propertyMemoryManagementPolicy;

rac_propertyMemoryManagementPolicy 是枚举类型,其表示属性在内存中的管理策略。

rac_propertyAttributes 是一个结构体,描述属性的特征信息。

typedef struct {
    BOOL readonly;
    BOOL nonatomic;
    BOOL weak;
    BOOL canBeCollected;
    BOOL dynamic;
    
    rac_propertyMemoryManagementPolicy memoryManagementPolicy;

    SEL getter;

    SEL setter;

    const char *ivar;

    Class objectClass;

    char type[];
} rac_propertyAttributes;
结构体成员变量含义
readonly表示属性是否是只读的
nonatomic表示属性是否是非线程安全的
weak表示属性是否是弱引用的
canBeCollected表示属性是否支持垃圾回收
dynamic表示属性是否使用了 @dynamic 进行定义
memoryManagementPolicy属性的内存管理方式
getter属性的获取选择器
setter属性的设置选择器
ivar属性所对应的成员变量
objectClass属性所属的类
type属性的类型编码
Method rac_getImmediateInstanceMethod (Class aClass, SEL aSelector) {
    unsigned methodCount = 0;
    Method *methods = class_copyMethodList(aClass, &methodCount);
    Method foundMethod = NULL;

    for (unsigned methodIndex = 0;methodIndex < methodCount;++methodIndex) {
        if (method_getName(methods[methodIndex]) == aSelector) {
            foundMethod = methods[methodIndex];
            break;
        }
    }

    free(methods);
    return foundMethod;
}

rac_getImmediateInstanceMethod 函数可以用来查询指定类中是否含有指定的方法,如果有就返回,否则返回 NULL ,而该函数不同于 class_getInstanceMethod() 函数,其并不会去检查父类是否包含指定的方法。

class_copyMethodList() 函数是不会去获取父类中声明的方法的。

rac_propertyAttributes *rac_copyPropertyAttributes (objc_property_t property);

rac_copyPropertyAttributes() 函数是用来获取属性的相关信息的,如下面的伪代码:

@property (nonatomic, strong) Person *person;

objc_property_t property = class_getProperty(object_getClass(self), @"person".UTF8String);
if (property != NULL) {
	rac_propertyAttributes *attributes = rac_copyPropertyAttributes(property);
}

调用 rac_copyPropertyAttributes() 函数的执行步骤如下:

  1. 使用原生函数获取属性的编码

    const char * const attrString = property_getAttributes(property);
    if (!attrString) {
        fprintf(stderr, "ERROR: Could not get attribute string from property %s\n", property_getName(property));
        return NULL;
    }
    
    if (attrString[0] != 'T') {
        fprintf(stderr, "ERROR: Expected attribute string \"%s\" for property %s to start with 'T'\n", attrString, property_getName(property));
        return NULL;
    }
    

    这里会判断 attrString 的值是否为空以及是否是以 T 开头,正常情况下 attrString 的值为:T@\"Person\",&,N,V_person@"Person" 表示该属性是类 Person 的实例对象。

  2. typeString 是 attrString 向后移动一个字符,所以其值为:@\"Person\",&,N,V_person ,其包含属性所有的声明特征。函数 NSGetSizeAndAlignment() 会返回下一个特征编码的位置,所以 next 的值为:,&,N,V_person

    const char *typeString = attrString + 1;
    const char *next = NSGetSizeAndAlignment(typeString, NULL, NULL);
    if (!next) {
        fprintf(stderr, "ERROR: Could not read past type in attribute string \"%s\" for property %s\n", attrString, property_getName(property));
        return NULL;
    }
    
  3. 上一步判断声明的属性特征是否存在,而该步骤则判断第一个特征,即属性所属类型是否存在。

    size_t typeLength = (size_t)(next - typeString);
    if (!typeLength) {
        fprintf(stderr, "ERROR: Invalid type in attribute string \"%s\" for property %s\n", attrString, property_getName(property));
        return NULL;
    }
    

    typeLength 的值是 9 ,即 @"Person" 的长度。

  4. 接着动态申请一片内存,用来保存接下来读取的信息,并判断是否分配成功。

    rac_propertyAttributes *attributes = calloc(1, sizeof(rac_propertyAttributes) + typeLength + 1);
    if (!attributes) {
        fprintf(stderr, "ERROR: Could not allocate rac_propertyAttributes structure for attribute string \"%s\" for property %s\n", attrString, property_getName(property));
        return NULL;
    }
    

    attributes 是指向 rac_propertyAttributes 结构体的指针,在使用 calloc() 函数为其分配内存空间时,指定了两个参数,1 表示分配一个连续内存,而要分配的内存长度则是第二个参数指定的,包含结构体的长度、所属类型的编码长度以及一个结束符号(1+48+1)。

  5. 接着为 attributes 中的 type 成员变量拷贝所属类型的编码

    strncpy(attributes->type, typeString, typeLength);
    attributes->type[typeLength] = '\0';
    
  6. 接着判断传递的属性是否是 OC 类对象

    if (typeString[0] == *(@encode(id)) && typeString[1] == '"') {
        
        const char *className = typeString + 2;
        next = strchr(className, '"');
        
        //提取类名,此时
        //className 的值为 Person",&,N,V_person
        //next 的值为 ",&,N,V_person
        
        if (!next) {
            fprintf(stderr, "ERROR: Could not read class name in attribute string \"%s\" for property %s\n", attrString, property_getName(property));
            return NULL;
        }
    
        if (className != next) {
            size_t classNameLength = (size_t)(next - className);
            
            //计算类名的长度,classNameLength 的值为 6
            //接着根据该长度并加上一个结束字符,申请空间并赋值给结构体的 objectClass
            
            char trimmedName[classNameLength + 1];
            strncpy(trimmedName, className, classNameLength);
            trimmedName[classNameLength] = '\0';
            attributes->objectClass = objc_getClass(trimmedName);
        }
    }
    

    此时,next 的值为 ",&,N,V_person

  7. 判断 next ,如果还有属性特征,那么跳到分隔的 , 位置。

    if (*next != '\0') {
        next = strchr(next, ',');
    }
    

    此时,next 的值为 ,&,N,V_person

  8. 接着,遍历剩下所有的属性编码,并为结构体中的成员变量赋值。

    while (next && *next == ',') {
        char flag = next[1];
        next += 2;
    
        switch (flag) {
        case '\0':
            break;
    
        case 'R':
            attributes->readonly = YES;
            break;
    
        case 'C':
            attributes->memoryManagementPolicy = rac_propertyMemoryManagementPolicyCopy;
            break;
    
        case '&':
            attributes->memoryManagementPolicy = rac_propertyMemoryManagementPolicyRetain;
            break;
    
        case 'N':
            attributes->nonatomic = YES;
            break;
    
        case 'G':
        case 'S':
            {
                const char *nextFlag = strchr(next, ',');
                SEL name = NULL;
    
                if (!nextFlag) {
                    // assume that the rest of the string is the selector
                    const char *selectorString = next;
                    next = "";
    
                    name = sel_registerName(selectorString);
                } else {
                    size_t selectorLength = (size_t)(nextFlag - next);
                    if (!selectorLength) {
                        fprintf(stderr, "ERROR: Found zero length selector name in attribute string \"%s\" for property %s\n", attrString, property_getName(property));
                        goto errorOut;
                    }
    
                    char selectorString[selectorLength + 1];
    
                    strncpy(selectorString, next, selectorLength);
                    selectorString[selectorLength] = '\0';
    
                    name = sel_registerName(selectorString);
                    next = nextFlag;
                }
    
                if (flag == 'G')
                    attributes->getter = name;
                else
                    attributes->setter = name;
            }
    
            break;
    
        case 'D':
            attributes->dynamic = YES;
            attributes->ivar = NULL;
            break;
    
        case 'V':
            // assume that the rest of the string (if present) is the ivar name
            if (*next == '\0') {
                // if there's nothing there, let's assume this is dynamic
                attributes->ivar = NULL;
            } else {
                attributes->ivar = next;
                next = "";
            }
    
            break;
    
        case 'W':
            attributes->weak = YES;
            break;
    
        case 'P':
            attributes->canBeCollected = YES;
            break;
    
        case 't':
            fprintf(stderr, "ERROR: Old-style type encoding is unsupported in attribute string \"%s\" for property %s\n", attrString, property_getName(property));
    
            // skip over this type encoding
            while (*next != ',' && *next != '\0')
                ++next;
    
            break;
    
        default:
            fprintf(stderr, "ERROR: Unrecognized attribute string flag '%c' in attribute string \"%s\" for property %s\n", flag, attrString, property_getName(property));
        }
    }
    
  9. 设置属性的默认获取方法

    if (!attributes->getter) {
        // use the property name as the getter by default
        attributes->getter = sel_registerName(property_getName(property));
    }
    
  10. 设置属性的默认设置方法

    if (!attributes->setter) {
        const char *propertyName = property_getName(property);
        size_t propertyNameLength = strlen(propertyName);
    
        // we want to transform the name to setProperty: style
        size_t setterLength = propertyNameLength + 4;
    
        char setterName[setterLength + 1];
        strncpy(setterName, "set", 3);
        strncpy(setterName + 3, propertyName, propertyNameLength);
    
        // capitalize property name for the setter
        setterName[3] = (char)toupper(setterName[3]);
    
        setterName[setterLength - 1] = ':';
        setterName[setterLength] = '\0';
    
        attributes->setter = sel_registerName(setterName);
    }
    
  11. 返回保存有属性信息的结构体指针

    return attributes;
    
  12. 这里有个错误出口,在第 8 步时,若发生错误则跳转到这里,释放申请的空间并返回 NULL 。

    errorOut:
        free(attributes);
        return NULL;
    

编码字符串是以 T 开头,所有特征的编码以 , 隔开,最后以 V+属性所对应的成员变量名 结尾,参见运行时

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值