前言
今天我們再來通過另外一個(gè)機(jī)制來感受一下OC的動(dòng)態(tài)特性吧,那就是OC的消息轉(zhuǎn)發(fā)機(jī)制
在之前的不一樣的OC中我們有提到,OC是消息型語言,OC中的方法調(diào)用其實(shí)只是傳遞消息而已,編譯器并不能決定程序真正執(zhí)行的到底是哪段代碼,這個(gè)工作,需要運(yùn)行時(shí)系統(tǒng)來完成.
那我們今天要討論的消息轉(zhuǎn)發(fā)又是什么呢?
既然OC的方法調(diào)用實(shí)際上都是消息傳遞,那傳遞的消息內(nèi)容和時(shí)機(jī)其實(shí)編譯器并不能完全控制,這時(shí)我們是不是能夠聯(lián)想到這樣的場景:一個(gè)對象接受了一個(gè)編譯器控制外的消息,這個(gè)對象對這個(gè)消息無從下手,他并不具備解決這個(gè)消息的能力(沒有實(shí)現(xiàn)相應(yīng)方法),這個(gè)時(shí)候該怎么辦呢?
當(dāng)遇到這種情況的時(shí)候,我們的消息轉(zhuǎn)發(fā)機(jī)制就被觸發(fā)了,系統(tǒng)將利用這套機(jī)制竭力幫我們控制這個(gè)糟糕的局面,不至于讓程序崩潰,但是如果消息轉(zhuǎn)發(fā)機(jī)制竭盡全力通過各種辦法也無法解決這個(gè)意料之外的消息時(shí),那么程序就會崩潰,并產(chǎn)生那個(gè)經(jīng)典的崩潰日志:
unrecognized selector sent to instance 0x60000000b480
消息轉(zhuǎn)發(fā)機(jī)制的三部曲
消息轉(zhuǎn)發(fā)機(jī)制在極力為我們收拾殘局的時(shí)候,會有三步措施,如果在最壞的情況下,這三個(gè)步驟將會按順序依次執(zhí)行,如果這三個(gè)步驟都無法挽回,那么程序?qū)罎?值得一提的是:如果消息轉(zhuǎn)發(fā)機(jī)制的某一個(gè)步驟就能解決這個(gè)問題,那么下一個(gè)步驟就不會執(zhí)行了,消息轉(zhuǎn)發(fā)機(jī)制就到此為止了(事情都解決了,當(dāng)然不需要下一步了)
我們現(xiàn)在用一個(gè)簡單的例子來觸發(fā)消息轉(zhuǎn)發(fā)機(jī)制,并利用消息轉(zhuǎn)發(fā)機(jī)制解決問題
我新建了一個(gè)叫做Person的類,并在它的.h文件里為Person聲明了一個(gè)實(shí)例方法:run
以下是.h文件的內(nèi)容.
#import <Foundation/Foundation.h>
@interface Person : NSObject
-(void)run;
@end
大家猜猜我在.m文件里做了什么?以下是.m文件的內(nèi)容:
#import "Person.h"
@implementation Person
@end
哈哈,我什么都沒做,甚至沒有對.h中的run方法寫實(shí)現(xiàn).
我為什么要這么做呢?這樣我們就可以欺騙編譯器,在調(diào)用這個(gè)方法的時(shí)候,編譯器默認(rèn)我們已經(jīng)對這個(gè)方法寫了實(shí)現(xiàn),他就會讓我們舒舒服服的調(diào)用這個(gè)方法,并不會報(bào)錯(cuò),組織我們隊(duì)這個(gè)方法的調(diào)用
那接下來,我們在控制器中新建一個(gè)person對象,并調(diào)用這個(gè)方法的run方法
Person *p1=[Person new];
[p1 run];
大家應(yīng)該猜的到會發(fā)生什么了吧,我們能夠成功編譯并運(yùn)行這個(gè)程序,但是當(dāng)代碼執(zhí)行到了run方法時(shí),程序崩潰了,并報(bào)了我們常見的那個(gè)錯(cuò)誤unrecognized selector sent to instance 0x60000000b480
大家一定想知道這其中到底發(fā)生了什么事情:當(dāng)run方法被調(diào)用時(shí),運(yùn)行時(shí)系統(tǒng)就會對p1這個(gè)對象發(fā)送一條run的消息,意料之中的是p1這個(gè)對象并不能對相應(yīng)這個(gè)消息,因?yàn)槲覀儧]有寫run的實(shí)現(xiàn)方法,那接下來就會觸發(fā)消息轉(zhuǎn)發(fā)機(jī)制了,那既然觸發(fā)了消息轉(zhuǎn)發(fā)機(jī)制,為什么還是崩潰了呢,因?yàn)?這就是那種最壞的情況,消息轉(zhuǎn)發(fā)機(jī)制三個(gè)步驟依次執(zhí)行卻依然沒有挽回崩潰的結(jié)果.
那么我們快看一看怎么讓消息轉(zhuǎn)發(fā)機(jī)制發(fā)揮作用吧.
步驟一:能不能動(dòng)態(tài)添加一個(gè)函數(shù)解決這個(gè)消息啊
在這一步中,面對無法解決的消息,消息轉(zhuǎn)發(fā)機(jī)制首先做的是,看看能否動(dòng)態(tài)添加一個(gè)方法實(shí)現(xiàn),來解決這個(gè)消息.
如果是實(shí)例方法,系統(tǒng)會調(diào)用這個(gè)這個(gè)實(shí)例所屬類的類方法:
+(BOOL)resolveInstanceMethod:(SEL)sel
如果是類方法:
+(BOOL)resolveClassMethod:(SEL)sel
這對方法如何使用呢?我們來看:
我在person類的.m文件中寫了如下代碼:
+(BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel==@selector(run)) {
class_addMethod(self, sel, (IMP)run , "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void run (id self,SEL _cmd)
{
NSLog(@"fdfsfaf");
}
我們寫了一個(gè)run的c語言函數(shù),當(dāng)消息機(jī)制觸發(fā)時(shí),selector為run時(shí)那么就會利用運(yùn)行時(shí)庫動(dòng)態(tài)添加一個(gè)方法,而這個(gè)方法的函數(shù)實(shí)現(xiàn)是我們用C寫的一個(gè)函數(shù),這個(gè)函數(shù)的兩個(gè)參數(shù)為self,和_cmd,因?yàn)镺C方法的本質(zhì)就是至少包含兩個(gè)參數(shù)的C函數(shù)這兩個(gè)參數(shù)便是隱藏的self,和_cmd,前者是消息接受者,后者是一個(gè)SEL指針
通過這樣的操作,系統(tǒng)就為我們通過動(dòng)態(tài)添加一個(gè)方法的方式來解決了這個(gè)不能響應(yīng)的消息
這也是消息轉(zhuǎn)發(fā)機(jī)制的第一步,如果這一步并沒有解決這個(gè)問題,那便要有下面的步驟了.
步驟二:能不能找別人處理這個(gè)消息啊
當(dāng)上一步?jīng)]有解決這個(gè)問題的時(shí)候,這一步,系統(tǒng)將會通過尋找有沒有別的對象可以幫忙處理這條消息.
系統(tǒng)會調(diào)用如下方法:
-(id)forwardingTargetForSelector:(SEL)aSelector
{
return [RunPerson new];
}
大家可以看到,我在這個(gè)方法中返回了一個(gè)新的類RunPerson的實(shí)例,而在這個(gè)類的.m文件中,我寫了如下實(shí)現(xiàn):
-(void)run
{
NSLog(@"我是會跑的人,我能跑!");
}
通過返回一個(gè)新的類,我們把這個(gè)消息轉(zhuǎn)發(fā)給了一個(gè)其它對象,讓這個(gè)對象來代替我們解決這個(gè)消息.
步驟三: 完整的消息轉(zhuǎn)發(fā)
在這一步中,消息轉(zhuǎn)發(fā)機(jī)制將進(jìn)行最后一步操作
我在Person的.m文件中寫了如下代碼,將會使得run消息,在消息轉(zhuǎn)發(fā)機(jī)制最后一步得到處理
-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
if (aSelector==@selector(run)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector: aSelector];
}
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
SEL selector =[anInvocation selector];
RunPerson *RP1=[RunPerson new];
RunPerson *RP2=[RunPerson new];
if ([RP1 respondsToSelector:selector]) {
[anInvocation invokeWithTarget:RP1];
}
if ([RP2 respondsToSelector:selector]) {
[anInvocation invokeWithTarget:RP2];
}
}
第一個(gè)方法返回的是一個(gè)方法簽名,而代碼中的v@:表示這個(gè)函數(shù)的性質(zhì),v代表返回值為void,@代表self,:代表_cmd;
當(dāng)有了方法簽名,消息轉(zhuǎn)發(fā)機(jī)制將會調(diào)用-(void)forwardInvocation:(NSInvocation )anInvocation方法,這個(gè)方法的參數(shù)anInvocation*為這個(gè)消息的全部內(nèi)容,包括調(diào)用者,參數(shù)等等.
在這個(gè)方法里我們初始化了兩個(gè)RunPerson實(shí)例,并把這個(gè)Invocation轉(zhuǎn)發(fā)給了這兩個(gè)RunPerson實(shí)例,這樣,這兩個(gè)RunPerson對象就都會對這個(gè)消息進(jìn)行處理了
這也是與第二個(gè)步驟最大的區(qū)別,可以轉(zhuǎn)發(fā)給多個(gè)對象進(jìn)行處理.
到此,消息轉(zhuǎn)發(fā)的三個(gè)步驟就結(jié)束了,如果到第三步還沒有解決這個(gè)消息,那么程序就會崩潰了.