簡介:
[object doSomething];
編譯器會轉換為下面形式:
objc_msgSend(receiver, selector);
如果消息含有參數,則為:
objc_msgSend(receiver, selector, arg1, arg2, ...)
1、消息發送機制
- 檢查selector 是否需要忽略(比如 Mac OS X 開發,有了垃圾回收就不理會 retain,release 這些函數。)
- 檢查target是否為nil,如果為nil,直接cleanup,然后return(這就是為什么可以向nil發送消息的原因)
- 然后在target的Class中根據Selector去找IMP。
尋找IMP的過程:
- 先從當前class的cache方法列表(cache methodLists)里去找。
- 如果找到了,跳到對應函數實現。
- 如果沒找到,就從class的方法列表(methodLists)里找。
- 如果還找不到,就到super class的方法列表里找,直到找到基類(NSObject)為止。
- 最后再找不到,就會進入動態方法解析和消息轉發的機制。
2、消息轉發機制
- 消息發送是Runtime通過selector快速查找IMP的過程,有了函數指針就可以執行對應的方法實現;
- 消息轉發是在查找IMP失敗后執行一系列轉發流程的慢速通道,如果不作轉發處理,則會打日志和拋出異常。
①動態方法解析:
對象在收到無法解讀的消息之后,首先將調用所屬類的下列類方法:
+(BOOL)resolveInstanceMethod:(SEL)name
demo解析:
#import <Foundation/Foundation.h>@interface Person : NSObject
- (void)eat; //沒有實現
@end
運行結果image-20180418170832412.png
在person.m中寫入以下代碼,對象在接受的無法解讀的消息后,首先會調用+(BOOL)resolveInstanceMethod:(SEL)sel或者+ (BOOL)resolveClassMethod:(SEL)sel, 詢問是否有動態添加方法來進行處理,處理實例如下
@implementation Person
+(BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"sel = %@",NSStringFromSelector(sel));
return [super resolveInstanceMethod:sel];
}
結果如下:image-20180418172155127.png
具體添加方法如下:
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(eat))
{
class_addMethod([self class], sel, (IMP)addeat, "V@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void addeat(id self,SEL sel,NSString *str)
{
NSLog(@"添加成功");
}
class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types)
參數一:被添加方法的累
參數二:要添加的方法
參數三:實現這個方法的函數
參數四:定義返回值和參數類型的字符串(i:代表int v代表void @:代表參數)
如果動態添加失敗,就會進入下面的操作
②消息轉發重定向
- (id)forwardingTargetForSelector:(SEL)aSelector
在消息轉發機制執行前,Runtime系統會再給我們一次偷梁換柱的機會,即通過重載- (id)forwardingTargetForSelector:(SEL)aSelector方法替換消息的接受者為其他對象:
新建一個tempObject,定義eat方法,在.m文件中實現
#import "tempObject.h"
@implementation tempObject
- (void)eat
{
NSLog(@"i am tempObject");
}
@end
在person類中將接受消息對象換為tempObject
-(id)forwardingTargetForSelector:(SEL)aSelector
{
return [[tempObject alloc]init];
}
結果如下:
image-20180419150039116.png
③ 消息轉發
首先獲取方法的簽名,拿著簽名再去配發消息,如果不能接受消息就會拋出異常
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
獲取方法簽名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector OBJC_SWIFT_UNAVAILABLE("")
{
//轉化字符
NSString *sel = NSStringFromSelector(aSelector);
//判斷, 手動生成簽名
if([sel isEqualToString:@"eat"])
{
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
else
{
return [super methodSignatureForSelector:aSelector];
}
}
拿到方法簽名配發消息
- (void)forwardInvocation:(NSInvocation *)anInvocation OBJC_SWIFT_UNAVAILABLE(""){
NSLog(@"forwardInvocation: %@", NSStringFromSelector([anInvocation selector]));
//取到消息
SEL seletor = [anInvocation selector];
//轉發
tempObject *temp = [[tempObject alloc]init];
if([temp respondsToSelector:seletor])
{
//調用對象,進行轉發
[anInvocation invokeWithTarget:temp];
}
else
{
return [super forwardInvocation:anInvocation];
}
}
如果不能接受消息,拋出異常
- (void)doesNotRecognizeSelector:(SEL)aSelector
{
NSString *selStr = NSStringFromSelector(aSelector);
NSLog(@"%@不存在",selStr);
}
參考文章:
runtime 消息轉發:http://www.lxweimin.com/p/8cd06cd496d5
? runtime消息轉發demo:http://www.2bjs.com/%E6%9E%B6%E6%9E%84/iOS%20%E6%B6%88%E6%81%AF%E8%BD%AC%E5%8F%91%E6%9C%BA%E5%88%B6Demo%E8%A7%A3%E6%9E%90/