一颇象、什么是Runtime?
OC是一門動態(tài)類型的語言,它允許很多操作都推遲到程序運行時再進行
OC的動態(tài)性就是由Runtime來支撐和實現(xiàn)的并徘,而Runtime是一套C語言的API遣钳,它封裝了很多動態(tài)性相關(guān)的函數(shù)
我們平時編寫的OC代碼,其實底層都是將代碼轉(zhuǎn)換成了Runtime API來進行調(diào)用
二麦乞、OC的消息機制
OC的方法調(diào)用其實都是轉(zhuǎn)成了objc_msgSend函數(shù)的調(diào)用蕴茴,給方法的調(diào)用者發(fā)送了一條消息。
objc_msgSend底層有3個階段
- 消息發(fā)送(當(dāng)前類姐直、父類中查找)
- 動態(tài)方法解析
- 消息轉(zhuǎn)發(fā)
三倦淀、流程解析
例如,我們有一個Person類声畏,將這個Person實例化對象
先導(dǎo)入頭文件
#import <objc/runtime.h>
#import <objc/message.h>
#import "Person.h"
//oc寫法
Person *person1 = [[Person alloc] init];
//runtime寫法
Person *person2 = objc_msgSend(objc_msgSend([Person class], @selector(alloc)), @selector(init));
新工程這里會報錯
Too many arguments to function cal
這個問題只需要在FuDemo->Target中Build Setting的Enable Strict Checking of objc_msgSend Calls
的值設(shè)置為NO
即可撞叽。
運行程序,我們能看到砰识,person1與person2都創(chuàng)建成功了,接下來就看看匯編代碼片段是不是執(zhí)行了objc_msgSend方法能扒。首先將Person1的初始化代碼注釋掉,然后打開Always Show Disassembly
辫狼,讓我們在調(diào)試時初斑,斷點能直接進入到匯編代碼界面,如下圖:
從匯編代碼界面,我們能看到如下信息:
Person進行了alloc真椿,然后該對象調(diào)用了
objc_msgSend
進行init
鹃答。將斷點執(zhí)行到調(diào)用objc_msgSend方法,‘按住Control + step into’查看
objc_msgSend
內(nèi)部實現(xiàn)突硝,再用同樣方法查看objc_msgSend_uncached
测摔,最終我們可以找到class_lookupMethodAndLoadCache3
這樣的調(diào)用步驟。
四解恰、objc_msgSend執(zhí)行流程
工程搜索objc_msgSend
锋八,找到objc-msg-x86_64.s
我們可以找到如下代碼片段
/********************************************************************
*
* id objc_msgSend(id self, SEL _cmd,...);
* IMP objc_msgLookup(id self, SEL _cmd, ...);
*
* objc_msgLookup ABI:
* IMP returned in r11
* Forwarding returned in Z flag
* r10 reserved for our use but not used
*
********************************************************************/
這下面就是實現(xiàn)的主要步驟,我們抽取主要信息查看
/*進入到objc_msgSend*/
ENTRY _objc_msgSend
/*查找當(dāng)前isa*/
GetIsaFast NORMAL // r10 = self->isa
/*從緩存中查找當(dāng)前方法护盈,如果查找到了IMP將結(jié)果返回給調(diào)用者*/
CacheLookup NORMAL, CALL // calls IMP on success
/*在緩存中沒找到挟纱,搜索方法列表*/
// cache miss: go search the method lists
LCacheMiss:
/*\*/
// isa still in r10
/*跳轉(zhuǎn)objc_msgSend_uncached*/
jmp __objc_msgSend_uncached
objc_msgSend_uncached
內(nèi)基本都是在調(diào)用MethodTableLookup(從當(dāng)前方法列表中查找)
。如果找到腐宋,則將IMP放到寄存器中紊服。
MethodTableLookup
能找到class_lookupMethodAndLoadCache3
被調(diào)用檀轨,正好這也是之前我們所驗證的最后一部。
在class_lookupMethodAndLoadCache3
中只做了lookUpImpOrForward(查找方法實現(xiàn))
這一件事
因此runtime的執(zhí)行順序為:
1.objc_msgSend
2.CacheLookup(有緩存IMP欺嗤,則返回給調(diào)用者参萄。沒有緩存則往下執(zhí)行)
3.objc_msgSend_unCached
4.MethodTableLookup
5.class_lookupMethodAndLoadCache3
6.lookUpImpOrForward
五、lookUpImpOrForward代碼片段注釋
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
/*從緩存中查找*/
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
/*確保當(dāng)前isa初始化*/
checkIsKnownClass(cls);
if (!cls->isRealized()) {
realizeClass(cls);
}
if (initialize && !cls->isInitialized()) {
runtimeLock.unlock();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.lock();
}
retry:
runtimeLock.assertLocked();
/*從當(dāng)前類的緩存中查找*/
imp = cache_getImp(cls, sel);
if (imp) goto done;
/*從當(dāng)前類的方法列表中查找*/
{
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
/*將查找到的meth放入緩存中*/
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
/*沿著繼承鏈向上查找*/
{
unsigned attempts = unreasonableClassCount();
for (Class curClass = cls->superclass;
curClass != nil;
curClass = curClass->superclass)
{
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
break;
}
}
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
}
/*沒有找到IMP煎饼,嘗試動態(tài)決議 resolveInstanceMethod*/
if (resolver && !triedResolver) {
runtimeLock.unlock();
_class_resolveMethod(cls, sel, inst);
runtimeLock.lock();
triedResolver = YES;
goto retry;
}
/*沒有實現(xiàn)動態(tài)決議方法拧揽,觸發(fā)消息轉(zhuǎn)發(fā)流程 (forwardingTargetForSelector和forwardInvocation)*/
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
runtimeLock.unlock();
return imp;
}