iOS消息轉發機制

iOS方法調用實際上就是消息轉發過程

最簡單的方法調用:bash

[[MessageSend new] sendMessage:@"Hello"];
    //等同於
    //objc_msgSend([MessageSend new], @selector(sendMessage:), @"Hello");
複製代碼

開發過程當中常常會遇到這個錯誤unrecognized selector sent to instance; 沒有實現方法,或是方法沒有找到,就直接崩潰了。 markdown

崩潰信息.png

實例:

調用一個方法:模塊化

[[MessageSend new] sendMessage:@"Hello"];
複製代碼

來看下具體怎麼實現。函數

1.首先經過[Message new]對象的ISA指針找打它對應的class。 2.在class的method list 查找是否有sendMessage方法。 3.若是沒有就去它的superclass裏繼續查找。 4.一旦查找到對應的函數方法,就去執行它的實現IMP。 5.若是一直沒有找到,就執行消息轉發。spa

首先我不實現這個方法,來看下消息轉發機制。debug

//實際上消息轉發分爲三個部分 //1.動態方法解析 //2.快速轉發 //3.慢速轉發 //4.消息未處理。 //越往下花費的代價越大。。 指針

消息轉發.png

1.動態方法解析code

#import <objc/message.h>

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    NSString *method = NSStringFromSelector(sel);
    if ([method isEqualToString:@"sendMessage:"]) {
        return class_addMethod(self, sel, (IMP)sendIMP, "v@:@");
    }
    return NO;
}

void sendIMP(id self, SEL _cmd, NSString *msg) {
    NSLog(@"msg = %@", msg);
}

複製代碼

以上就是動態方法解析。orm

2.快速轉發對象

- (id)forwardingTargetForSelector:(SEL)aSelector {
    NSString *method = NSStringFromSelector(aSelector);
    if ([method isEqualToString:@"sendMessage:"]) {
        //去找備胎類,看有沒有實現這個方法
        return [SpareWheel new];
    }
    return [super forwardingTargetForSelector:aSelector];
}
複製代碼

這時候涉及到了另一個類,看它有沒有實現對應的方法。 若是有實現,消息轉發結束。

3.慢速轉發

//封裝方法
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSString *method = NSStringFromSelector(aSelector);
    if ([method isEqualToString:@"sendMessage:"]) {
        //把這個方法存起來
        return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
    }
    return [super methodSignatureForSelector:aSelector];

}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    //得到方法編號
    SEL sel = [anInvocation selector];
    //還來找備胎
    SpareWheel *sp = [SpareWheel new];
    //判斷可否響應方法
    if ([sp respondsToSelector:sel]) {
        anInvocation.target = sp;
    }else {
        [super forwardInvocation:anInvocation];
    }
}
複製代碼

若是備胎類或是哪都找不到對應的實現方法,就會到這個方法裏

-(void)doesNotRecognizeSelector:(SEL)aSelector {
    
}
複製代碼

做爲找不到函數實現的最後一步,NSObject實現這個函數只有一個功能,就是拋出異常。 雖然理論上能夠重載這個函數實現保證不拋出異常(不調用super實現),可是蘋果文檔着重提出「必定不能讓這個函數就這麼結束掉,必須拋出異常」。 在release 模式下咱們能夠嘗試不讓它拋出異常,這樣就能夠避免找不到方法崩潰了。 可是debug、還沒上線時千萬別這麼作 假如你必定要這麼作,能夠這麼寫個分類:

@implementation NSObject (Message)

- (void)doesNotRecognizeSelector:(SEL)aSelector {
    if (DEBUG) {
        NSLog(@"這裏有方法沒實現,可是我就不讓他崩潰");
    }
}
複製代碼

消息轉發能夠來兼容API。

NSInvocation 模塊化、路由模式 解耦。

//消息轉發三種方法:
    //1. 直接調用
    MessageSend *send = [MessageSend new];
    [send sendMessage:@"Eddiegooo"];

    //2。perform 方法
    [send performSelector:@selector(sendMessage:) withObject:@"Eddiegooo"];
    //可是多個參數怎麼辦呢? 用三方法
    
    //3. NSInvocation
    NSMethodSignature *methodSign = [send methodSignatureForSelector:@selector(sendMessage:)];
    
    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSign];
    [invocation setTarget:send];
    [invocation setSelector:@selector(sendMessage:)];
    NSString *string = @"Eddiegooo";
    [invocation setArgument:&string atIndex:2]; //這裏爲啥v從2開始?  IMP(self, _cmd, *) 傳參都是在第三個纔是。
    [invocation invoke];
複製代碼

NSInvocation 調用Block有點複雜,須要知道Block的底層源碼。Aspects庫參考。

相關文章
相關標籤/搜索