探索方法的本質(zhì)
一個(gè)最基本的方法調(diào)用代碼
void run(){
NSLog(@"%s",__func__);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
LGPerson *person = [[LGPerson alloc] init];;
[person sayNB];
}
return 0;
}
方法的調(diào)用底層到底是個(gè)什么東西呢
我們可以利用clang的一些命令 clang -rewrite-objc main.m -o main.cpp,將main文件編譯成c之后的代碼
cd /Users/caoxiang/Desktop/dealloc/dealloc
xcrun -sdk iphonesimulator clang -rewrite-objc ViewController.m
擴(kuò)展一下指定真機(jī)
xcrun -sdk iphoneos clang -rewrite-objc ViewController.m
- 指定模擬器
xcrun -sdk iphonesimulator clang -rewrite-objc ViewController.m
- 指定SDK版本
xcrun -sdk iphonesimulator10.3 clang -rewrite-objc ViewController.m

其中有個(gè)問(wèn)題
run()方法直接調(diào)用,而我們的oc方法被編譯成一個(gè)objc_msgSend函數(shù)(runtime里的消息發(fā)送機(jī)制)run函數(shù)在編譯器就確定了函數(shù)的調(diào)用與實(shí)現(xiàn),
因此,oc的方法的本質(zhì)就是
objc_msgSend(或者objc_msgSendSuper等函數(shù))的調(diào)用objc_msgSend兩個(gè)參數(shù),第一個(gè)參數(shù)是對(duì)象是哪個(gè)對(duì)象的操作,第二個(gè)參數(shù)就是sel也就是方法,通過(guò)sel找到imp的實(shí)現(xiàn),完成方法的調(diào)用.就叫做消息發(fā)送機(jī)制.
objc_msgSend流程
方法的查找流程分為兩種
- 快速查找:利用匯編直接從緩存中找
-
慢速查找:快速查找沒(méi)有命中,從方法表中查找
現(xiàn)在方法調(diào)用處打一個(gè)斷點(diǎn)
打斷點(diǎn)
然后debug->debug WorkFlow ->Always Show Disassembly

然后進(jìn)入?yún)R編

然后按住cotrol + stepIn

可以看到
objc_msgSend在libobjc中
打開(kāi)源碼搜索objc_msgSend,我們直接看匯編,找到.s文件,現(xiàn)在架構(gòu)大部分都是arm64,所以我們直接看objc-msg-arm64.s文件
看匯編重要的一點(diǎn)事看ENTRY表示入口,如下圖


// person - isa - 類
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13 // p16 = class
p13為isa,因?yàn)閤0為首地址,[]就是首地址的值,首地址就是isa指針
GetClassFromIsa_p16是一個(gè)宏,下邊是實(shí)現(xiàn)
.macro GetClassFromIsa_p16 /* src */
#if SUPPORT_INDEXED_ISA
// Indexed isa
mov p16, $0 // optimistically set dst = src
tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f // done if not non-pointer isa
// isa in p16 is indexed
adrp x10, _objc_indexed_classes@PAGE
add x10, x10, _objc_indexed_classes@PAGEOFF
ubfx p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS // extract index
ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array
1:
#elif __LP64__
// 64-bit packed isa
and p16, $0, #ISA_MASK
#else
// 32-bit raw isa
mov p16, $0
#endif
.endmacro
SUPPORT_INDEXED_ISAindexIsa一般不常用,watchos開(kāi)發(fā)是indexIsa
and p16, $0, #ISA_MASK這句才是重點(diǎn),$0是傳進(jìn)來(lái)的參數(shù),也就是p13 isa 拿$0與isa_mask進(jìn)行&運(yùn)算得到類,在前兩篇文章中介紹了對(duì)象和類之間是怎么聯(lián)系起來(lái)的,所以我們的p16是一個(gè)類
拿到isa之后進(jìn)行下邊操作
LGetIsaDone:
CacheLookup NORMAL
CacheLookup是一個(gè)宏定義,下邊是實(shí)現(xiàn)代碼
.macro CacheLookup
// p1 = SEL, p16 = isa
ldp p10, p11, [x16, #CACHE] // p10 = buckets, p11 = occupied|mask
#if !__LP64__
and w11, w11, 0xffff // p11 = mask
#endif
and w12, w1, w11 // x12 = _cmd & mask
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // wrap: p12 = first bucket, w11 = mask
add p12, p12, w11, UXTW #(1+PTRSHIFT)
// p12 = buckets + (mask << 1+PTRSHIFT)
// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
ldp p17, p9, [x12] // {imp, sel} = *bucket
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // double wrap
JumpMiss $0
.endmacro
1: cmp p9, p1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
b.ne 2f如果bucket的sel != _cmd找2,否則直接命中返回$0
快速查找流程結(jié)束,如果沒(méi)有命中就進(jìn)入慢速查找流程
2: // not hit: p12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp p12, p10 // wrap if bucket == buckets
b.eq 3f //進(jìn)行3主要是為了保存一份方便下次查找
ldp p17, p9, [x12, #-BUCKET_SIZE]! // {imp, sel} = *--bucket
b 1b // loop
3: // wrap: p12 = first bucket, w11 = mask
add p12, p12, w11, UXTW #(1+PTRSHIFT)
// p12 = buckets + (mask << 1+PTRSHIFT)
// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
ldp p17, p9, [x12] // {imp, sel} = *bucket
CheckMiss也是一個(gè)宏
.macro CheckMiss
// miss if bucket->sel == 0
.if $0 == GETIMP
cbz p9, LGetImpMiss
.elseif $0 == NORMAL
cbz p9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
cbz p9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
我們這里傳進(jìn)來(lái)的是normal,那么找__objc_msgSend_uncached
我們搜索__objc_msgSend_uncached找到他的入口
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band p16 is the class to search
MethodTableLookup
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
來(lái)看一下MethodTableLookup是什么東西
他也是一個(gè)宏定義
.macro MethodTableLookup
...
bl __class_lookupMethodAndLoadCache3
...
這里只展示了一個(gè)最重要的一行代碼,bl跳轉(zhuǎn)到__class_lookupMethodAndLoadCache3
我們根據(jù)以往經(jīng)驗(yàn),匯編會(huì)自動(dòng)在前邊加一個(gè)_,那么我們?nèi)サ粢粋€(gè)下劃線全局搜_class_lookupMethodAndLoadCache3
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
來(lái)一個(gè)demo
@interface LGPerson : NSObject
- (void)sayNB;
+ (void)sayHappay;
@end
@implementation LGPerson
- (void)sayNB{
NSLog(@"%s",__func__);
}
+ (void)sayHappay{
NSLog(@"%s",__func__);
}
@end
@interface LGStudent : LGPerson
- (void)sayHello;
+ (void)sayObjc;
@end
@implementation LGStudent
- (void)sayHello{
NSLog(@"%s",__func__);
}
+ (void)sayObjc{
NSLog(@"%s",__func__);
}
@end
@interface NSObject (LG)
- (void)sayMaster;
+ (void)sayEasy;
@end
@implementation NSObject (LG)
- (void)sayMaster{
NSLog(@"%s",__func__);
}
+ (void)sayEasy{
NSLog(@"%s",__func__);
}
@end
LGStuednt繼承與LGPerson,LGPerson繼承與NSObject
然后調(diào)用
LGStudent *student = [[LGStudent alloc] init];
// 對(duì)象方法
// 自己有 - 返回自己
[student sayHello];
// 自己沒(méi)有 - 老爸 -
// [person sayNB]; // CACHE
[student sayNB];
// 自己沒(méi)有 - 老爸沒(méi)有 - NSObject
[student sayMaster];
// 自己沒(méi)有 - 老爸沒(méi)有 - NSObject 沒(méi)有
// unrecognized selector sent to instance 0x103000450
[student performSelector:@selector(saySomething)];
- 自己有的時(shí)候返回自己的方法
- 自己沒(méi)有的時(shí)候找父類
- 老爸沒(méi)有時(shí)找NSObject
- 都沒(méi)有,拋出異常
實(shí)例方法的查找會(huì)根據(jù)繼承連去一層一層的找
類方法的調(diào)用
// 類方法
// 自己有 - 返回自己
[LGStudent sayObjc];
// 自己沒(méi)有 - 老爸 -
[LGStudent sayHappay];
// 自己沒(méi)有 - 老爸沒(méi)有 - NSObject
[LGStudent sayEasy];
// 自己沒(méi)有 - 老爸沒(méi)有 - NSObject 沒(méi)有
這里會(huì)有一個(gè)問(wèn)題如果我這樣調(diào)用會(huì)不會(huì)崩潰
[LGStudent performSelector:@selector(sayMaster)];
會(huì)打印
-[NSObject(LG) sayMaster]
發(fā)現(xiàn)不會(huì)蹦,因?yàn)轭惙椒ù嬖谠惱镞?我們調(diào)用類方法
- 首先去元類里邊找,
- 元類里邊沒(méi)有找到就招父元類,
- 沒(méi)有找到繼續(xù)根據(jù)繼承鏈找,
- 最后找到根元類(
NSObject的元類),根元類又繼承與NSObject

最后找到
sayMaster,所以不會(huì)崩潰看下源碼來(lái)驗(yàn)證下到底是不是這個(gè)流程
我們上邊看匯編,如果緩存沒(méi)有命中就來(lái)到慢速查找流程,也就是
_class_lookupMethodAndLoadCache3方法
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
- 當(dāng)調(diào)用實(shí)例方法的時(shí)候,
cls就是當(dāng)前類,sel就是調(diào)用的方法,obj就是當(dāng)前實(shí)例 - 當(dāng)調(diào)用類方法的時(shí)候,
cls則代表的是當(dāng)前類的元類(MetaClass),因?yàn)殪o態(tài)方法是存放在元類里邊的
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) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
// runtimeLock is held during isRealized and isInitialized checking
// to prevent races against concurrent realization.
// runtimeLock is held during method search to make
// method-lookup + cache-fill atomic with respect to method addition.
// Otherwise, a category could be added but ignored indefinitely because
// the cache was re-filled with the old value after the cache flush on
// behalf of the category.
runtimeLock.lock();
checkIsKnownClass(cls);
if (!cls->isRealized()) {
realizeClass(cls);
}
if (initialize && !cls->isInitialized()) {
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.lock();
// If sel == initialize, _class_initialize will send +initialize and
// then the messenger will send +initialize again after this
// procedure finishes. Of course, if this is not being called
// from the messenger then it won't happen. 2778172
}
retry:
runtimeLock.assertLocked();
// Try this class's cache.
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists.
{
Method meth = getMethodNoSuper_nolock(cls, sel);
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)
{
// 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;
}
}
}
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;
}
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlock();
return imp;
}
因?yàn)槭锹俨檎?這里傳進(jìn)來(lái)的cache為NO,所以下邊這幾行代碼不看,
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
這里加了一把鎖,是防止多線程情況下產(chǎn)生錯(cuò)亂,比如同時(shí)調(diào)用a方法和b方法,那么這里在調(diào)用a的時(shí)候返回一個(gè)b的imp,會(huì)產(chǎn)生問(wèn)題
checkIsKnownClass,是判斷類是否合法
if (!cls->isRealized()) {
realizeClass(cls);
}
這行代碼是拿到父類元類,以及類的data里邊的rw里的ro里的methodlist等等一系列信息,是為方法查找做準(zhǔn)備條件,這里不做重點(diǎn)研究.
// Try this class's method lists.
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
先在本類里邊找,getMethodNoSuper_nolock
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
assert(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
for (auto mlists = cls->data()->methods.beginLists(),
end = cls->data()->methods.endLists();
mlists != end;
++mlists)
{
method_t *m = search_method_list(*mlists, sel);
if (m) return m;
}
return nil;
}
拿到類的data里的methodList進(jìn)行循環(huán)遍歷,
search_method_list進(jìn)行二分法查找,查找速度更快,下邊為查找算法,這里不做過(guò)多研究
static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
assert(list);
const method_t * const first = &list->first;
const method_t *base = first;
const method_t *probe;
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)probe->name;
if (keyValue == probeValue) {
// `probe` is a match.
// Rewind looking for the *first* occurrence of this value.
// This is required for correct category overrides.
while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
probe--;
}
return (method_t *)probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
通過(guò)一些列算法找到方法,然后看
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
如果找到了method那么下次還慢速查找么,蘋果爸爸肯定不會(huì)這樣蠢的,如果找到就調(diào)用log_and_fill_cache進(jìn)行緩存,下次直接利用匯編快速查找,緩存的操作跟我們研究cache_t結(jié)構(gòu)時(shí)是一模模一樣樣的.
goto done 然后查找結(jié)束
如果找不到呢,那么就去找父類Try superclass caches and method lists.根據(jù)代碼注釋我們也可以看出來(lái)接下來(lái)要找父類了.
如果找父類,就不開(kāi)始跳匯編了,因?yàn)閯傞_(kāi)始父類元類等條件我們已經(jīng)準(zhǔn)備好了,那么我們現(xiàn)在直接找父類的cache.
// Superclass cache.
imp = cache_getImp(curClass, sel);
如果找到imp,直接goto done直接返回,如果找不到,然后找到父類的方法列表進(jìn)行查找(流程同在本類中查找流程)
如果都找不到方法呢,直接報(bào)錯(cuò)我們很熟悉的一個(gè)錯(cuò)誤+[LGStudent sayLove]: unrecognized selector sent to class 0x1000012e8.
imp = (IMP)_objc_msgForward_impcache;看這句代碼.發(fā)現(xiàn)點(diǎn)不進(jìn)去.那么按照國(guó)際慣例,全局搜索
然后選擇objc_msg_arm64.s沒(méi)錯(cuò)又是惡心人的匯編,為什么找他呢,因?yàn)槠渌胤蕉际钦{(diào)用,沒(méi)有實(shí)現(xiàn),按照經(jīng)驗(yàn)STATIC_ENTRY __objc_msgForward_impcache ,是不是很熟悉
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
__objc_forward_handler是個(gè)啥玩意兒啊,來(lái)搜一下(經(jīng)驗(yàn)告訴我搜不到的時(shí)候去掉一個(gè)下劃線),
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
發(fā)現(xiàn)他是個(gè)這么個(gè)玩意兒objc_defaultForwardHandler,點(diǎn)進(jìn)去看一下我的天啊,出現(xiàn)了好熟悉的代碼
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}
這不就是經(jīng)常出現(xiàn)的報(bào)錯(cuò)原因么
但是找不到方法直接報(bào)錯(cuò),體驗(yàn)很不好,有沒(méi)有什么方法補(bǔ)救一下呢?
蘋果爸爸告訴我們,可以的,在給你一次機(jī)會(huì).可以利用消息轉(zhuǎn)發(fā)機(jī)制
http://www.itdecent.cn/p/03383d2d395d
補(bǔ)充:為什么要設(shè)計(jì)meteClass
1、首先會(huì)再一次的從類中尋找需要調(diào)用方法的緩存,如果能命中緩存直接返回該方法的實(shí)現(xiàn),如果不能命中則繼續(xù)往下走。
2、從類的方法列表中尋找該方法,如果能從列表中找到方法則對(duì)方法進(jìn)行緩存并返回該方法的實(shí)現(xiàn),如果找不到該方法則繼續(xù)往下走。
3、從父類的緩存尋找該方法,如果父類緩存能命中則將方法緩存至當(dāng)前調(diào)用方法的類中(注意這里不是存進(jìn)父類),如果緩存未命中則遍歷父類的方法列表,之后操作如同第2步,未能命中則繼續(xù)走第3步直到尋找到基類。
4、如果到基類依然沒(méi)有找到該方法則觸發(fā)動(dòng)態(tài)方法解析流程。
5、還是找不到就觸發(fā)消息轉(zhuǎn)發(fā)流程
走到這里一套方法發(fā)送的流程就都走完了,那這跟元類的存在有啥關(guān)系?我們都知道類方法是存儲(chǔ)在元類中的,那么可不可以把元類干掉,在類中把實(shí)例方法和類方法存在兩個(gè)不同的數(shù)組中?
答:行是肯定可行的,但是在lookUpImpOrForward執(zhí)行的時(shí)候就得標(biāo)注上傳入的cls到底是實(shí)例對(duì)象還是類對(duì)象,這也就意味著在查找方法的緩存時(shí)同樣也需要判斷cls到底是個(gè)啥。
倘若該類存在同名的類方法和實(shí)例方法是該調(diào)用哪個(gè)方法呢?這也就意味著還得給傳入的方法帶上是類方法還是實(shí)例方法的標(biāo)識(shí),SEL并沒(méi)有帶上當(dāng)前方法的類型(實(shí)例方法還是類方法),參數(shù)又多加一個(gè),而我們現(xiàn)在的objc_msgSend()只接收了(id self, SEL _cmd, ...)這三種參數(shù),第一個(gè)self就是消息的接收者,第二個(gè)就是方法,后續(xù)的...就是各式各樣的參數(shù)。
通過(guò)元類就可以巧妙的解決上述的問(wèn)題,讓各類各司其職,實(shí)例對(duì)象就干存儲(chǔ)屬性值的事,類對(duì)象存儲(chǔ)實(shí)例方法列表,元類對(duì)象存儲(chǔ)類方法列表,完美的符合6大設(shè)計(jì)原則中的單一職責(zé),而且忽略了對(duì)對(duì)象類型的判斷和方法類型的判斷可以大大的提升消息發(fā)送的效率,并且在不同種類的方法走的都是同一套流程,在之后的維護(hù)上也大大節(jié)約了成本。
