Objective-C 是一門動態語言,它將不少靜態語言在編譯和連接時期作的事情,放到了運行時來處理。之因此能具有這種特性,離不開 Runtime 這個庫。Runtime 很好的解決了如何在運行時期找到調用方法這樣的問題。緩存
在 Objective-C 中,方法調用稱爲向對象發送消息:bash
// MyClass 類
@interface MyClass: NSObject
- (void)printLog;
@end
@implementation MyClass
- (void)printLog {
NSLog(@"print log !");
}
@end
MyClass *myClass = [[MyClass alloc] init];
[myClass printLog];
// 輸出: print log !
複製代碼
上面代碼中的 [myClass printLog]
也能夠這麼寫:函數
((void (*)(id, SEL))(void *) objc_msgSend)(myClass, @selector(printLog));
複製代碼
[myClass printLog]
通過編譯後就是調用 objc_msgSend
方法。性能
咱們看看這個方法的文檔定義:學習
id objc_msgSend(id self, SEL op, ...);
複製代碼
self:消息的接收者 op: 消息的方法名,C 字符串 ... :參數列表ui
講以前,咱們須要先明白一些基礎概念:Objective-C 是一門面向對象的語言,對象又分爲實例對象、類對象、元類對象以及根元類對象。它們是經過一個叫 isa
的指針來關聯起來,具體關係以下圖:spa
以咱們上文的代碼爲例:3d
MyClass *myClass = [[MyClass alloc] init];
複製代碼
整理下相互間的關係:指針
myClass
是實例對象MyClass
是類對象MyClass
的元類的元類就是 NSObject
的元類NSObject
就是 Root class (class)NSObject
的 superclass
爲 nil
NSObject
的元類就是它本身NSObject
的元類的 superclass
就是 NSObject
對應上圖中的位置關係以下:code
接着,咱們用代碼來驗證下上文的關係:
MyClass *myClass = [[MyClass alloc] init];
Class class = [myClass class];
Class metaClass = object_getClass(class);
Class metaOfMetaClass = object_getClass(metaClass);
Class rootMetaClass = object_getClass(metaOfMetaClass);
Class superclass = class_getSuperclass(class);
Class superOfSuperclass = class_getSuperclass(superclass);
Class superOfMetaOfSuperclass = class_getSuperclass(object_getClass(superclass));
NSLog(@"MyClass 實例對象是:%p",myClass);
NSLog(@"MyClass 類對象是:%p",class);
NSLog(@"MyClass 元類對象是:%p",metaClass);
NSLog(@"MyClass 元類對象的元類對象是:%p",metaOfMetaClass);
NSLog(@"MyClass 根元類對象是:%p",rootMetaClass);
NSLog(@"MyClass 父類是:%@",class_getSuperclass(class));
NSLog(@"MyClass 父類的父類是:%@",superOfSuperclass);
NSLog(@"MyClass 父類的元類的父類是:%@",superOfMetaOfSuperclass);
NSLog(@"NSObject 元類對象是:%p",object_getClass([NSObject class]));
NSLog(@"NSObject 父類是:%@",[[NSObject class] superclass]);
NSLog(@"NSObject 元類對象的父類是:%@",[object_getClass([NSObject class]) superclass]);
//輸出:
MyClass 實例對象是:0x60c00000b8d0
MyClass 類對象是:0x109ae3fd0
MyClass 元類對象是:****0x109ae3fa8
MyClass 元類對象的元類對象是:****0x10ab02e58**
MyClass 根元類對象是:0x10ab02e58
MyClass 父類是:NSObject
MyClass 父類的父類是:(null)
MyClass 父類的元類的父類是:NSObject
NSObject 元類對象是:0x10ab02e58
NSObject 父類是:(null)
NSObject 元類對象的父類是:NSObject
複製代碼
能夠發現,輸出結果是徹底符合咱們的結論的!
如今咱們能知道各類對象之間的關係:
實例對象經過
isa
指針,找到類對象Class
;類對象一樣經過isa
指針,找到元類對象;元類對象也是經過isa
指針,找到根元類對象;最後,根元類對象的isa
指針,指向本身。能夠發現NSObject
是整個消息機制的核心,絕大數對象都繼承自它。
上文提到了,一個 Objective-C 方法會被編譯成 objc_msgSend
,這個函數有兩個默認參數,id
類型的 self
, SEL
類型的 op
。咱們先看看 id
的定義:
typedef struct objc_object *id;
struct objc_object {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
};
複製代碼
咱們能夠看到,在 objc_object
結構體中,只有一個指向 Class
類型的 isa
指針。
咱們再看看 Class
的定義:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
複製代碼
裏面有不少參數,很顯眼的能看到這一行:
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
複製代碼
看名字也容易理解,這個 methodLists
就是用來存放方法列表的。咱們再看看 objc_method_list
這個結構體:
struct objc_method_list {
struct objc_method_list * _Nullable 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;
}
複製代碼
裏面的 objc_method
,也就是咱們熟悉的 Method
:
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
}
複製代碼
Method
裏面保存了三個參數:
IMP
指針指向通過層層挖掘,咱們能明白實例對象調用方法的大體邏輯:
MyClass *myClass = [[MyClass alloc] init];
[myClass printLog];
複製代碼
((void (*)(id, SEL))(void *) objc_msgSend)(myClass, @selector(printLog));
myClass
的 isa
指針,找到 myClass
的類對象(Class
),也就是 MyClass
MyClass
的方法列表 methodLists
中,找到對應的 Method
Method
中的 IMP
指針,執行具體實現由上文,咱們已經知道,實例對象是經過 isa
指針,找到其類對象(Class
)中保存的方法列表中的具體實現的。
好比:
MyClass *myClass = [[MyClass alloc] init];
[myClass printLog];
複製代碼
能夠理解爲:printLog
方法就是保存在 MyClass
中的。
那麼若是是個類方法,又是保存在什麼地方的呢?
咱們回顧下 Class
的定義:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
複製代碼
能夠發現到這一行:
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
複製代碼
這裏的 isa
一樣是指向一個 Class
的指針。上文中,咱們也知道了類對象的 isa
指針是指向元類對象的。那麼不可貴出:
類對象的類方法,是保存在元類對象中的!
類對象和元類對象都是 Class
類型,僅僅服務的對象不一樣罷了。找到了元類對象,天然就找到了元類對象中的 methodLists
,接下來就和實例對象的方法尋找調用同樣的流程了。
上文中,咱們大概知道,方法是經過 isa
指針,查找 Class
中的 methodLists
的。若是子類沒實現對應的方法實現,還會沿着父類去查找。整個工程,可能有成萬上億個方法,是如何解決性能問題的呢?
例如:
for (int i = 0; i < 100000; ++i) {
MyClass *myObject = myObjects[i];
[myObject methodA];
}
複製代碼
這種高頻次的調用 methodA
,若是每調用一次都須要遍歷,性能是很是差的。因此引入了 Class Cache 機制:
Class Cache 認爲,當一個方法被調用,那麼它以後被調用的可能性就越大。
查找方法時,會先從緩存中查找,找到直接返回 ;找不到,再去 Class
的方法列表中找。
在上文中 Class
的定義中,咱們能夠發現 cache
:
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
複製代碼
說明了緩存是存在類中的,每一個類都有一份方法緩存,而不是每一個類的 object
都保存了一份。
在 Objective-C 中,子類調用一個方法,若是沒有子類沒有實現,父類實現了,會去調用父類的實現。上文中,找到 methodLists
後,尋找 Method
的大體過程以下:
ps: 其實這裏的尋找過程遠沒有這麼簡單,可能會遍歷不少遍,由於咱們可能會在運行時動態的添加方法(好比
category
)。遍歷的過程當中一樣不時的去查詢緩存表。
若是方法列表(methodLists
)沒找到對應的 selector
呢?
// ViewController.m 中 (未實現 myTestPrint 方法)
[self performSelector:@selector(myTestPrint:) withObject:@",你好 !"];
複製代碼
系統會提供三次補救的機會。
+ (BOOL)resolveInstanceMethod:(SEL)sel {} (實例方法)
+ (BOOL)resolveClassMethod:(SEL)sel {} (類方法)
複製代碼
這兩個方法,一個針對實例方法;一個針對類方法。返回值都是 Bool
。
使用示例:
// ViewController.m 中
void myMethod(id self, SEL _cmd,NSString *nub) {
NSLog(@"ifelseboyxx%@",nub);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
if (sel == @selector(myTestPrint:)) {
#pragma clang diagnostic pop
class_addMethod([self class],sel,(IMP)myMethod,"v@:@");
return YES;
}else {
return [super resolveInstanceMethod:sel];
}
}
複製代碼
咱們只須要在 resolveInstanceMethod:
方法中,利用 class_addMethod
方法,將未實現的 myTestPrint:
綁定到 myMethod
上就能完成轉發,最後返回 YES
。
- (id)forwardingTargetForSelector:(SEL)aSelector {}
複製代碼
這個方法要求返回一個 id
。使用場景通常是將 A 類的某個方法,轉發到 B 類的實現中去。
使用示例:
想轉發到 Person
類中的 -myTestPrint:
方法中:
@interface Person : NSObject
@end
@implementation Person
- (void)myTestPrint:(NSString *)str {
NSLog(@"ifelseboyxx%@",str);
}
@end
複製代碼
// ViewController.m 中
- (id)forwardingTargetForSelector:(SEL)aSelector {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
if (aSelector == @selector(myTestPrint:)) {
#pragma clang diagnostic pop
return [Person new];
}else{
return [super forwardingTargetForSelector:aSelector];
}
}
複製代碼
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {}
- (void)forwardInvocation:(NSInvocation *)anInvocation {}
複製代碼
第一個要求返回一個方法簽名,第二個方法轉發具體的實現。兩者相互依賴,只有返回了正確的方法簽名,纔會執行第二個方法。
此次的轉發做用和第二次的比較相似,都是將 A 類的某個方法,轉發到 B 類的實現中去。不一樣的是,第三次的轉發相對於第二次更加靈活,forwardingTargetForSelector:
只能固定的轉發到一個對象;forwardInvocation:
可讓咱們轉發到多個對象中去。
使用實例:
想轉發到 Person
類以及 Animal
類中的 -myTestPrint:
方法中:
@interface Person : NSObject
@end
@implementation Person
- (void)myTestPrint:(NSString *)str {
NSLog(@"ifelseboyxx%@",str);
}
@end
複製代碼
@interface Animal : NSObject
@end
@implementation Animal
- (void)myTestPrint:(NSString *)str {
NSLog(@"tiger%@",str);
}
@end
複製代碼
// ViewController.m 中
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
if (aSelector == @selector(myTestPrint:)) {
#pragma clang diagnostic pop
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
Person *person = [Person new];
Animal *animal = [Animal new];
if ([person respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:person];
}
if ([animal respondsToSelector:anInvocation.selector]) {
[anInvocation invokeWithTarget:animal];
}
}
複製代碼
⚠️ 若是到了第三次機會,還沒找到對應的實現,就會 crash:
unrecognized selector sent to instance 0x7f9f817072b0
複製代碼
到這裏,咱們大概能瞭解消息發送與轉發的過程了。整理了下大體的流程,有問題歡迎你們積極提出來:
感謝 Sky愛學習 的指正,修改了下