runtime应用

2020-01-12  本文已影响0人  半边枫叶

我们来通过runtime的动态属性创建一个Class:

Class LGPerson = objc_allocateClassPair([NSObject class], "LGPerson", 0);

下面我们来看下这个方法的参数

objc_allocateClassPair(<#Class  _Nullable __unsafe_unretained superclass#>, <#const char * _Nonnull name#>, <#size_t extraBytes#>)

上面的这个方法用来创建Class,有三个参数:第一个为superClass,第二个为属性的name,第三个为extraBytes。

class_addIvar(LGPerson, "lgName", sizeof(NSString *), log2(sizeof(NSString *)), "@");

这个方法有四个参数

class_addIvar(<#Class  _Nullable __unsafe_unretained cls#>, <#const char * _Nonnull name#>, <#size_t size#>, <#uint8_t alignment#>, <#const char * _Nullable types#>)

参数一:目标class;
参数二:成员变量名称;
参数三:成员变量的字节大小,字符串就是8个字节;
参数四:alignment:对齐方式。因为成员变量占用8个字节,所以对齐方式应该是3。我们可以通过log函数计算得到;
参数五:类型为任意类型。

objc_registerClassPair(LGPerson);

这样我们就完成了Class的动态注册添加,然后就可以使用该类了

id person = [LGPerson alloc];
[person setValue:@"KC" forKey:@"lgName"];
NSLog(@"%@",[person valueForKey:@"lgName"]);

问题:如果我们将增加成员变量和注册Class两个步骤调换一下顺序可不可以呢?
我们通过实验,调换顺序后,会crash崩溃。下面我们来探究为什么不行呢?
通过查看Class的结构我们发现对象的成员变量ivars存在class的ro中
const ivar_list_t * ivars;

struct class_ro_t {    
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;
    property_list_t *baseProperties;
}

ro是在编译时候就确定了,不能进行动态添加的。所以在注册完成Class后就不能再添加了。
我们通过跟踪objc_registerClassPairclass_addIvar方法也可以找到证据
下面是objc_registerClassPair中的,使用RW_CONSTRUCTED做了标识

// Clear "under construction" bit, set "done constructing" bit
cls->ISA()->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING);
cls->changeInfo(RW_CONSTRUCTED, RW_CONSTRUCTING | RW_REALIZING);

然后在class_addIvar方法中判断,是否标识了RW_CONSTRUCTING,如果是直接return,中指添加流程。

// Can only add ivars to in-construction classes.
if (!(cls->data()->flags & RW_CONSTRUCTING)) {
    return NO;
}

现在我们明白了为什么不能再注册完Class后不能再添加ivars了,那么能不能动态添加property属性呢?
答案是可以的,因为property可以存储在rw中。下面我们来添加property:

首先我们给LGTeacher对象正常添加一个subject属性。

@interface LGTeacher : NSObject
@property (nonatomic, copy) NSString *subject; // 属性的属性
@end

然后我们添加下面的方法来协助我们打印出Class的property信息。

void lg_printerProperty(Class targetClass){
    unsigned int outCount, i;
    objc_property_t *properties = class_copyPropertyList(targetClass, &outCount);
    for (i = 0; i < outCount; i++) {
        objc_property_t property = properties[i];
        fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));
    }
}

我们调用打印方法来打印LGTeacher的属性信息

lg_printerProperty([LGTeacher class]);

打印结果如下:

subject T@"NSString",C,N,V_subject
(lldb) 

属性的name为“subject”,属性的相关属性配置为"T@"NSString",C,N,V_subject"。我们来解读一下这个属性配置:
其中的T@"String"意思为属性的类型为String, T@为固定的拼接格式;C表示copy;N表示nonatomicV_subject中的subject为名字,V_为固定的拼接格式。
然后我们就可以模仿上面的属性配置来动态的给Class增加property了,代码如下:

void lg_class_addProperty(Class targetClass , const char *propertyName){
    
    objc_property_attribute_t type = { "T", [[NSString stringWithFormat:@"@\"%@\"",NSStringFromClass([NSString class])] UTF8String] }; //type
    objc_property_attribute_t ownership0 = { "C", "" }; // C = copy
    objc_property_attribute_t ownership = { "N", "" }; //N = nonatomic
    objc_property_attribute_t backingivar  = { "V", [NSString stringWithFormat:@"_%@",[NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding]].UTF8String };  //variable name
    objc_property_attribute_t attrs[] = {type, ownership0, ownership,backingivar};

    class_addProperty(targetClass, propertyName, attrs, 4);

}

封装好了上述方法,我们就可以直接调用来增加property,并且打印出我们添加的property。

lg_class_addProperty(LGPerson, "subject");
lg_printerProperty(LGPerson);

发现结果和正常添加的一样。
给Class添加完成property后,我们就可以使用添加的property了。

[person setValue:@"master" forKey:@"subject"];
NSLog(@"%@",[person valueForKey:@"subject"]);

我们可以直接通过KVC设置我们添加的属性吗?
现在还不行,因为我们通过KVC给对象设置属性的时候,KVO会通过set方法来进行赋值,而我们现在只是给Class添加了属性,还没有添加set和get方法。下面我们来给属性添加set和get方法

class_addMethod(LGPerson, @selector(setSubject:), (IMP)lgSetter, "v@:@");
class_addMethod(LGPerson, @selector(subject), (IMP)lgName, "@@:");

其中的imp实现为我们自己实现的方法,现在我们就可以正常的对属性进行赋值和访问了。

void lgSetter(NSString *value){
    printf("%s/n",__func__);
}

NSString *lgName(){
    printf("%s/n",__func__);
    return @"master NB";
}
上一篇下一篇

猜你喜欢

热点阅读