OC的消息轉(zhuǎn)發(fā)

大家都知道,OC是一門(mén)動(dòng)態(tài)語(yǔ)言 ,傳統(tǒng)的靜態(tài)語(yǔ)言如C語(yǔ)言,在編譯的時(shí)候已經(jīng)決定了要調(diào)用的方法。而對(duì)于像OC這樣的動(dòng)態(tài)語(yǔ)言,由于有runtime機(jī)制,所以編譯階段并不知道要調(diào)用的具體方法實(shí)現(xiàn),在運(yùn)行的時(shí)候才會(huì)去調(diào)用,這也導(dǎo)致了一些case比如給某個(gè)target指定了action的SEL,但是沒(méi)有對(duì)這個(gè)SEL對(duì)應(yīng)的方法做具體實(shí)現(xiàn),在編譯的時(shí)候非常No Problem,但是在運(yùn)行的時(shí)候就會(huì)因?yàn)闆](méi)有找到對(duì)應(yīng)的實(shí)現(xiàn)而crash。而這種情況也是我們經(jīng)常會(huì)碰到的。

那么該怎么處理這種case呢?

研究了runtime的一些原理以及OC對(duì)象的內(nèi)存布局 ,我們可以得出這么幾個(gè)結(jié)論:
1、OC對(duì)象調(diào)用方法會(huì)用到選擇器selector(也就是SEL),通過(guò)SEL來(lái)找到具體的實(shí)現(xiàn)IMP
2、SEL是具體方法的一個(gè)索引,IMP是具體實(shí)現(xiàn)的函數(shù)指針,兩者配合可以快速找到具體實(shí)現(xiàn)的方法并調(diào)用
3、每個(gè)OC的類其實(shí)就是個(gè)結(jié)構(gòu)體,查看objc/runtime.h中objc_class結(jié)構(gòu)體的定義如下

struct objc_class
{
    struct objc_class* isa;//isa指針指向本類
    struct objc_class* super_class;//父類指針,可以根據(jù)此找到父類
    const char* name;//類名
    long version;//類的版本信息,默認(rèn)為0(一般不怎么關(guān)注)
    long info;//類信息,供運(yùn)行期使用的一些位標(biāo)識(shí)
    long instance_size;// 該類的實(shí)例變量大小
    struct objc_ivar_list* ivars;//成員變量列表
    struct objc_method_list** methodLists;//方法列表
    struct objc_cache* cache;//方法緩存
    struct objc_protocol_list* protocols;//協(xié)議方法列表
};

4、對(duì)象查找方法實(shí)現(xiàn)的過(guò)程:

1 先到cache方法緩存中找,有則調(diào)用,沒(méi)有則下一步
2 到methodLists方法列表中找,有則調(diào)用,沒(méi)有則下一步
3 本類中沒(méi)找到,就到super_class的methodLists中找,若還沒(méi)有,就一直往上找,找到最頂層還沒(méi)有的話就crash了。

但是在crash之前,其實(shí)有3個(gè)消息轉(zhuǎn)發(fā)的補(bǔ)救方式,來(lái)防止crash。當(dāng)然若沒(méi)有采取什么"補(bǔ)救方式",就直接crash了。這三個(gè)補(bǔ)救方式簡(jiǎn)單講就是:

1 動(dòng)態(tài)向類中加入缺失的方法
2 指定處理消息的接受者(備用的接受者,就像快遞電話來(lái)了你沒(méi)空,可以找同學(xué)來(lái)幫忙簽收一下)
3 使用NSInvocation進(jìn)行消息轉(zhuǎn)發(fā)

這三種補(bǔ)救方法涉及到的API有:

//動(dòng)態(tài)方法解析
+(BOOL) resolveInstanceMethod:(SEL)selector
+(BOOL)resolveClassMethod:(SEL)sel

//備用接收者
-(id)forwardingTargetForSelector:(SEL)aSelector

//NSInvocation進(jìn)行消息轉(zhuǎn)發(fā)
-(NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
-(void)forwardInvocation:(NSInvocation *)anInvocation

并且runtime會(huì)依次按以上順序調(diào)用,去看你是否有補(bǔ)救。


我們先人為造一個(gè)crash的場(chǎng)景:
建立一個(gè)Person類,Person.h

#import@interface Person : NSObject
@property(nonatomic,copy)NSString *name;
@property(nonatomic,strong)NSNumber *age;
@property(nonatomic,weak)id delegate;
@end

Person.m
#import "Person.h"
@implementation Person
@end

在控制器中 創(chuàng)建一個(gè)Person對(duì)象,

Person *pson = [[Person alloc] init];
pson.name = @"wc";
pson.age = @99;
 [pson performSelector:@selector(eat) withObject:nil];

顯然這里我們并沒(méi)有時(shí)間eat方法,程序未找到eat的具體實(shí)現(xiàn)而直接崩潰。

補(bǔ)救措施1:

在+(BOOL) resolveInstanceMethod:(SEL)selector中動(dòng)態(tài)添加eat方法。

+(BOOL) resolveInstanceMethod:(SEL)selector
{
       NSString *selectorStr = NSStringFromSelector(selector);
       if ([selectorStr isEqualToString:@"eat"])
       {
               class_addMethod(self, selector, (IMP)eat, "v@:");
               return YES;
        }
        return [self resolveClassMethod:selector];
}

具體實(shí)現(xiàn):

void eat()
{
   NSLog(@"person eat");
}

補(bǔ)救措施2:使用備用接收者,此時(shí),回調(diào)用這個(gè)方法-(id)forwardingTargetForSelector:(SEL)aSelector,這里為peson增加一個(gè)代理屬性,將person沒(méi)有實(shí)現(xiàn)的方法,交給代理去做

#import@interface Person : NSObject
@property(nonatomic,copy)NSString *name;
@property(nonatomic,strong)NSNumber *age;
//代理,將person沒(méi)有實(shí)現(xiàn)的方法,交給代理去做
@property(nonatomic,weak)id delegate;
@end

//為pson對(duì)象添加了代理
Person *pson = [[Person alloc] init];
pson.name = @"wc";
pson.age = @99;
pson.delegate = self;
[pson performSelector:@selector(eat) withObject:nil];

//代理實(shí)現(xiàn)eat方法

-(void)eat
{
   NSLog(@"viewcontroller eat");
}

//這里返回代理,讓代理去完成

-(id)forwardingTargetForSelector:(SEL)aSelector
{
   return  _delegate;
}

當(dāng)然這里不一定要用到代理,只要是你在這個(gè)上下文中可以訪問(wèn)到的合適的對(duì)象就行。

如果到這一步還沒(méi)有采取補(bǔ)救措施,只能啟動(dòng)完整地消息轉(zhuǎn)發(fā)機(jī)制了。
補(bǔ)救措施3:

-(NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
-(void)forwardInvocation:(NSInvocation *)anInvocation

默認(rèn)的-(void)forwardInvocation:(NSInvocation *)anInvocation的實(shí)現(xiàn)是調(diào)用-(void)doesNotRecognizeSelector:(SEL)aSelector方法,在該方法中將會(huì)拋出異常,導(dǎo)致程序崩潰。

-(NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector
{
        NSMethodSignature *methodSignature = nil;
        NSString *selectorStr = NSStringFromSelector(aSelector);
      if ([selectorStr isEqualToString:@"eat"])
      {
                methodSignature = [NSMethodSignature signatureWithObjCTypes:"v@:"];
              //方法簽名
               return methodSignature;
       }
      return [self methodSignatureForSelector:aSelector];
}

-(void)forwardInvocation:(NSInvocation *)anInvocation
{
     NSLog(@"forwordInvocation");
     SEL selector = anInvocation.selector;
    if ([_delegate respondsToSelector:selector])
    {
           [anInvocation invokeWithTarget:_delegate];
    }
   else
  {
           return[self forwardInvocation:anInvocation];
   }
}

這里消息轉(zhuǎn)發(fā),完全實(shí)現(xiàn)交給了我代理去做了。

參考鏈接:http://blog.csdn.net/liangliang103377/article/details/39007683

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容