參考:
https://www.jianshu.com/p/9e975a1cab93
https://www.jianshu.com/p/6ebda3cd8052
https://blog.ibireme.com/2013/11/26/objective-c-messaging/
目錄
本文不是講解,只做面試梳理,但本文可能比一些講解更易懂。
1、isa指針
//對象
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
上面這個結構體就是我們常說的OC中的對象,OC中的所有對象(包括id)裏面有個isa,這個isa指向類對象,就是下面這個。
以NSString *str = @"123";爲例,str是一個對象,它的isa指向NSString這個類。
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父類
const char *name OBJC2_UNAVAILABLE; // 類名
long version OBJC2_UNAVAILABLE; // 類的版本信息,默認爲0
long info OBJC2_UNAVAILABLE; // 類信息
long instance_size OBJC2_UNAVAILABLE; // 該類的實例變量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 該類的成員變量鏈表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定義的鏈表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法緩存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 協議鏈表
#endif
} OBJC2_UNAVAILABLE;
以上結構體就是類,它有isa,它是對象,稱爲類對象。類對象裏面的這些元素稱爲元數據。
類的isa指向meta-class(元類),super_class指向父類。
父類的isa指向它的元類,父類的super_class指向它的父類。
一直到NSObject,NSObject的isa指向NSObject的元類,NSObject的super_class指向nil。
NSObject的元類的isa指向自己,NSObject的元類的super_class指向NSObject。
2、消息發送
id objc_msgSend(id self, SEL _cmd, ...)
self是消息接收者,_cmd是selector(SEL)。
當向一般對象發送消息時,調用objc_msgSend;
當向super發送消息時,調用的是objc_msgSendSuper;
如果返回值是一個結構體,則會調用objc_msgSend_stret或objc_msgSendSuper_stret。
所謂消息發送,就是如何找到消息接收者的selector。
//方法列表
struct objc_method_list {
struct objc_method_list *obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
//方法
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
以普通對象爲例,objc_msgSend(id self, SEL _cmd)(看流程之前始終想着id self和 SEL _cmd這兩個參數)。
消息發送流程:
1、對象(id)通過它的isa找到class類對象(看第一小節中的objc_class結構體)
2、在class的cache緩存中查找(沒有緩存則初始化緩存),如果有,則根據_cmd這個方法名找到它的IMP實現,將Method加入緩存,並執行IMP。
3、如果沒有,從methodLists中查找,如果有,加入緩存,並執行IMP。
4、如果沒有,根據super_class指向,從父類中查找。
5、在父類中也是先從cache裏找,再從methodLists裏找,直到找到根類,如果一直都沒有則嘗試消息轉發(看第三節)。
3、消息轉發
消息轉發由_objc_msgForward完成。
消息轉發:(摘抄過來的)
1.調用resolveInstanceMethod:方法,允許用戶在此時爲該Class動態添加實現(看第四節)。如果有實現了,則調用並返回。如果仍沒實現,繼續下面的動作。
2.調用forwardingTargetForSelector:方法,嘗試找到一個能響應該消息的對象。如果獲取到,則直接轉發給它。如果返回了nil,繼續下面的動作。
3.調用methodSignatureForSelector:方法,嘗試獲得一個方法簽名。如果獲取不到,則直接調用doesNotRecognizeSelector拋出異常。如果獲取到了,調用forwardInvocation:方法,將獲取到的方法簽名包裝成Invocation傳入,如何處理就在這裏面了。
上面這3個方法均是模板方法,開發者可以override,由runtime來調用。最常見的實現消息轉發,就是重寫方法3和4,吞掉一個消息或者代理給其他對象都是沒問題的。
我總結一下:
簡單來說,消息轉發有三個階段,對應三個方法:
resolveInstanceMethod:在這個方法裏可以動態添加一個方法實現。(看第四節)
forwardingTargetForSelector: 在這個方法裏可以返回一個對象,然後讓它去這個對象的類裏查找方法。(不明白的去看這個博客裏的"備用接收者"https://www.jianshu.com/p/6ebda3cd8052)
methodSignatureForSelector: 在這個方法裏返回一個簽名,進入forwardInvocation:方法,然後再這個方法裏具體實現。
4、動態添加方法
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//執行foo函數
[self performSelector:@selector(foo:)];
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(foo:)) {//如果是執行foo函數,就動態解析,指定新的IMP
class_addMethod([self class], sel, (IMP)fooMethod, "v@:");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void fooMethod(id obj, SEL _cmd) {
NSLog(@"Doing foo");//新的foo函數
}
5、給分類添加屬性
objc_setAssociatedObject(self, &key, value, OBJC_ASSOCIATION_COPY_NONATOMIC);
objc_getAssociatedObject(self,&key);
6、方法交換
Method test = class_getInstanceMethod(self, @selector(test));
Method otherTest = class_getInstanceMethod(self, @selector(otherTest));
method_exchangeImplementations(test, otherTest);