五、利用runtime-API创建对象
这题对runtime-API要求程度比较高
1.API介绍
- 动态创建类
/**
*创建类
*
*superClass: 父类,传Nil会创建一个新的根类
*name: 类名
*extraBytes: 额外的内存空间,一般传0
*return:返回新类,创建失败返回Nil,如果类名已经存在,则创建失败
*/
Class FXPerson = objc_allocateClassPair([NSObject class], "LGPerson", 0);
- 添加成员变量
/**
*添加成员变量
*这个函数只能在objc_allocateClassPair和objc_registerClassPair之前调用。不支持向现有类添加一个实例变量
*这个类不能是元类,不支持在元类中添加一个实例变量
*实例变量的最小对齐为1 << align,实例变量的最小对齐依赖于ivar的类型和机器架构。对于任何指针类型的变量,请通过log2(sizeof(pointer_type))
*
*cls 往哪个类添加
*name 添加的名字
*size 大小
*alignment 对齐处理方式
*types 签名
*/
class_addIvar(FXPerson, "fxName", sizeof(NSString *), log2(sizeof(NSString *)), "@");
- 注册到内存
/**
*往内存注册类
*
* cls 要注册的类
*/
objc_registerClassPair(FXPerson);
- 添加属性变量
/**
*往类里面添加属性
*
*cls 要添加属性的类
*name 属性名字
*attributes 属性的属性数组。
*attriCount 属性中属性的数量。
*/
class_addProperty(targetClass, propertyName, attrs, 4);
- 添加方法
/**
*往类里面添加方法
*
*cls 要添加方法的类
*sel 方法编号
*imp 函数实现指针
*types 签名
*/
class_addMethod(FXPerson, @selector(setHobby), (IMP)fxSetter, "v@:@");
2.整体使用
// hobby的setter-IMP
void fxSetter(NSString *value) {
printf("%s/n",__func__);
}
// hobby的getter-IMP
NSString *fxHobby() {
return @"iOS";
}
// 添加属性变量的封装方法
void fx_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);
}
// 打印属性变量的封装方法
void fx_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));
}
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 动态创建类
Class FXPerson = objc_allocateClassPair([NSObject class], "FXPerson", 0);
// 添加成员变量
class_addIvar(FXPerson, "name", sizeof(NSString *), log2(sizeof(NSString *)), "@");
// 注册到内存
objc_registerClassPair(FXPerson);
// 添加属性变量
fx_class_addProperty(FXPerson, "hobby");
fx_printerProperty(FXPerson);
// 添加方法(为属性方法添加setter、getter方法)
class_addMethod(FXPerson, @selector(setHobby:), (IMP)fxSetter, "v@:@");
class_addMethod(FXPerson, @selector(hobby), (IMP)fxHobby, "@@:");
// 开始使用
id person = [FXPerson alloc];
[person setValue:@"Felix" forKey:@"name"];
NSLog(@"FXPerson的名字是:%@ 爱好是:%@", [person valueForKey:@"name"], [person valueForKey:@"hobby"]);
}
return 0;
}
3.注意事项
- 记得导入
<objc/runtime.h>
- 添加成员变量
class_addIvar
必须在objc_registerClassPair
前,因为注册到内存时ro
已经确定了,不能再往ivars
添加(同第四个面试题) - 添加属性变量
class_addProperty
可以在注册内存前后,因为是往rw
中添加的 -
class_addProperty
中“属性的属性”——nonatomic/copy
是根据属性的类型变化而变化的 -
class_addProperty
不会自动生成setter和getter
方法,因此直接调用KVC
会崩溃
不只可以通过KVC
打印来检验,也可以下断点查看ro、rw
的结构来检验