最簡單的方法調用:bash
[[MessageSend new] sendMessage:@"Hello"];
//等同於
//objc_msgSend([MessageSend new], @selector(sendMessage:), @"Hello");
複製代碼
開發過程當中常常會遇到這個錯誤unrecognized selector sent to instance
; 沒有實現方法,或是方法沒有找到,就直接崩潰了。 模塊化
調用一個方法:函數
[[MessageSend new] sendMessage:@"Hello"];
複製代碼
來看下具體怎麼實現。ui
1.首先經過[Message new]對象的ISA指針找打它對應的class。 2.在class的method list 查找是否有sendMessage方法。 3.若是沒有就去它的superclass裏繼續查找。 4.一旦查找到對應的函數方法,就去執行它的實現IMP。 5.若是一直沒有找到,就執行消息轉發。spa
首先我不實現這個方法,來看下消息轉發機制。debug
//實際上消息轉發分爲三個部分 //1.動態方法解析 //2.快速轉發 //3.慢速轉發 //4.消息未處理。 //越往下花費的代價越大。。 指針
![]()
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.快速轉發cdn
- (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。
//消息轉發三種方法:
//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庫參考。