歡迎閱讀iOS探索系列(按序閱讀食用效果更加)html
書接上文說到cache_t
緩存的是方法,那麼方法又是什麼呢?c++
Runtime
是一套API
,由c、c++、彙編
一塊兒寫成的,爲OC
提供了運行時git
Runtime有兩個版本——Legacy
和Modern
,蘋果開發者文檔都寫得清清楚楚github
源碼中-old
、__OBJC__
表明Legacy
版本,-new
、__OBJC2__
表明Modern
版本,以此作兼容算法
Runtime
底層通過編譯會提供一套API和供FrameWork
、Service
使用 緩存
Runtime
調用方式:bash
經過clang編譯成cpp文件
能夠看到底層代碼,獲得方法的本質多線程
FXPerson *p = [FXPerson alloc];
[p fly];
複製代碼
FXPerson *p = ((FXPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("FXPerson"), sel_registerName("alloc"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("fly"));
複製代碼
((FXPerson *(*)(id, SEL))(void *)
是類型強轉(id)objc_getClass("FXPerson")
獲取FXPerson類對象sel_registerName("alloc")
等同於@selector()
那麼能夠理解爲((類型強轉)objc_msgSend)(對象, 方法調用)
併發
方法的本質是經過objc_msgSend
發送消息,id
是消息接收者,SEL
是方法編號app
若是外部定義了C函數並調用如
void fly() {}
,在clang編譯以後仍是fly()
而不是經過objc_msgSend
去調用。由於發送消息就是找函數實現的過程,而C函數能夠經過函數名
——指針
就能夠找到
#import <Foundation/Foundation.h>
#import <objc/message.h>
@interface FXFather: NSObject
- (void)walk;
+ (void)run;
@end
@implementation FXFather
- (void)walk { NSLog(@"%s",__func__); }
+ (void)run { NSLog(@"%s",__func__); }
@end
@interface FXSon: FXFather
- (void)jump;
+ (void)swim;
@end
複製代碼
子類FXSon
有實例方法jump
、類方法swim
父類FXFather
有實例方法walk
、類方法run
①發送實例方法
消息接收者——實例對象
FXSon *s = [FXSon new];
objc_msgSend(s, sel_registerName("jump"));
複製代碼
②發送類方法
消息接收者——類對象
objc_msgSend(objc_getClass("FXSon"), sel_registerName("swim"));
複製代碼
objc_msgSend
不能向父類發送消息,須要使用objc_msgSendSuper
,並給objc_super
結構體賦值(在objc2中只須要賦值receiver
、super_class)
/// Specifies the superclass of an instance.
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
複製代碼
③向父類發送實例方法
receiver——實例對象
;super_class——父類類對象
struct objc_super superInstanceMethod;
superInstanceMethod.receiver = s;
superInstanceMethod.super_class = objc_getClass("FXFather");
objc_msgSendSuper(&superInstanceMethod, sel_registerName("walk"));
複製代碼
④向父類發送類方法
receiver——類對象
;super_class——父類元類對象
struct objc_super superClassMethod;
superClassMethod.receiver = [s class];
superClassMethod.super_class = class_getSuperclass(object_getClass([s class]));
objc_msgSendSuper(&superClassMethod, sel_registerName("run"));
複製代碼
若是出現Too many arguments to function call, expected 0, have 2
問題,來到BuildSetting
把配置修改爲以下圖
objc_msgSend
是用匯編寫成的,至於爲何不用C而是用匯編寫,是由於:
打開objc
源碼,因爲主要研究arm64結構
的彙編實現,來到objc-msg-arm64.s
①開始objc_msgSend
②判斷消息接收者
是否爲空,爲空直接返回
③判斷tagged_pointers
(以後會講到)
④取得對象中的isa
存一份到p13
中(寄存器指令在逆向篇中會講到)
⑤根據isa
進行mask
地址偏移獲得對應的上級對象
(類、元類)
GetClassFromIsa_p16
定義,主要就是進行
isa & mask
獲得
class
操做
(其定義方式與iOS探索 isa初始化&指向分析一文中提到的shiftcls
殊途同歸)
⑥開始在緩存中查找imp
——開始了快速流程
從CacheLookup
開始了快速查找流程(此時x0是sel
,x16是class
)
#CACHE
是個宏定義表示16個字節,
[x16, #CACHE]
表示
類對象
內存地址偏移
16字節
獲得
cache
。
cache
一分爲二——8字節的
buckets
存放在p10,兩個4字節的
occupied
和
mask
存放在p11
#define CLASS __SIZEOF_POINTER__
#define CACHE (2 * __SIZEOF_POINTER__)
複製代碼
②x1是sel即cmd
,取出p11中的低32位(w11)——mask
,二者進行與運算獲得hash下標
存放在x12
③p12先左移動(1+PTRSHIFT)
,再與p10buckets
相加獲得新的p12——bucket
④拿出p12bucket
地址所在的值,放在p17imp
和p9sel
中,這點能夠從bucket_t
的結構中看出(sel強轉成key)用bucket
中的sel
與x1cmd
做對比,若是相同則緩存命中CacheHit
獲得其中的imp
;若是不等就跳轉⑤
struct bucket_t {
private:
// IMP-first is better for arm64e ptrauth and no worse for arm64.
// SEL-first is better for armv7* and i386 and x86_64.
#if __arm64__
MethodCacheIMP _imp;
cache_key_t _key;
#else
cache_key_t _key;
MethodCacheIMP _imp;
#endif
...
}
複製代碼
⑤若是bucket->sel == 0
則CheckMiss
;比較p12bucket
和p10buckets
,若是不相等就將x12bucket
的值進行自減操做(查找上一個bucket
),跳轉回④從新循環,直到bucket == buckets
遍歷結束跳轉⑥
.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
複製代碼
⑥平移哈希使得p12 = first bucket
,再重複進行一下相似④⑤⑥的操做—— 防止不斷循環的過程當中多線程併發,正好緩存更新了。若是bucket->sel == 0
走CheckMiss
,若是bucket == buckets
走JumpMiss
,本質是同樣的
.macro JumpMiss
.if $0 == GETIMP
b LGetImpMiss
.elseif $0 == NORMAL
b __objc_msgSend_uncached
.elseif $0 == LOOKUP
b __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
複製代碼
當NORMAL
時,CheckMiss
和JumpMiss
都走__objc_msgSend_uncached
⑦__objc_msgSend_uncached
調用MethodTableLookup
⑧保存參數調用c++方法進入慢速流程(準備好裝備和藥水打BOSS)
總結:方法查找的快速流程
能夠和cache_t::find
方法對比加深理解
彙編
__class_lookupMethodAndLoadCache3
與c++中_class_lookupMethodAndLoadCache3
相對應
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
複製代碼
// initialize = YES , cache = NO , resolver = YES
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver) {
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
// 緩存查找,cache爲NO直接跳過
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.
// lock是爲了防止多線程操做; 類是否被編譯
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.
// 從緩存裏面查找一遍,如有直接goto done
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists.
// 造成局部做用域,避免局部變量命名重複
{
// 在類的方法列表中查找方法,如有直接cache_fill
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.
// 在父類緩存中查找,如有直接cache_fill
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.
// 在父類的方法列表中查找方法,如有直接cache_fill
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);
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;
}
// 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;
}
複製代碼
慢速流程主要分爲幾個步驟:
①_class_lookupMethodAndLoadCache3
調用lookUpImpOrForward
,此時參數initialize=YES cache=NO resolver=YES
②runtimeLock.lock()
爲了防止多線程操做
③realizeClass(cls)
爲查找方法作準備條件,若是類沒有初始化時,初始化類和父類、元類等
④imp = cache_getImp(cls, sel)
爲了容錯從緩存
中再找一遍,如有goto done⑨
⑤// Try this class's method lists
局部做用域中,在類的方法列表
中查找方法,如有直接log_and_fill_cache
並goto done⑨
⑥// Try superclass caches and method lists
局部做用域中,遍歷父類:先在父類緩存
中查找,如有直接log_and_fill_cache
並goto done
;沒有再去父類的方法列表中
查找方法,如有直接log_and_fill_cache
並goto done⑨
⑦若是還沒找到就動態方法解析_class_resolveMethod
,標記爲triedResolver = YES(已自我拯救過)
並跳轉慢速流程④
⑧若是動態方法解析以後仍然沒找到imp
,就_objc_msgForward_impcache
獲得imp
並cache_fill
⑨done
:多線程解鎖,返回imp
接下來拆解步驟進行說明:
cache_getImp
這個方法後續會解釋getMethodNoSuper_nolock
遍歷調用search_method_list
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;
}
複製代碼
search_method_list
利用二分查找尋找方法static method_t *search_method_list(const method_list_t *mlist, SEL sel) {
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
// 若是方法列表已經排序好了,則經過二分查找法查找方法,以節省時間
return findMethodInSortedMethodList(sel, mlist);
} else {
// Linear search of unsorted method list
// 若是方法列表沒有排序好就遍歷查找
for (auto& meth : *mlist) {
if (meth.name == sel) return &meth;
}
}
#if DEBUG
// sanity-check negative results
if (mlist->isFixedUp()) {
for (auto& meth : *mlist) {
if (meth.name == sel) {
_objc_fatal("linear search worked when binary search did not");
}
}
}
#endif
return nil;
}
複製代碼
findMethodInSortedMethodList
二分查找算法的具體實現(瞭解便可)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;
// >>1 表示將變量n的各個二進制位順序右移1位,最高位補二進制0
// count >>= 1 若是count爲偶數則值變爲(count / 2);若是count爲奇數則值變爲(count-1) / 2
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);
// 取出中間method_t的name,也就是SEL
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--;
}
// 取出 probe
return (method_t *)probe;
}
// 若是keyValue > probeValue 則折半向後查詢
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
複製代碼
log_and_fill_cache
->cache_fill
->cache_fill_nolock
進行緩存log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
if (objcMsgLogEnabled) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
cache_fill (cls, sel, imp, receiver);
}
複製代碼
_class_resolveMethod
動態方法解析——在找不到imp時的自我拯救操做
cls
是元類的話說明調用類方法,走_class_resolveInstanceMethod
;非元類的話調用了實例方法,走_class_resolveInstanceMethod
SEL_resolveInstanceMethod
消息,系統調用resolveInstanceMethod
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);
}
}
}
複製代碼
_objc_msgForward_impcache
在彙編中調用了_objc_msgForward
,而後又進入_objc_forward_handler
,它在c++調用了objc_defaultForwardHandler
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
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);
}
複製代碼
原來unrecognized selector sent to instance xxx
是這麼來的啊...
OC的消息機制分爲三個階段:
本文主要講了方法查找流程
,順帶提了幾句動態方法解析
,下一篇文章將經過案例來詳細解讀動態方法解析
並着重介紹消息轉發機制
最後準備了一份動態方法決議的Demo,有興趣的小夥伴們能夠本身下斷點看看方法查找流程
和研究下動態方法決議