runtime应用
我们来通过runtime的动态属性创建一个Class:
- 首先创建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函数计算得到;
参数五:类型为任意类型。
- 注册Class
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_registerClassPair
和class_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表示nonatomic
;V_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";
}