- 什么是runtime
runtime是基于C语言的一套API,C语言是基于面向过程的,而runtime在C的基础上进行了一次封装,使得C语言有了面向对象这一说。
换一种比较明了的说法:
我们平时写的OC代码其实都会在运行时转成C语言来运行,比如OC中调用方法会转化成C语言中的
id objc_msgSend ( id self, SEL op, … );
而oc中的对象在runtime中以结构体的形式来展示,结构体中包含着类名, 成员变量, 方法名,方法列表,协议列表等等。
一个类在runtime中的表示
struct objc_class {
Class isa;//指针,顾名思义,表示是一个什么,
//实例的isa指向类对象,类对象的isa指向元类
#if !__OBJC2__
Class super_class; //指向父类
const char *name; //类名
long version;
long info;
long instance_size
struct objc_ivar_list *ivars //成员变量列表
struct objc_method_list **methodLists; //方法列表
struct objc_cache *cache;//缓存
//一种优化,调用过的方法存入缓存列表,下次调用先找缓存
struct objc_protocol_list *protocols //协议列表
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
有了这个结构体, 我们就可以通过遵循runtime的协议,来使用这个结构体。
例如 : 获取属性/方法/协议列表、拦截调用与动态添加等等(后续使用了会继续添加。。。)
a) 获取属性/方法/协议列表
最直接的一种用法,就是获取我们的结构体中存储的对象的属性、方法、协议等列表,从而获取其所有这些信息。
具体如下:
#import <objc/runtime.h>
// 输出类的一些信息
- (void)logInfo {
unsigned int count;// 用于记录列表内的数量,进行循环输出
// 获取属性列表
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i = 0; i < count; i++) {
const char *propertyName = property_getName(propertyList[i]);
NSLog(@"property --> %@", [NSString stringWithUTF8String:propertyName]);
}
// 获取方法列表
Method *methodList = class_copyMethodList([self class], &count);
for (unsigned int i; i < count; i++) {
Method method = methodList[i];
NSLog(@"method --> %@", NSStringFromSelector(method_getName(method)));
}
// 获取成员变量列表
Ivar *ivarList = class_copyIvarList([self class], &count);
for (unsigned int i; i < count; i++) {
Ivar myIvar = ivarList[i];
const char *ivarName = ivar_getName(myIvar);
NSLog(@"Ivar --> %@", [NSString stringWithUTF8String:ivarName]);
}
// 获取协议列表
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
for (unsigned int i; i < count; i++) {
Protocol *myProtocal = protocolList[i];
const char *protocolName = protocol_getName(myProtocal);
NSLog(@"protocol --> %@", [NSString stringWithUTF8String:protocolName]);
}
}
不过需要注意:
a )需要给类加几个属性、方法,遵循一些协议,否则是看不到输出信息的。
b )要使用这些获取的方法,需要导入头文件 #import
b) 拦截调用与动态添加
拦截调用:
就是在所有地方都找不到方法的实现的话,会出发拦截调用,可以自己去做一些处理避免程序崩溃。那在什么地方做处理呢?
NSObject有四个方法可以用来做处理:
// 调用不存在的类方法时触发,默认返回NO,可以加上自己的处理后返回YES
+ (BOOL)resolveClassMethod:(SEL)sel;
// 调用不存在的实例方法时触发,默认返回NO,可以加上自己的处理后返回YES
+ (BOOL)resolveInstanceMethod:(SEL)sel;
// 将调用的不存在的方法重定向到一个其他声明了这个方法的类里去,返回那个类的target
- (id)forwardingTargetForSelector:(SEL)aSelector;
// 将调用的不存在的方法打包成 NSInvocation 给你,自己处理后调用 invokeWithTarget: 方法让某个类来触发
- (void)forwardInvocation:(NSInvocation *)anInvocation;
例子:
@interface ViewController ()
@property (nonatomic, strong) UIButton *logBtn;
- (void)notHas;// 要调用的实例方法,没有具体实现
@end
- (void)viewDidLoad {
[super viewDidLoad];
// 添加按钮
self.logBtn = [[UIButton alloc] initWithFrame:CGRectMake(100, 200, 100, 20)];
[self.logBtn setTitle:@"测 试" forState:UIControlStateNormal];
[self.logBtn setTitleColor:[UIColor lightGrayColor] forState:UIControlStateNormal];
// 添加没有实现的点击事件
[self.logBtn addTarget:self action:@selector(notHas) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:self.logBtn];
}
// 拦截对不存在的方法的调用
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"notFind!");
// 给本类动态添加一个方法
if ([NSStringFromSelector(sel) isEqualToString:@"notHas"]) {
class_addMethod(self, sel, (IMP)runAddMethod, "v@:*");
}
// 注意要返回YES
return YES;
}
// 要动态添加的方法,这是一个C方法
void runAddMethod(id self, SEL _cmd, NSString *string) {
NSLog(@"动态添加一个方法来提示");
}