runtime可以幫助我們實現(xiàn)一些oc層的api達不到的功能。那就先需要了解一下。
一匿情、消息轉(zhuǎn)發(fā)
oc中的動態(tài)特性兰迫,就是他在運行的時候,才能確定某些東西炬称。比如實現(xiàn)方法這個過程汁果。實際上是一個發(fā)送消息的過程。這個消息玲躯,也許是由接受者執(zhí)行据德,也可能是由開發(fā)者設定的其他對象執(zhí)行。消息與方法的綁定跷车,也是運行時才確定.
消息的發(fā)送過程是這樣的:
1.通過對象的isa指針找到它的類
2.在類的method list 中找到這個方法(根據(jù)SEL來找棘利,方法的唯一辨識)
3.如果class中沒有這個方法,就沿著這個類的isa指針 指向它的superclass去找
4.一旦找到朽缴,則執(zhí)行這個方法的IMP(函數(shù)指針善玫,指向方法執(zhí)行的首地址)
但是,如果每次都要這么找的話密强,沒有必要每次都在methodlist中循環(huán)查找茅郎,所以,一旦找到了這個方法或渤,就存為class_cache系冗,下一次就先在這里找,所以這一步應在執(zhí)行在上文步驟1的前面薪鹦。
如果找不到掌敬,而開發(fā)者沒有做任何處理。那么自然會出現(xiàn) unrecognized selector sent to instance的錯誤
其實在這個異常出現(xiàn)之前距芬,會有三個步驟來轉(zhuǎn)發(fā)這個消息涝开,如果我們不做任何一個動作,則會異常框仔。
1.動態(tài)的給這個消息添加一個實現(xiàn)方法
對象在接收到未知的消息時舀武,會調(diào)用
+ (BOOL)resolveInstanceMethod:(SEL)sel ;如果是類方法离斩,則是+ (BOOL)resolveClassMethod:(SEL)sel
在該方法中银舱,我們可以將實現(xiàn)了的方法添加到這個消息里面
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(w)) {
class_addMethod([self class], sel, (IMP)w, "v@:@");
//找到了方法然后添加了實現(xiàn),返回yes跛梗。
return YES;
}
return [super resolveInstanceMethod:sel];
}
void w(id self,SEL _cmd, NSString * name){
NSLog(@"%@",name);
}
這里需要理解一下class_addMethod
先看一下官方文檔怎么說
首先寻馏,
第一個參數(shù),是要添加的類核偿,自然要傳[self class]
第二個參數(shù) name诚欠,是方法的名稱,也就是SEL的類型,用@selector可以獲取
第三個參數(shù)imp轰绵,也就是要給這個方法添加的實現(xiàn)函數(shù)粉寞,文檔里面說了,這個函數(shù)必須要接受兩個參數(shù)左腔,一個是self唧垦,一個是_cmd
第四個參數(shù):types,對imp的描述液样。至少有三個參數(shù):第一個是返回類型振亮,第二個是self,第三個是sel類型的_cmd,列子中用的是“v @:@”v代表鞭莽,返回類型void
坊秸,@代表idleix,:代表sel類型撮抓,第二個@代表nsstring類型妇斤。
這里面的轉(zhuǎn)換類型,可以在官方文檔中搜Type Encodings查找
2丹拯,如果在上一步?jīng)]有做任何處理站超,runtime會執(zhí)行這個方法,嘗試把這個消息發(fā)給另一個對象來執(zhí)行乖酬。
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(setBackgroundColor:)) {
return label;
}
return [super forwardingTargetForSelector:aSelector];
}
這一步只合適于我們將消息轉(zhuǎn)發(fā)到另一個能處理該消息的對象死相,但是不能對該消息做任何處理
3,如果上一步還不能處理咬像,則會走這個方法
- (void)forwardInvocation:(NSInvocation *)anInvocation
//拋出異常之前算撮,會給對象發(fā)送這個消息forwardInvocation
//anInvocation這個參數(shù)包括selector,目標(target)和參數(shù)
- (void)forwardInvocation:(NSInvocation *)anInvocation
//拋出異常之前县昂,會給對象發(fā)送這個消息forwardInvocation
這個參數(shù)包裝了原始消息和對應的參數(shù)
SEL sel = anInvocation.selector;
if ([label respondsToSelector:sel]) {
[anInvocation invokeWithTarget:label];把消息轉(zhuǎn)發(fā)給label
}else {
[self doesNotRecognizeSelector:sel];
}
}
用這個方法的時候肮柜,我們必須要重寫另一個方法:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
因為上一個方法中的參數(shù)使用從這個方法創(chuàng)建的NSInvocation對象,所以必須要重寫方法倒彰。
- (NSMethodSignature *)methodSignatureForSelector: (SEL)aSelector {
NSMethodSignature *s = [super methodSignatureForSelector: aSelector];
if (!s ) {
s = [label methodSignatureForSelector:aSelector];
}
return s;
}
這里是需要重新給method簽名审洞,method里面包三個對象:一個sel,一個method_types,一個是imp待讳,sel是根據(jù)方法名和參數(shù)類型生成唯一識別芒澜,method_type包括了消息的所有參數(shù)類型(包括兩個隱藏參數(shù):一個self,一個_cmd也就是SEL類型)创淡,imp就是函數(shù)指針痴晦。
消息轉(zhuǎn)發(fā)的過程就是這樣的。
下面介紹一下上面提到的SEL,IMP琳彩,Method
SEL
SEL是表示一個方法的selector指針誊酌,oc會根據(jù)不同方法的名字部凑,參數(shù)序列,生成唯一的標識术辐,也就是這個指針的地址,所以就不難理解為什么oc中不能定義參數(shù)不同砚尽,但是方法名相同的方法了施无。
所以我們現(xiàn)在知道了辉词,SEL是一個指向方法的指針。 對于我們而言猾骡,也就是這個方法的函數(shù)名(唯一辨識)瑞躺;
我們可以通過@selector()或者NSSelectorFromString()得到這個指針;
IMP
上文提到,IMP是一個函數(shù)指針兴想,指向方法實現(xiàn)的首地址幢哨。通過SEL能夠找到對應的IMP。
Method
Method表示方法嫂便,包含
一個SEL
一個 method_types
一個 IMP
這里就相當于SEL和IMP之間有了映射捞镰。
所以現(xiàn)在不難了解開頭說的,消息與方法的綁定(objc_msgSend(receiver,selector,參數(shù)···))毙替,也是運行時才確定
1岸售,首先找到這個selector對應的方法實現(xiàn)。因為不同的類中對同一方法有不同的實現(xiàn),需要根據(jù)接受者找到確切的實現(xiàn)。
2茸时,找到了SEL之后东囚,找到IMP,然后將接受者對象本身恩急,以及SEL本身和其他參數(shù)傳給他
3,將IMP的返回作為這個綁定操作的返回。
所以也不難理解腻惠,為什么[self class]和[super class]會打印出self這個子類。因為在super中調(diào)用的這個class方法欲虚,傳入的第一個參數(shù)self是子類集灌。