方法異常捕獲轉發的流程

引入例子

@interface Person : NSObject

- (void)logString:(NSString *)str;

@end

@implementation Person
@end

Person聲明瞭一個方法但是沒有實現。這時如果調用會崩潰

Person * p = [Person new];    
[p logString:@"哈哈哈哈😀"];

消息轉發的過程:

Isa去類對象中找到方法,然後去發送消息。找不到的話去父類一層一層往上。如果還沒找到就會進到動態解析。

(注意:isa指向類;類對象的isa指向元類;如果當前類繼承自NSObject,那麼元類再指向根源類;如果當前類繼承自NSObject,那麼元類再指向自己)

動態解析流程:

// 攔截類方法
+ (BOOL)resolveClassMethod:(SEL)sel {
    return NO;
}
// 攔截實例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    return NO;
}

以此爲例,我們要在Person中攔截

@implementation Person

// 注意 `id self,SEL log`前兩個參數必須要加上接收,不能只寫str這一個參數
void dynamicLogString(id self,SEL log,NSString *str) { // 1.1
    NSLog(@"%@", str);
}

// 攔截實例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    
    // 動態創建方法接收消息
    if (sel == @selector(logString:)) {
        class_addMethod(self, sel, (IMP)dynamicLogString, @"v@:@"); // 1.2
    }
    return NO;
}

@end
  • 1.1
    我們要引用的動態方法(dynamicLogString爲例),必須接收兩個參數,如果需要接受額外參數,那麼必須在後面追加。
  • 1.2
    參數v@:@是根據原-logString:方法的配置來定的 v對應void;@對應方法(即id類型);:代表有參數;@代表參數類型;
    參數配置連接
    以上就是第一步攔截,但是如果沒有設置動態方法或者不想通過動態添加方法的方式處理的話,系統就進行動態轉發

尋找備用接收者

- (id)forwardingTargetForSelector:(SEL)aSelector
設置一個合適的對象來調用這個方法(該對象聲明+實現了該方法)

// 攔截實例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    // 動態創建方法接收消息
//    if (sel == @selector(logString:)) {
//        class_addMethod(self, sel, (IMP)dynamicLogString, @"v@:@");
//    }
    return NO;
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if ([TempObjct instancesRespondToSelector:aSelector]) {
        return [TempObjct new];
    }
    return [super forwardingTargetForSelector:aSelector];
}

如果還是沒找到合適的對象來接收這個方法的話會進入方法轉發

方法轉發

分兩步:
1.方法簽名
2.方法轉發

- (id)forwardingTargetForSelector:(SEL)aSelector {
//    if ([TempObjct instancesRespondToSelector:aSelector]) {
//        return [TempObjct new];
//    }
    return [super forwardingTargetForSelector:aSelector];
}

// 方法簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(logString:)) {
        return [NSMethodSignature signatureWithObjCTypes:"@@:*"];  // 注意這裏接受的是c字符串不是@"",不然會crash
    }
    return [super methodSignatureForSelector:aSelector];
}

// 方法轉發
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    if ([TempObjct instancesRespondToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:[TempObjct new]];
        return;
    }
    // super
    [super forwardInvocation:anInvocation];
}

終極攔截

如果以上方法都沒有攔截到,或者想要保證程序不會崩潰,可以添加下面方法

- (void)doesNotRecognizeSelector:(SEL)aSelector {
    NSLog(@"未知方法");
}

注意這個方法會捕獲到最終所有沒法直行的方法;

以上就是完整流程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章