iOS OC 方法的本質

前言:

前面探究了方法在類中的緩存,那麼方法的本質是什麼呢?方法調用在底層作了什麼呢?今天咱們來探索一下:api

1. 方法本質初探

看一下一段代碼: 先定義一個LGPerson類,而後定義sayNB對象方法,而後在main函數中調用緩存

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *person = [LGPerson alloc];
        [person sayNB];
    }
    return 0;
}
複製代碼

而後經過clang生成cpp文件,在底層編譯的cpp文件中查看main函數以下:sass

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc"));
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));imp -  函數
    }
    return 0;
}
複製代碼

由此:咱們能夠簡單得出,方法的本質是經過objc_msgSend發送消息,第一個參數爲id消息接受者,第二個參數爲sel方法編號。bash

那麼咱們定義的函數會調用objc_msgSend發送消息嗎? 咱們定義下面函數,並在main中調用,多線程

void run(){
    NSLog(@"%s",__func__);
}
複製代碼

經過clang查看cpp文件,發現函數不須要調用objc_msgSend,函數能夠直接經過函數名(指針),找到函數的實現,不須要像方法經過sel,找到ipm,再找到方法的實現。函數

父類發送消息(對象方法):性能

struct objc_super lgSuper;
        lgSuper.receiver = s;
        lgSuper.super_class = [LGPerson class];
        objc_msgSendSuper(&lgSuper, @selector(sayHello));
複製代碼

父類發送消息(類方法):測試

struct objc_super myClassSuper;
        myClassSuper.receiver = [s class];
        myClassSuper.super_class = class_getSuperclass(object_getClass([s class]));// 元類
        objc_msgSendSuper(&myClassSuper, sel_registerName("sayNB"));
複製代碼

objc_super源碼:ui

struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;
    /// Specifies the particular superclass of the instance to message. 
#if !defined(__cplusplus) && !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained _Nonnull Class class;
#else
    __unsafe_unretained _Nonnull Class super_class;
#endif
    /* super_class is the first class to search */
};
#endif
複製代碼

所以,在調用Runtim api父類發送消息時,須要設置receiversuper_classspa

問題:在測試中,不要嚴格識別參數,須要以下設置:

2. objc_msgSend彙編分析

objc源碼中斷點

而後 Debug -> Debug Workflow ->always Show Disassembly進行彙編分析:

objc_msgSend處斷點,

經過control + in,查看libobjc.A.dylib objc_msgSend,發現objc_msgSend底層是用彙編實現的。

補充:

爲何`objc_msgSend`用匯編實現呢?
1. 在性能方面,`彙編`更容易被機器識別
2. 在發送消息時,有不少未知的參數,c 語言中不能經過寫一個函數來保留未知的參數而且跳轉到一個任意的
函數指針,c語言沒有知足作這件事的必要特性。

彙編寄存器
arm64下有31位通用寄存器,x0 - x7,是參數,返回值會放到 x0中
複製代碼

objc_msgSend的彙編分析:

首先,在objc源碼中全局搜索objc_msgSend找到彙編源碼,

彙編代碼:

具體分析:

1. cmp	p0, #0			// nil check and tagged pointer check
  先對比當前0號寄存器是否爲空,爲空,當前沒有接收者
  
2. 判斷 SUPPORT_TAGGED_POINTERS 
  直接執行 LNilOrTagged 或者 LReturnZero
3. 當有消息接收者,正常狀況下,拿到 p13	// p13 = isa
4. GetClassFromIsa_p16 p13	 經過p13(isa),獲取 Class ;
    GetClassFromIsa_p16 先平移,取值 shiftcls,而後獲得 Class, 或者 isa & Mask 直接獲取 Class
複製代碼

LGetIsaDone 源碼:

5. LGetIsaDone 查找isa完畢  開始正常查找 CacheLookup NORMAL
   5.1  ldp	p10, p11, [x16, #CACHE]	// p10 = buckets, p11 = occupied|mask
        先平移16字節,得到cache,找到緩存方法的 buckets 和 occupied
   5.2 and	w12, w1, w11		// x12 = _cmd & mask
        經過 _cmd & mask 獲取哈希的下標,
   5.3 循環查找 bucket  add	p12, p10, p12, LSL
   5.4 ldp p17, p9, [x12] 經過 sel 找到 bucket 中的cmd 對比,相等直接返回 CacheHit $0, 找不到,直接走 b.ne	2f 即:CheckMiss
        cmp	p9, p1			// if (bucket->sel != _cmd) 
6. CheckMiss 中找到後,b.eq 3f,進入步驟三,平移哈希,將方法緩存到 bucket中一份,
若是沒有找到則 {imp, sel} = *--bucket,循環遞歸查找。
而後會在查找一遍 5.4 流程(
防止多線程,緩存更新),找不到緩存,則 JumpMiss $0
複製代碼

CheckMiss代碼:

當時 NORMAL形式時,進入 __objc_msgSend_uncached,以下:

MethodTableLookup源碼:

_class_lookupMethodAndLoadCache3方法:

IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{        
    return lookUpImpOrForward(cls, sel, obj, 
                              YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}

複製代碼
問題:
爲何從彙編調用 C 方法?
_class_lookupMethodAndLoadCache3 是一系列慢速方法查找,沒有必要使用匯編
複製代碼

總結:

1. ENTRY _objc_msgSend 進入 
2. TAGGED_POINTERS  判斷  
3. GetClassFromIsa_p16 p13 經過 isa 獲取 Class
4. 緩存查找 CacheLookup
5. cache_t 處理,處理哈希,查找 buckrt,找到返回{imp,sel} = *buckrt->imp,找不到 JumpMiss
6. 緩存中找不到方法 進入 __objc_msgSend_uncached
7. STATIC_ENTRY __objc_msgSend_uncached
8. MethodTableLookup  調用__class_lookupMethodAndLoadCache3
複製代碼

最後一個遺留問題,調用_class_lookupMethodAndLoadCache3中是怎麼慢速查找的呢?下一篇接着探索。

相關文章
相關標籤/搜索