iOS 訊息轉發機制Demo解析

吃粑粑的畢教授發表於2019-02-25

訊息轉發機制

原文連結 : 原文 假設說我們宣告一個類, 初始化物件, 並且在此類宣告一個方法, 呼叫方法的時候底層是怎麼處理的呢? 今天我們來簡單模擬測試, 來看道理髮生了什麼 以下是呼叫方法處理的方案圖, 按照方案順序去處理

iOS 訊息轉發機制Demo解析
以下是測試方法

//訊息轉發
//- (id)forwardingTargetForSelector:(SEL)aSelector OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);

//標準的訊息轉發
//- (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE("");
//- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("");
//

//動態方法解析
//+ (BOOL)resolveClassMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
//+ (BOOL)resolveInstanceMethod:(SEL)sel OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0, 2.0);
複製程式碼

Demo解析

基礎步驟

建立類, Person類, 宣告方法, 並且在ViewController進行初始化呼叫

VCPerson *person = [Person new];
[person run];
Person類中
- (void)run;//沒有實現
複製程式碼

此時執行是不是會報錯呢? 就是這個常見的錯誤 " - [Person run]: unrecognized selector sent to instance 0x600000008310' " 那麼這樣做到底發生了什麼? 做了哪些事情? 我們一步步來剖析

動態測試

在Presenter類中, 寫動態方法

+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"sel = %@",NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
複製程式碼

再次執行Demo就會走到這個方法中, 也就是我們所指的方案1, 此時列印出來的scl為" 訊息轉發機制Demo[41829:4186268] sel = run "

解析模擬

+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"sel = %@",NSStringFromSelector(sel));
//1.判斷沒有實現方法, 那麼我們就是動態新增一個方法
if (sel == @selector(run:)) {
class_addMethod(self, sel, (IMP)newRun, "v@:@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
複製程式碼

宣告函式

void newRun(id self,SEL sel,NSString *str) {
NSLog(@"---runok---%@",str);
}
複製程式碼

溫馨小提示, 動態新增方法引數意譯 : //將要新增方法的類/sel名/IMP函式指標<新增函式>, 官方文件其實是有解釋的

此時我們再次執行, 那麼列印結果就來了" 訊息轉發機制Demo[43269:4212899] ---runok---ok跑 ", 這樣的話我們就解決掉了報錯這個問題

訊息轉發重定向測試

此時我們新建立一個類Mbxb, 此時我們還是重新寫一個同名字的方法run方法, 並且進行實現

- (void)run{
NSLog(@"---Mbxbrunok---");
}
複製程式碼

解析

此時有兩個同樣的方法, 我們重新在Person類中 來實現方法

- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"aSelector = %@",NSStringFromSelector(aSelector));
return [super forwardingTargetForSelector:aSelector];
}
複製程式碼

此時執行測試, 動態測試輸出" 訊息轉發機制Demo[45875:4255869] sel = run ", 訊息轉發重定向輸出" 訊息轉發機制Demo[45875:4255869] ---Mbxbrunok--- ", 同樣也可以找見方法run 當我們進行處理

- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"aSelector = %@",NSStringFromSelector(aSelector));
return [[Mbxb alloc]init];
}
複製程式碼

那麼此時執行成功輸出, " ---Mbxbrunok--- "

生成方法簽名轉發訊息

此時我們在Person類中, 生成方法簽名

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE(""){
//轉化字元
NSString *sel = NSStringFromSelector(aSelector);
//判斷, 手動生成簽名
if([sel isEqualToString:@"run"]){
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}else{
return [super methodSignatureForSelector:aSelector];
}
複製程式碼

拿到簽名

- (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE(""){
NSLog(@"---%@---",anInvocation);
return [super forwardInvocation:anInvocation];
}
複製程式碼

此時我們的po的簽名輸出為" <NSInvocation: 0x60400027e700> return value: {v} void target: {@} 0x600000016ba0 selector: {:} run "

拿到訊息轉發簽名

- (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE(""){
NSLog(@"---%@---",anInvocation);
//取到訊息
SEL seletor = [anInvocation selector];
//轉發
Mbxb *bxb = [[Mbxb alloc]init];
if([bxb respondsToSelector:seletor]){
//呼叫物件,進行轉發
[anInvocation invokeWithTarget:bxb];
}else{
return [super forwardInvocation:anInvocation];
}
}
複製程式碼

小細節: 丟擲異常

假如說我們沒有這個方法, 同樣是遇到會崩潰的問題 我們這裡來進行一個異常處理

- (void)doesNotRecognizeSelector:(SEL)aSelector{
NSString *selStr = NSStringFromSelector(aSelector);
NSLog(@"%@不存在",selStr);
}
複製程式碼

我們可以在這個異常處理中一些處理, 比如說彈框

總結

對於訊息轉發機制, 我們重新來梳理一下Demo解析思路 還是三個方案, 按順序來走

  1. 動態方法解析
  2. 訊息轉發重定向
  3. 生成方法簽名
  4. 拿到簽名轉發訊息
  5. 細節處理, 丟擲異常

最後獻上一張邏輯圖

iOS 訊息轉發機制Demo解析

好了, 給大家這個簡單demo, 當然在程式碼中也寫了註釋, 可以去我的git下載, 歡迎star 下載連結 : demo地址

技術交流q群150731459

相關文章