一、關(guān)于runtime
之前在項目中有遇到過用runtime解決改變?nèi)肿煮w的問題,所以再一次感受到了runtime黑魔法的強大,趁現(xiàn)在有機會分享一下對runtime的一些理解。在對象調(diào)用方法是Objective-C中經(jīng)常使用的功能绍绘,也就是消息的傳遞,而Objective-C是C的超集迟赃,所以和C不同的是陪拘,Objective-C使用的是動態(tài)綁定,也就是runtime纤壁。Objective-C的消息傳遞和消息機制也就不多說了左刽,今天主要說的是動態(tài)方法,也就是函數(shù)的調(diào)用酌媒。
二欠痴、相關(guān)的幾個函數(shù)
下面一張圖詳細的概括了每個函數(shù)調(diào)用的先后以及執(zhí)行的前提
消息傳遞函數(shù)的調(diào)用
1.對象在收到無法解讀的消息后,首先會調(diào)用所屬類的
1
+?(BOOL)resolveInstanceMethod:(SEL)sel
這個方法在運行時秒咨,沒有找到SEL的IML時就會執(zhí)行喇辽。這個函數(shù)是給類利用class_addMethod添加函數(shù)的機會。根據(jù)文檔雨席,如果實現(xiàn)了添加函數(shù)代碼則返回YES菩咨,未實現(xiàn)返回NO。舉個例子陡厘,新建了一個工程抽米,首先我在ViewController這個類中執(zhí)行doSomething1這個方法,代碼如下
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//
//??ViewController.m
//??RuntimeTest1
//
//??Created?by?HenryCheng?on?15/12/24.
//??Copyright??(版權(quán)符號)?2015年?www.igancao.com??All?rights?reserved.
//
#import?"ViewController.h"
@interface?ViewController?()
@end
@implementation?ViewController
-?(void)viewDidLoad?{
[superviewDidLoad];
[self?performSelector:@selector(doSomething)];
}
-?(void)didReceiveMemoryWarning?{
[superdidReceiveMemoryWarning];
//?Dispose?of?any?resources?that?can?be?recreated.
}
@end
運行結(jié)果
1
2
3**2015-12-24?10:35:37.726?RuntimeTest1[1877:337842]?-[ViewController?doSomething]:?unrecognized?selector?sent?to?instance?0x7fe9f3736680**
**2015-12-24?10:35:37.729?RuntimeTest1[1877:337842]?***?Terminating?app?due?to?uncaught?exception'NSInvalidArgumentException',?reason:'-[ViewController?doSomething]:?unrecognized?selector?sent?to?instance?0x7fe9f3736680'**
*****?Firstthrowcall?stack:**
不出意外糙置,程序崩潰云茸,因為沒有找到doSomething這個方法,下面我們在里面實現(xiàn) + (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
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35//
//??ViewController.m
//??RuntimeTest1
//
//??Created?by?HenryCheng?on?15/12/24.
//??Copyright??(版權(quán)符號)?2015年?www.igancao.com?All?rights?reserved.
//
#import?"ViewController.h"
@interface?ViewController?()
@end
@implementation?ViewController
-?(void)viewDidLoad?{
[superviewDidLoad];
[self?performSelector:@selector(doSomething)];
}
+?(BOOL)resolveInstanceMethod:(SEL)sel?{
if(sel?==?@selector(doSomething))?{
NSLog(@"add?method?here");
returnYES;
}
return[superresolveInstanceMethod:sel];
}
-?(void)didReceiveMemoryWarning?{
[superdidReceiveMemoryWarning];
//?Dispose?of?any?resources?that?can?be?recreated.
}
@end
繼續(xù)運行标捺,然后看到log
1
2
3
4**2015-12-24?10:47:24.687?RuntimeTest1[2007:382077]?add?method?here**
**2015-12-24?10:47:24.687?RuntimeTest1[2007:382077]?-[ViewController?doSomething]:?unrecognized?selector?sent?to?instance?0x7f9568c331f0**
**2015-12-24?10:47:24.690?RuntimeTest1[2007:382077]?***?Terminating?app?due?to?uncaught?exception'NSInvalidArgumentException',?reason:'-[ViewController?doSomething]:?unrecognized?selector?sent?to?instance?0x7f9568c331f0'**
*****?Firstthrowcall?stack:**
可以看到程序依然是崩潰了懊纳,但是我們可以看到輸出了add method here,這說明我們 + (BOOL)resolveInstanceMethod:(SEL)sel這個方法執(zhí)行了亡容,并進入了判斷嗤疯,所以,在這兒萍倡,我們可以做一下操作身弊,使這個方法得到相應(yīng)辟汰,不至于走到最后- (void)doesNotRecognizeSelector:(SEL)aSelector這個方法中而崩掉了列敲,接下來,我么繼續(xù)操作帖汞,如下
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41//
//??ViewController.m
//??RuntimeTest1
//
//??Created?by?HenryCheng?on?15/12/24.
//??Copyright??(版權(quán)符號)?2015年?www.igancao.com?All?rights?reserved.
//
#import?"ViewController.h"
#import?[objc/runtime.h](因識別問題戴而,此處將尖括號改為方括號)
@interface?ViewController?()
@end
@implementation?ViewController
-?(void)viewDidLoad?{
[superviewDidLoad];
[self?performSelector:@selector(doSomething)];
}
+?(BOOL)resolveInstanceMethod:(SEL)sel?{
if(sel?==?@selector(doSomething))?{
NSLog(@"add?method?here");
class_addMethod([self?class],?sel,?(IMP)dynamicMethodIMP,"v@:");
returnYES;
}
return[superresolveInstanceMethod:sel];
}
void?dynamicMethodIMP?(id?self,?SEL?_cmd)?{
NSLog(@"doSomething?SEL");
}
-?(void)didReceiveMemoryWarning?{
[superdidReceiveMemoryWarning];
//?Dispose?of?any?resources?that?can?be?recreated.
}
@end
導(dǎo)入了并且在+ (BOOL)resolveInstanceMethod:(SEL)sel中執(zhí)行了class_addMethod這個方法,然后定義了一個void dynamicMethodIMP (id self, SEL _cmd)這個函數(shù)翩蘸,運行工程所意,看log
1
2**2015-12-24?11:45:11.934?RuntimeTest1[2284:478571]?add?method?here**
**2015-12-24?11:45:11.934?RuntimeTest1[2284:478571]?doSomething?SEL**
這時候我們發(fā)現(xiàn),程序并沒有崩潰催首,而且還輸出了doSomething SEL扶踊,這時候就說明我們已經(jīng)通過runtime成功的向我們這個類中添加了一個方法。關(guān)于class_addMethod這個方法郎任,是這樣定義的
1
OBJC_EXPORT?BOOL?class_addMethod(Class?cls,?SEL?name,?IMP?imp,??const?char?*types)
cls ? 在這個類中添加方法秧耗,也就是方法所添加的類
name ?方法名,這個可以隨便起的
imp ? 實現(xiàn)這個方法的函數(shù)
types 定義該數(shù)返回值類型和參數(shù)類型的字符串舶治,這里比如"v@:"分井,其中v就是void,帶表返回類型就是空霉猛,@代表參數(shù)尺锚,這里指的是id(self),這里:指的是方法SEL(_cmd)惜浅,比如我再定義一個函數(shù)
1
2
3
4int?newMethod?(id?self,?SEL?_cmd,?NSString?*str)?{
return100;
}
那么添加這個函數(shù)的方法就應(yīng)該是ass_addMethod([self class], @selector(newMethod), (IMP)newMethod, "i@:@");
2.如果在+ (BOOL)resolveInstanceMethod:(SEL)sel中沒有找到或者添加方法
消息繼續(xù)往下傳遞到- (id)forwardingTargetForSelector:(SEL)aSelector看看是不是有對象可以執(zhí)行這個方法瘫辩,我們來重新建個工程,然后新建一個叫SecondViewController的類坛悉,里面有一個- (void)secondVCMethod方法杭朱,如下
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41//
//??SecondViewController.m
//??RuntimeTest2
//
//??Created?by?HenryCheng?on?15/12/24.
//??Copyright??(版權(quán)符號)?2015年??www.igancao.com?All?rights?reserved.
//
#import?"SecondViewController.h"
@interface?SecondViewController?()
@end
@implementation?SecondViewController
-?(void)viewDidLoad?{
[superviewDidLoad];
//?Do?any?additional?setup?after?loading?the?view.
}
-?(void)secondVCMethod?{
NSLog(@"This?is?secondVC?method?!");
}
-?(void)didReceiveMemoryWarning?{
[superdidReceiveMemoryWarning];
//?Dispose?of?any?resources?that?can?be?recreated.
}
/*
#pragma?mark?-?Navigation
//?In?a?storyboard-based?application,?you?will?often?want?to?do?a?little?preparation?before?navigation
-?(void)prepareForSegue:(UIStoryboardSegue?*)segue?sender:(id)sender?{
//?Get?the?new?view?controller?using?[segue?destinationViewController].
//?Pass?the?selected?object?to?the?new?view?controller.
}
*/
@end
工程結(jié)構(gòu)應(yīng)該是這樣的
工程目錄圖
現(xiàn)在我想在ViewController中調(diào)用- (void)secondVCMethod這個方法,我們知道ViewController和SecondViewController并無繼承關(guān)系吹散,按照正常的步驟去做程序肯定會因為在ViewController找不到- (void)secondVCMethod這個方法而直接崩潰的
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//
//??ViewController.m
//??RuntimeTest2
//
//??Created?by?HenryCheng?on?15/12/24.
//??Copyright??(版權(quán)符號)?2015年?www.igancao.com??All?rights?reserved.
//
#import?"ViewController.h"
#import?@interface?ViewController?()
@end
@implementation?ViewController
-?(void)viewDidLoad?{
[superviewDidLoad];
[self?performSelector:@selector(secondVCMethod)];
}
-?(void)didReceiveMemoryWarning?{
[superdidReceiveMemoryWarning];
//?Dispose?of?any?resources?that?can?be?recreated.
}
@end
運行結(jié)果
1
2
3**2015-12-24?13:54:44.314?RuntimeTest2[3164:835814]?-[ViewController?secondVCMethod]:?unrecognized?selector?sent?to?instance?0x7fc3a8535c10**
**2015-12-24?13:54:44.317?RuntimeTest2[3164:835814]?***?Terminating?app?due?to?uncaught?exception'NSInvalidArgumentException',?reason:'-[ViewController?secondVCMethod]:?unrecognized?selector?sent?to?instance?0x7fc3a8535c10'**
*****?Firstthrowcall?stack:**
現(xiàn)在我們來處理一下這個消息弧械,如下
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41//
//??ViewController.m
//??RuntimeTest2
//
//??Created?by?HenryCheng?on?15/12/24.
//??Copyright??(版權(quán)符號)?2015年?www.igancao.com?All?rights?reserved.
//
#import?"ViewController.h"
#import?@interface?ViewController?()
@end
@implementation?ViewController
-?(void)viewDidLoad?{
[superviewDidLoad];
[self?performSelector:@selector(secondVCMethod)];
}
-?(id)forwardingTargetForSelector:(SEL)aSelector?{
Class?class?=?NSClassFromString(@"SecondViewController");
UIViewController?*vc?=?class.new;
if(aSelector?==?NSSelectorFromString(@"secondVCMethod"))?{
NSLog(@"secondVC?do?this?!");
returnvc;
}
returnnil;
}
+?(BOOL)resolveInstanceMethod:(SEL)sel?{
return[superresolveInstanceMethod:sel];
}
-?(void)didReceiveMemoryWarning?{
[superdidReceiveMemoryWarning];
//?Dispose?of?any?resources?that?can?be?recreated.
}
@end
運行結(jié)果
1
2**2015-12-24?14:00:34.168?RuntimeTest2[3284:870957]?secondVCdothis!**
**2015-12-24?14:00:34.169?RuntimeTest2[3284:870957]?This?is?secondVC?method?!**
我們會發(fā)現(xiàn)- (void)secondVCMethod這個方法執(zhí)行了,程序也并沒有崩潰空民,原因就是在這一步
1
2
3
4
5
6
7
8
9
10-?(id)forwardingTargetForSelector:(SEL)aSelector?{
Class?class?=?NSClassFromString(@"SecondViewController");
UIViewController?*vc?=?class.new;
if(aSelector?==?NSSelectorFromString(@"secondVCMethod"))?{
NSLog(@"secondVC?do?this?!");
returnvc;
}
returnnil;
}
在沒有找到- (void)secondVCMethod這個方法的時候刃唐,消息繼續(xù)傳遞羞迷,直到- (id)forwardingTargetForSelector:(SEL)aSelector,然后我在里面創(chuàng)建了一個SecondViewController的對象画饥,并且判斷如過有這個方法衔瓮,就返回SecondViewController的對象。這個函數(shù)就是消息的轉(zhuǎn)發(fā)抖甘,在這兒我們成功的把消息傳給了SecondViewController热鞍,然后讓它來執(zhí)行,所以就執(zhí)行了那個方法衔彻。同時薇宠,也相當(dāng)于完成了一個多繼承!
三艰额、最后一點
當(dāng)然澄港,還有好幾個函數(shù),在上面那張圖里面已經(jīng)清晰的表達了柄沮,有興趣的可以自己試試回梧,看看消息的傳遞順序到底是怎么樣的。上面提到的這些知識runtime的冰山一角祖搓,runtime黑魔法的強大遠不止于此狱意,比如方法的調(diào)配(Method Swizzling)等,在項目實戰(zhàn)中還是很有用的拯欧,后面有時間會再介紹.