最簡單的方法調用:bash
[[MessageSend new] sendMessage:@"Hello"]; //等同於 //objc_msgSend([MessageSend new], @selector(sendMessage:), @"Hello"); 複製代碼
開發過程當中常常會遇到這個錯誤unrecognized selector sent to instance
; 沒有實現方法,或是方法沒有找到,就直接崩潰了。 markdown
調用一個方法:模塊化
[[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.消息未處理。 //越往下花費的代價越大。。 指針
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。
//消息轉發三種方法: //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庫參考。