我的對iOS OC 消息轉發機制的基本原理理解

關於OC的消息轉發機制,是大部分面試官在面試過程當中常常問到的問題。在此我整理了一下我對OC消息轉發機制的理解。git

衆所周知OC的一個對象在發送消息的時候首先在該類的struct objc_method_list列表中去搜索,若是找到則直接調用相關方法的實現,若是沒有找到就會經過super_class指針沿着繼承樹向上去搜索,若是找到就跳轉,若是到了繼承樹的根部(一般爲NSObject)尚未找到。那就會調用NSObjec的一個方法doesNotRecognizeSelector:,這樣就會報unrecognized selector 錯誤。其實在調用doesNotRecognizeSelector:方法以前還會進行消息轉發---還有三次機會來補救。也就是常說的OC消息轉發的三次補救措施。github

總的來講一個OC消息的發送會經歷四個階段(該四個階段都是搜索到NSObject再進入下階段)面試

1)先在本類中搜索改方法的實現,若是有則直接調用若果沒有則去父類中搜索直到NSObject,若是NSObject沒有則進入消息轉發(類的動態方法解析、備用接受者對象、完整的消息轉發)。bash

2)類的動態方法解析:app

首先建立SonPerson類,在ViewController 裏面寫函數

id person = [[SonPerson alloc]init];
[person appendString:@""];
複製代碼

注意這裏要用id 否則編譯報錯。ui

在該類和父類中沒有找到該方法的實現則會執行 +(BOOL)resolveClassMethod:(SEL)sel 或+(BOOL)resolveInstanceMethod:(SEL)sel 方法。在+(BOOL)resolveClassMethod:(SEL)sel 或+(BOOL)resolveInstanceMethod:(SEL)sel 方法 中利用黑魔法runtime 動態添加方法實現。spa

void dynamicAdditionMethodIMP(id self,SEL _cmd){
    NSLog(@"dynamicAdditionMethodIMP");
}

+(BOOL)resolveClassMethod:(SEL)sel{

    NSLog(@"resolveInstanceMethod: %@", NSStringFromSelector(sel));

    if(sel ==@selector(appendString:)) {

        class_addMethod([selfclass], sel, (IMP)dynamicAdditionMethodIMP,"v@:");

        returnYES;

    }

    return[superresolveClassMethod:sel];

}

+(BOOL)resolveInstanceMethod:(SEL)sel{

    NSLog(@"resolveInstanceMethod: %@", NSStringFromSelector(sel));

    if(sel ==@selector(appendString:)) {

        class_addMethod([selfclass], sel, (IMP)dynamicAdditionMethodIMP,"v@:");

        returnYES;

    }

    return[super resolveInstanceMethod:sel];

}

BOOL class_addMethod(Class cls, SEL name, IMP imp,constchar*types);
複製代碼

第一個參數是須要添加方法的類,第二個參數是一個selector,也就是實例方法的名字,第三個參數是一個IMP類型的變量也就是函數實現,須要傳入一個C函數,這個函數至少有兩個參數,一個是id self一個是SEL _cmd,第四個參數是函數類型。具體設置方法能夠看註釋。指針

控制檯輸出:code

resolveInstanceMethod: appendString:

dynamicAdditionMethodIMP

3)備用接受者: 在+(BOOL)resolveClassMethod:(SEL)sel 或+(BOOL)resolveInstanceMethod:(SEL)sel 方法返回NO的時候進入備用接受者階段 。

建立一個備用接受者類ForwardPerson 實現appendString:方法

-(void)appendString:(NSString*)str{

  NSLog(@"%@===%@",NSStringFromClass([self class]),NSStringFromSelector(_cmd));

}
複製代碼

在SonPerson類中實現- (id)forwardingTargetForSelector:(SEL)aSelector 方法 並返回一個備用接受者對象

- (id)forwardingTargetForSelector:(SEL)aSelector{

   NSLog(@"forwardingTargetForSelector");

   return [ForwardPerson new];

}
複製代碼

控制檯輸出:

forwardingTargetForSelector

ForwardPerson===appendString:

4)完整的消息轉發:當-(void)forwardInvocation:(NSInvocation*)anInvocation 方法方法nil的時候則進入消息轉發的最後階段,完整的消息轉發。也須要建立一個轉發對象ForwardInvocation

#import "ForwardInvocation.h"

@implementationForwardInvocation

-(void)appendString:(NSString*)str{

    NSLog(@"%@===%@",NSStringFromClass([self class]),NSStringFromSelector(_cmd));

}

@end
複製代碼

在SonPerson中實現-(void)forwardInvocation:(NSInvocation*)anInvocation和- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector方法

-(void)forwardInvocation:(NSInvocation*)anInvocation{

    NSLog(@"forwardInvocation");

    if ([ForwardInvocation instancesRespondToSelector:anInvocation.selector]) {

        [anInvocation invokeWithTarget:self.invocation];

    }

}

/*必須從新這個方法,消息轉發機制使用從這個方法中獲取的信息來建立NSInvocation對象 返回nil上面方法不執行*/

- (NSMethodSignature*)methodSignatureForSelector:(SEL)aSelector{

    NSMethodSignature*signature = [super methodSignatureForSelector:aSelector];

    if(!signature){

        if ([ForwardInvocation instancesRespondToSelector:aSelector]){

            signature = [ForwardInvocation instanceMethodSignatureForSelector:aSelector];

        }

    }

    returnsignature;

}
複製代碼

控制檯輸出:

forwardInvocation

ForwardInvocation===appendString:

最後附Demo:github.com/SionChen/OB…
並附消息轉發一張圖:

相關文章
相關標籤/搜索