Runtime的运行机制

之前在项目中有遇到过用runtime解决改变全局字体的问题,所以再一次感受到了runtime的黑魔法的强大,趁现在有机会分享一下对runtime的理解。

在对象调用方法是Objective-C中经常使用的功能,也就是消息的传递,而Objective-C是C的超集,所以和C不同的是,Objective-C使用的是动态绑定,也就是runtime。Objective-C的消息传递和消息机制也就不多说了,今天主要说的是动态方法,也就是函数的调用。

每个函数调用的先后以及执行的前提

引入

对象在收到无法解读的消息后,首先会调用所属类的+(BOOL)resolveInstanceMethod:(SEL)sel;
在这个方法运行时,没有找到SEL的IML时就会执行,这个函数是给类利用class_addMethod添加函数的机会,根据文档,如果实现了添加函数代码则返回YES,未实现返回NO,

1
2
3
4
5
6
7
8
9
@interface ViewController()
@end

@implement ViewController
- (void) viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(dosomething)];
}
@end

不出意外的话,程序崩溃,因为没有找到doSomething这个方法,下面我们在里面实现+(BOOL)resolveInstanceMethod:(SEL)sel这个方法,并且判断如果SEL是dosomething那就输出add method here

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void) viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(doSomeThing)];
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
if(sel == @selector(dosomething)) {
class_addMethod([self class],sel,(IMP)dynamicMeThodIMP,"v@:");
return YES;
}
}

这里需要添加这个方法来进行处理 不然代码也是会出现crash的
void dynamicMethodIMP (id self, SEL _cmd) {
NSLog(@"doSomething SEL");
}

通过上述代码,我们已经通过runtime成功的向我们这个类中添加了一个方法,关于class_addMethod这个方法,是这样定义的:
OBJC_EXPORT BOOL class_addMethod(class cls , SEL name , IMP imp , const char *types);
参数分别是所添加的类,方法名, 实现这个方法的函数,types定义该返回值类型和参数类型的字符串,这里比如@”V@:”,其实V就是void,代表返回类型是空,@代表参数 ,这里指的是id(self),这里:指方法SEL(_cmd),比如我们扩展一个函数

1
2
3
int newMethod(id self , SEL _cmd , NSString *str) {
return 100;
}

那么添加这个函数的方法应该是class_addMethod([self class] , @selector(newMethod) , (IMP)newMethod,”i@:@”)

如果+(BOOL)resolveInstanceMethod:(SEL)sel中没有找到或者添加方法
消息继续往下传到:-(id)forwordingTargetForSelector:(SEL)aSelector;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#import @interface ViewController ()
@end

@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:@selector(secondVCMethod)];
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
Class class = NSClassFromString(@"SecondViewController");
UIViewController *vc = class.new;
if (aSelector == NSSelectorFromString(@"secondVCMethod")) {
NSLog(@"secondVC do this !");//当然在secondviewcontroller里面是有这个类的 并且是含有这个方法的
return vc;
}
return nil;
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
return [super resolveInstanceMethod:sel];
}

- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
@end

在消息没有找到-(void)secondVCMethod这个方法的时候,消息继续传递,直到-(id)forwardingTargetForSelector:(SEL)aSelector,然后我在创建了一个SecondViewController的对象,并且判断如果有这个方法,就返回SecondViewController的对象,这个函数就是消息转发,在这里我们成功的把消息传给了SecondViewController,然后让他来执行,所以就执行了那个方法,同时也相当于完成了一个多继承。

最后一点:

当然还有好几个函数,在上面那个图里已经清晰的表达,有兴趣的可以自己试试,看看消息的床底顺序到底是什么怎么样的,上面提到的这些知识runtime知识冰山一角,runtime黑魔法的强大远不止于此 ,比如方法的调配(method swizzling)等,在项目实战中还有很有用的,后面有时间再介绍。