?文章有點長,請做好心理準備...Let`s go!
基於Runtime機制,OC的對象發送消息就會通過一系列操作,根據對象從相應的類中查找方法對應的列表(類即類對象,方法存儲在元類的方法列表中),方法列表實質是一個哈希表,通過SEL查找到IMP(即函數指針),返回相應的實現。
案例:有一個類Person,Person類有一個類方法walk,調用如下:
[Person walk];
Person對象在調用run的時候會依次進行方法的查找,此時的查找有分爲快速查找(彙編cache_t查找)和慢速查找(C/C++查找),若找到則返回IMP,找不到則進行消息轉發流程,如果消息轉發沒有處理,則直接Crash掉。
一.方法查找
在調用方法的地方打一個breakpoint,使用Debug下的Always Show Disassembly查看一下彙編源碼如下:
objc_msgSend 利用runtime機制給對象發送消息的函數,_objc_msgSend之所以用匯編寫是因爲,寫一個函數,保留未知的參數,跳轉到任意的指針,參數是在運行時傳遞的,C是無法實現的並且也沒有滿足做這件事的必要性,還需要向底層編譯,彙編有寄存器,可以直接進行保存、直接進行編譯,比較快,彙編直接進行了一系列位移操作,直接操作內存,就非常快。全局搜索_objc_msgSend方法查看彙編源碼,這裏原則arm64進行分析。
如上圖,可以看到進入_objc_msgSend之後,發現執行了LNilOrTagged,很明顯是一個判斷,判斷是否爲空,或者是否是Tagged Point類型,這個LNilOrTagged執行了什麼呢?
LReturnZero 判斷是否爲nil,如果爲空,直接就返回了,不會在進行方法的查找。LGetIsaDone 開始處理isa,外物皆對象,萬物皆有isa。
LGetIsaDone:
CacheLookup NORMAL // calls imp or objc_msgSend_uncached
LGetIsaDone 加載完畢後調用了CacheLookup NORMAL,CacheLookup 主要是開始從緩存列表中查找。CacheLookup是一個宏,有三種情況,NORMAL|GETIMP|LOOKUP,而我們剛剛傳入的是NORMAL。
CacheHit是一個宏,表示命中了,找到了imp,就會調用TailCallCachedImp返回imp。
如果沒有找到,就會調用CheckMiss,CheckMiss也是一個宏,由於前面傳遞的是NORMAL,所以會調用__objc_msgSend_uncached。
開始調用__objc_msgSend_uncached,這個裏面有一個非常重要的調用MethodTableLookup,在TailCallFunctionPointer之前調用,說明開始進行方法列表的的查找。
MethodTableLookup也是一個宏,methodList內部比較復發,裏面存儲的是methode_t,SEL 和 IMP以哈希表的方式存儲。
此時搜索__class_lookupMethodAndLoadCache3已經查找不到了,這個時候我們就要考慮C/C++了,換一個思維,這裏有一個”__“的區別。搜索“_class_lookupMethodAndLoadCache”就會發現找到了。
_class_lookupMethodAndLoadCache3這個方法返回的類型就是IMP。注意:上面3個參數:
cls:因爲我們已經通過彙編已經編譯到了我們所有的結構,如果沒有編譯到,上面就不會有isa了,也就是YES。
cache:如果有cache,上面就返回了,所以這個肯定是沒有的,也就是NO。
resolver:動態解析,如果沒有即將進入下一個階段,動態方法解析和消息轉發機制了。
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// Optimistic cache lookup
if (cache) { //進入到這裏,傳遞的參數cache本身就是NO
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
runtimeLock.lock();
checkIsKnownClass(cls);
if (!cls->isRealized()) { //如果這個類沒有實現,就去實現這個類,裏面有class_rw_t
realizeClass(cls);
}
if (initialize && !cls->isInitialized()) {
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
}
//重點!!!
retry:
runtimeLock.assertLocked();
// Try this class's cache.
//imp ?設計
//併發
//remap(cls) -- 重映射 一開始找的時候的的確確是有的,但是沒有找到imp,我們在創建類的時候,哈希表會進行修復,對類進行重映射,可能就有了
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists. //先從本類的方法列表中開始查找
{
Method meth = getMethodNoSuper_nolock(cls, sel); //for循環進行查找方法,返回Method
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls); //給我們的緩存賦值
imp = meth->imp;
goto done;
}
}
// Try superclass caches and method lists. //從父類的方法列表中查找
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass) //只要不是nil,因爲上帝類NSObject最後指向了nil
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
// No implementation found. Try method resolver once. //如果都沒有找到,就進行解析
if (resolver && !triedResolver) {
runtimeLock.unlock();
_class_resolveMethod(cls, sel, inst); //當調用這個方法的時候,系統自動Call +resolveClassMethod or +resolveInstanceMethod.
runtimeLock.lock();
triedResolver = YES;
goto retry;
}
...
return imp;
}
二.動態方法解析
如果經過了一列操作,沒有找到imp,就會進行動態方法解析_class_resolveMethod。
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) { //判斷是否是元類
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
_class_resolveClassMethod(cls, sel, inst);
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
Person類裏面進行重寫resolveInstanceMethod和resolveClassMethod。
重寫了着兩個方法以後,調用類方法walk的時候,打印了兩次,意味着resolveClassMethod執行了兩次,Why???
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
// Resolver not implemented.
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel); //系統幫你給對象發送了消息
...
}
首先看一下消息轉發流程圖:
第一次調用,是沒有問題的,第二次調用是在消息無法處理以後又調用了一次。爲了印證這個理論,在打印的地方打一個breakpoint,打印一下堆棧調用信息。
resolveClassMethod是在上面分析的_objc_msgSend_uncached方法之後執行了第一次調用,再接着往下運行。
第二次調用就是在methodSignatureForSelector,也就是第二次消息轉發沒有處理之後開始了resolveClassMethod這個方法第二次調用。
消息轉發首先會進行動態方法解析resolveClassMethod,處理這個方法的處理很簡單,對select進行方法的轉移就可以了。這裏對類方法進行重寫,實例方法實現方式一樣,就不粘貼代碼了。
//重寫resolveClassMethod方法
+ (BOOL)resolveClassMethod:(SEL)sel{
if(sel == @selector(helloWord)) {
SEL helloWordSEL = @selector(helloWord);
Method helloWordMet = class_getClassMethod(self, helloWordSEL);
IMP helloWordIMP = method_getImplementation(helloWordMet);
const char *type = method_getTypeEncoding(helloWordMet);
return class_addMethod(self, helloWordSEL, helloWordIMP, type);
}
return [super resolveClassMethod:sel];
}
//自定義方法
+ (void)helloWord{
NSLog(@"%s",__func__);
}
當重寫了resolveClassMethod以後,回到我們當初的lookUpImpOrForward方法內部。
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
....
//重點
retry:
runtimeLock.assertLocked();
// Try this class's cache.
imp = cache_getImp(cls, sel);
if (imp) goto done;
...
// No implementation found. Try method resolver once.
if (resolver && !triedResolver) {
runtimeLock.unlock();
_class_resolveMethod(cls, sel, inst); //動態方法解析
runtimeLock.lock();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
triedResolver = YES;
goto retry; //回到了retry,重新查找方法
}
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = (IMP)_objc_msgForward_impcache; //如果動態方法解析過程沒有處理,就會進入到這裏--消息轉發
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlock();
return imp;
}
查看lookUpImpOrForward的源碼發現,執行完動態方法解析或消息轉發以後,又回到了retry進行方法的查找,剛剛在動態方法解析裏面已經對類添加了方法,實現了select的轉移,直接調用,不會再Crash了。
如果_class_resolveMethod動態方法沒有進行處理,就會進行消息轉發_objc_msgForward_impcache。這一塊只有彙編調用,沒有源碼實現,源碼實現閉源了。但是我們可以通過instrumentObjcMessageSends這個函數來打印底層調用了那些信息,最後會存儲在private/tmp文件夾下。
#import <Foundation/Foundation.h>
#import "Student.h"
#include <objc/message.h>
extern void instrumentObjcMessageSends(BOOL);
int main(int argc, const char * argv[]) {
@autoreleasepool {
instrumentObjcMessageSends(YES);
[Person walk];
instrumentObjcMessageSends(NO);
}
return 0;
}
+ Person NSObject initialize
+ Person Person resolveClassMethod:
+ Person Person resolveClassMethod:
+ NSObject NSObject resolveClassMethod:
+ NSObject NSObject resolveInstanceMethod:
+ NSObject NSObject resolveInstanceMethod:
+ Person Person forwardingTargetForSelector:
+ Person Person forwardingTargetForSelector:
- __NSCFConstantString __NSCFString _fastCStringContents:
+ NSObject NSObject forwardingTargetForSelector:
+ Person Person methodSignatureForSelector:
+ Person Person methodSignatureForSelector:
- __NSCFConstantString __NSCFString _fastCStringContents:
...
首先實現第一步,forwardingTargetForSelector。
//重寫forwardingTargetForSelector方法
- (id)forwardingTargetForSelector:(SEL)aSelector{
NSLog(@"%s",__func__);
if (aSelector == @selector(run)) {
// 轉發給我們的Student 對象
return [Student new];
}
return [super forwardingTargetForSelector:aSelector];
}
因此可以在forwardingTargetForSelector裏面進行自定義處理,還可以進行一系列cash的手機、防止崩潰等。
如果forwardingTargetForSelector依然沒有處理,那就開始處理消息簽名,消息轉發。
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
NSLog(@"%s",__func__);
if (aSelector == @selector(walk)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation{
//重定向
NSLog(@"%s",__func__);
NSString *sto = @"方法來了奧~";
anInvocation.target = [Student class];
[anInvocation setArgument:&sto atIndex:2];
NSLog(@"%@",anInvocation.methodSignature);
anInvocation.selector = @selector(run:);
[anInvocation invoke];
}
最後附上思維導圖