动态获取类对象信息
使用Objective-C语言提供的与runtime相关的函数,动态的获取类的所有信息包括成员变量、函数列表和协议列表等,如下所示。
先定义一个继承自NSObject的类CTMObject。
1 | #import <Foundation/Foundation.h> |
1 | #import "CTMObject.h" |
runtime动态获取类信息,如下所示。
1 | - (void)demo { |
该代码的关键之处主要有以下几点:
* import runtime 相关的头文件 objc/message.h。
* 使用object_getClass函数来获取类对象的isa指针所指向的对象。
* 使用class_getSuperclass函数来获取类对象的父类。
* 使用class_getName函数来获取类对象的类名。
* 使用class_getVersion函数来获取类对象的版本。
* 使用class_getInstanceSize函数来获取类对象实例的大小。
* 使用class_copyIvarList函数来获取类对象成员变量列表(共有+私有)。
* 使用class_copyPropertyList函数来获取类对象属性列表(共有+私有)。
* 使用class_copyMethodList函数来获取类对象成员方法列表(共有实例方法+私有实例方法)。
* 使用class_copyProtocolList函数来获取类对象成员协议列表。
最终得到运行结果如下。
1 | 元类: CTMObject |
根据运行结果可以得出如下结论:
* 类在runtime面前是透明的,通过runtime可以获取到类的所有信息。
* 成员变量和属性,不论公有还是私有,都可以获取到。
* 类对象中只保存实例方法,类方法保存在对应的元类中。
类对象动态新增信息
使用Objective-C语言提供的与runtime相关的函数,动态的给已有类添加新信息,如下所示。
1 | #import <objc/runtime.h> |
该代码的关键之处主要有以下几点:
* import runtime 相关的头文件 objc/message.h。
* 使用class_addMethod函数给类添加成员方法。
* 使用class_getInstanceMethod函数获取Method。
* 使用class_getInstanceVariable函数获取Ivar。
* 使用class_addProperty函数给类添加属性。
* 使用class_addProtocol函数给类添加协议。
注意:class_addIvar函数不能给已有类添加成员变量。
最终,得到的运行结果如下:
1 | ivar 添加失败 |
根据运行结果,可得出如下结论:
1,已存在的类,不可以动态添加成员变量。
2,已存在的类,可以动态添加成员方法,且能够遍历到动态添加的方法,但是不能和已有成员方法重名。
3,已存在的类,可以动态添加属性。
优点:能够在已有的类中添加property,且能够遍历到动态添加的属性。
缺点:比较麻烦,getter和setter需要自己写,且值也需要自己存储,如上面的代码,是把setter中的值存储到了_dictCustomerProperty里面,在getter中再从_dictCustomerProperty读出值。
4,已存在的类,可以动态添加协议,且能够遍历到动态添加的协议。
动态创建类对象
使用Objective-C语言提供的与runtime相关的函数,动态的创建一个新的类,如下所示。
1 | #import <objc/message.h> |
该代码的关键之处主要有以下几点:
* import runtime 相关的头文件 objc/message.h。
* 使用objc_allocateClassPair函数创建新的类。
* 使用class_addMethod函数给类添加实例方法。
* 使用class_addIvar函数给类添加成员变量。
* 使用objc_registerClassPair函数来注册新的类。
* 使用object_getClass函数来获取类对象的isa指针所指向的对象。
最终,得到的运行结果如下:
1 | This object is <CustomView: 0x7f9720c23ec0; frame = (0 0; 0 0); layer = <CALayer: 0x60000082e380>> |
将上面的运行结果中的内存地址与对应的类画在下图中,可以清楚看到:
1,从CustomView对象开始,在连续取了3次isa指针所指向的对象后,isa指针指向的地址变成了0x7fff89c1ecd8,也就是NSObject元类的地址。之后第四次取isa指针所指的对象时,其结果仍然为0x7fff89c1ecd8,这说明NSObject元类的isa指针确实是指向它自己的。
2,作为对比,我们在代码最后获取了NSObject类的isa指针地址,其值是0x7fff89c1ecd8,这说明所有的元类对象的isa指针,都是指向NSObject元类的。
3,已存在的类中不能添加成员变量,所有必须通过objc_allocateClassPair动态创建一个类,才能调用class_addIvar添加Ivar,最后通过objc_registerClassPair注册class。
Method Swizzling
方法交换的本质是交换两个方法的IMP。
Objective-C提供了以下API来动态替换类方法或实例方法的实现:
- class_replaceMethod
- method_exchangeImplementations
- method_setImplementation
具体使用方式如下。
1 | #import "ExChangeMethodController.h" |
该代码的关键之处主要有以下几点:
* import runtime 相关的头文件 objc/runtime.h。
* 使用class_getInstanceMethod函数获取类对象的Method。
* 使用method_exchangeImplementations函数交换两个方法的实现。
* 使用method_getImplementation函数获取Method的实现IMP。
* 使用method_getTypeEncoding函数获取Method的类型。
* 使用method_setImplementation函数设置一个方法的实现。
* 使用class_replaceMethod函数替换类方法的定义。
注意:1,class_replaceMethod,当类中没有想替换的原方法时,该方法会调用class_addMethod来为该类增加一个新方法,正因为如此,class_replaceMethod在调用是需要传入types函数。
2,method_exchangeImplementations的内部实现其实是调用了两次method_setImplementation方法,从苹果的文档中能清晰的了解到,如下如所示。
由以上的区别可以总结出这三个API的使用场景:
class_replaceMethod,当需要替换的方法有可能不存在时,可以考虑使用该方法。
method_exchangeImplementations,当需要交换两个方法的实现时使用。
method_setImplementation是最简单的用法,当仅仅需要为一个方法设置其实现方式时使用。