Runtime源碼解析-消息發送
- 在我們平時開發項目中,除了頻繁的創建對象之外,用的最多的就是調用方法。本篇文章就是主要研究方法是如何調用的。
前言
- 在
Objective-C
中的“方法調用”其實應該叫做消息傳遞- 我們為什么需要消息傳遞?
- 在很多語言,比如 C ,調用一個方法其實就是跳到內存中的某一點并開始執行一段代碼。沒有任何動態的特性,因為這在編譯時就決定好了。而在
Objective-C
中,[object foo]
語法并不會立即執行foo
這個方法的代碼。它是在運行時給object
發送一條叫foo
的消息。這個消息,也許會由object
來處理,也許會被轉發給另一個對象,或者不予理睬假裝沒收到這個消息。多條不同的消息也可以對應同一個方法實現,這些都是在程序運行的時候決定的。
- 在底層中,
[receiver message]
會被翻譯為objc_msgSend(receiver, @selector(message))
。也就是通過objc_msgSend()
方法進行調用。
objc_msgSend
- 在
libobjc
中,該方法是通過匯編實現的,我們可以在objc-msg-arm64.s
找到對應的實現。
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
# 1. 判斷receiver是否為nil
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
# 2. 獲取isa和對應class
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13, 1, x0 // p16 = class
LGetIsaDone:
# 3. 去緩存中查找方法
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq LReturnZero // nil check
GetTaggedClass
b LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret
END_ENTRY _objc_msgSend
- 該方法中主要有以下幾個步驟
- 首先判斷消息接收者是否為空,如果為空的話,則跳轉至
LReturnZero
方法,直接退出該方法 - 去獲取該對象的
isa
,并且通過isa
獲取對應的class
- 去緩存中查找方法
- 首先判斷消息接收者是否為空,如果為空的話,則跳轉至
CacheLookup 緩存查找
- 該方法需要類中
cache_t
的結構,如果對這塊不夠熟悉,請先閱讀Runtime源碼解析-類中cache - 我們查看一下
CacheLookup
的實現
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
mov x15, x16 // stash the original isa
LLookupStart\Function:
// p1 = SEL, p16 = isa
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
ldr p10, [x16, #CACHE] // p10 = mask|buckets
lsr p11, p10, #48 // p11 = mask
and p10, p10, #0xffffffffffff // p10 = buckets
and w12, w1, w11 // x12 = _cmd & mask
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16 // arm64系統
# 1. 把isa地址移動16個字節,找到其中cache_t地址
ldr p11, [x16, #CACHE] // p11 = mask|buckets
#if CONFIG_USE_PREOPT_CACHES
#if __has_feature(ptrauth_calls)
tbnz p11, #0, LLookupPreopt\Function
and p10, p11, #0x0000ffffffffffff // p10 = buckets
#else
# 2. 獲取buckets的首地址
# p10 = _bucketsAndMaybeMask & 0x0000fffffffffffe
and p10, p11, #0x0000fffffffffffe // p10 = buckets
tbnz p11, #0, LLookupPreopt\Function
#endif
# 3. 獲取傳入_cmd的哈希值
eor p12, p1, p1, LSR #7
and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
and p10, p11, #0x0000ffffffffffff // p10 = buckets
and p12, p1, p11, LSR #48 // x12 = _cmd & mask
#endif // CONFIG_USE_PREOPT_CACHES
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
ldr p11, [x16, #CACHE] // p11 = mask|buckets
and p10, p11, #~0xf // p10 = buckets
and p11, p11, #0xf // p11 = maskShift
mov p12, #0xffff
lsr p11, p12, p11 // p11 = mask = 0xffff >> p11
and p12, p1, p11 // x12 = _cmd & mask
#else
#error Unsupported cache mask storage for ARM64.
#endif
# 4. 去緩存中去查找方法
add p13, p10, p12, LSL #(1+PTRSHIFT)
// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
// do {
1: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
cmp p9, p1 // if (sel != _cmd) {
b.ne 3f // scan more
// } else {
2: CacheHit \Mode // hit: call or return imp
// }
3: cbz p9, \MissLabelDynamic // if (sel == 0) goto Miss;
cmp p13, p10 // } while (bucket >= buckets)
b.hs 1b
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
add p13, p10, w11, UXTW #(1+PTRSHIFT)
// p13 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
// p13 = buckets + (mask << 1+PTRSHIFT)
// see comment about maskZeroBits
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
add p13, p10, p11, LSL #(1+PTRSHIFT)
// p13 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = first probed bucket
// do {
4: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
cmp p9, p1 // if (sel == _cmd)
b.eq 2b // goto hit
cmp p9, #0 // } while (sel != 0 &&
ccmp p13, p12, #0, ne // bucket > first_probed)
b.hi 4b
# 省略部分實現
.endmacro
- 該方法內部主要做了一下幾件事
- 通過
isa
指針,獲取cache_t
結構的地址 - 通過
cache_t
的地址,獲取buckets
的首地址 - 計算傳入
_cmd
的哈希值,用作查詢起始位置 - 循環遍歷
buckets
,找到對應方法實現
- 通過
- 這里我們主要討論
arm64
下的實現,也就是條件判斷中CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
1. 獲取cache_t
ldr p11, [x16, #CACHE] // p11 = mask|buckets
- 在
objc_class
結構中,cache_t
相對首地址偏移16個字節 - 這里
x16
存儲了isa
地址,然后偏移16個字節,把得到的cache_t
地址存儲在p11
寄存器中
2. 獲取buckets
#if CONFIG_USE_PREOPT_CACHES
#if __has_feature(ptrauth_calls)
tbnz p11, #0, LLookupPreopt\Function
and p10, p11, #0x0000ffffffffffff // p10 = buckets
#else // 真機,走下面
and p10, p11, #0x0000fffffffffffe // p10 = buckets
tbnz p11, #0, LLookupPreopt\Function
#endif
eor p12, p1, p1, LSR #7
and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
and p10, p11, #0x0000ffffffffffff // p10 = buckets
and p12, p1, p11, LSR #48 // x12 = _cmd & mask
#endif // CONFIG_USE_PREOPT_CACHES
-
p11 = cache_t的地址
,cache_t
內部第一個元素是_bucketsAndMaybeMask
。然后把cacht_t(_bucketsAndMaybeMask) & 0x0000fffffffffffe
,就得到了buckets
的首地址。
3. 計算_cmd哈希值
eor p12, p1, p1, LSR #7
and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask
- 其中
eor
是異或,p1 = _cmd
。p12 = p1^(p1 >> 7) = _cmd^(_cmd >> 7)
。這是我們在存儲方法時,采用的encode
方法。具體實現可以查看前面提到cache
文章。這樣就得到_cmd
編碼后的值 -
LSR
表示邏輯向右偏移,p11, LSR #48
把_bucketsAndMaybeMask
偏移48位,拿到前16位,得到mask
的值 -
and
表示與,把編碼后的_cmd
&mask
,就得到了需要查詢方法對應的哈希值。
4. 遍歷查詢
- 接著就可以進行查詢過程
add p13, p10, p12, LSL #(1+PTRSHIFT)
// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
# 流程1
// do {
1: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
cmp p9, p1 // if (sel != _cmd) {
b.ne 3f // scan more
// } else {
# 流程2
2: CacheHit \Mode // hit: call or return imp
// }
# 流程3
3: cbz p9, \MissLabelDynamic // if (sel == 0) goto Miss;
cmp p13, p10 // } while (bucket >= buckets)
b.hs 1b
# 向前查詢到首位置,沒有找到合適的方法。跳轉至最后一個元素,接著查詢
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
add p13, p10, w11, UXTW #(1+PTRSHIFT)
// p13 = buckets + (mask << 1+PTRSHIFT)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
// mask = capacity - 1,開辟內存容量-1.找到最后一個bucket的位置
add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
// p13 = buckets + (mask << 1+PTRSHIFT)
// see comment about maskZeroBits
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
add p13, p10, p11, LSL #(1+PTRSHIFT)
// p13 = buckets + (mask << 1+PTRSHIFT)
#else
#error Unsupported cache mask storage for ARM64.
#endif
add p12, p10, p12, LSL #(1+PTRSHIFT)
// p12 = first probed bucket
# 流程4
// do {
4: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
cmp p9, p1 // if (sel == _cmd)
b.eq 2b // goto hit
cmp p9, #0 // } while (sel != 0 &&
ccmp p13, p12, #0, ne // bucket > first_probed)
b.hi 4b
# 沒有找到
LLookupEnd\Function:
LLookupRecover\Function:
b \MissLabelDynamic
通過下標
index
獲取對應的bucket。p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
-
獲取對應的
bucket
,然后取出imp
和sel
分別存放到p17
和p9
中,并且bucket--
。原因是方法緩存在存儲的時候,采用的是向前插入。- 流程1:比較當前
bucket
的sel
和傳入的_cmd
是否相同,- 如果不同則跳轉至流程3
- 如果相同則走流程2,
CacheHit
。
- 流程2:
CacheHit
:表示緩存命中,直接返回該方法 - 流程3:
- 首先判斷當前
sel
是否為nil
,如果為空,說明沒有找到緩存方法,跳轉至MissLabelDynamic
,走__objc_msgSend_uncached
流程。下面篇章具體講解。 - 如果當前
sel
不是我們需要找的,則去找下一個,直到找到第一個存儲位置。如果循環到第一個bucket
都沒有找到對應的方法。則跳轉至最后一個元素接著向前查找
- 首先判斷當前
- 流程4:從最后一個元素,開始向前查找
- 如果找到了,就跳轉至流程2
- 如果沒有找到,則向前查找,直到查詢到我們首次哈希計算的下標值。
- 流程1:比較當前
一直沒有找到,最后走到
MissLabelDynamic
,也就是__objc_msgSend_uncached
流程。
__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 p15 is the class to search
MethodTableLookup
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
- 里面就只有兩個方法,一個是
MethodTableLookup
,另一個是TailCallFunctionPointer
。我們先進入TailCallFunctionPointer
實現。
.macro TailCallFunctionPointer
// $0 = function pointer value
braaz $0
.endmacro
- 發現它僅僅是指返回傳入的地址,并跳轉過去。并沒有做查詢操作,所以這并不是我們需要關心的方法。那我們就查看一下
MethodTableLookup
的實現
.macro MethodTableLookup
SAVE_REGS MSGSEND
// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
// receiver and selector already in x0 and x1
mov x2, x16
mov x3, #3
bl _lookUpImpOrForward
// IMP in x0
mov x17, x0
RESTORE_REGS MSGSEND
.endmacro
- 發現該方法中,通過
_lookUpImpOrForward
來查詢到具體imp
。我們接著全局搜索發現沒有它的定義,這個時候我們去掉下劃線,就發現了結果。由于匯編函數會比c++函數多一個下劃線。
lookUpImpOrForward
NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
runtimeLock.assertUnlocked();
// 判斷類是否初始化
if (slowpath(!cls->isInitialized())) {...}
runtimeLock.lock();
// 1. 是否把類注冊到內存中
checkIsKnownClass(cls);
// 2. 初始化當前類和父類
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
// runtimeLock may have been dropped but is now locked again
runtimeLock.assertLocked();
curClass = cls;
// 3. 開始查詢方法。需要先再次查找緩存,如果沒找到在開始去類中查詢
for (unsigned attempts = unreasonableClassCount();;) {
// 判斷緩存中是否存在
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
imp = cache_getImp(curClass, sel);
if (imp) goto done_unlock;
curClass = curClass->cache.preoptFallbackClass();
#endif
} else {
// 在當前類中查找方法
method_t *meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp(false);
goto done;
}
// curClass = curClass->getSuperclass() 直到為nil走if里面的流程,不為nil走下面流程
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = forward_imp;
break;
}
}
// Halt if there is a cycle in the superclass chain.
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
// 去父類的緩存中查找
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
if (fastpath(imp)) {
// Found the method in a superclass. Cache it in this class.
goto done;
}
}
// No implementation found. Try method resolver once.
// 4. 如果沒有找到實現,調用resolveMethod_locked來去實現
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
// 5. 方法找到
done:
if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
cls = cls->cache.preoptFallbackClass();
}
#endif
log_and_fill_cache(cls, imp, sel, inst, curClass);
}
done_unlock:
runtimeLock.unlock();
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
- 該方法主要做了以下幾件事
- 判斷類是否加載到內存中
- 對應的類和元類、以及對應的父類是否初始化完畢,為接下來有可能到父類中查詢做準備
- 開始查詢方法。需要先查找緩存,如果沒找到再開始去類中查詢,如果當前類沒有,則去它的父類去查詢,如果找到方法了,則跳轉至
done
。 - 如果沒有找到
- 首次的話,系統會調用
resolveMethod_locked
給你一次機會,判斷是否有動態方法決議。 - 非首次的話,則直接返回
forward_imp
- 首次的話,系統會調用
- 方法找到
1. 判斷類是否加載到內存中
ALWAYS_INLINE
static void
checkIsKnownClass(Class cls)
{
if (slowpath(!isKnownClass(cls))) {
_objc_fatal("Attempt to use unknown class %p.", cls);
}
}
- 通過
isKnownClass
來判斷
ALWAYS_INLINE
static bool
isKnownClass(Class cls)
{
if (fastpath(objc::dataSegmentsRanges.contains(cls->data()->witness, (uintptr_t)cls))) {
return true;
}
auto &set = objc::allocatedClasses.get();
return set.find(cls) != set.end() || dataSegmentsContain(cls);
}
- 通過全局
allocatedClasses
表中去判斷,是否已經加載到內存中
2. 初始化對應類和元類
static Class
realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize)
{
runtimeLock.assertLocked();
// 判斷類是否已經實現
if (slowpath(!cls->isRealized())) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
// runtimeLock may have been dropped but is now locked again
}
// 判斷類是否初始化
if (slowpath(initialize && !cls->isInitialized())) {
cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
// runtimeLock may have been dropped but is now locked again
// 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
}
return cls;
}
- 通過
realizeClassMaybeSwiftAndLeaveLocked
去實現類,主要是按照isa
和繼承鏈去實現bits
中的data
數據。 - 通過
initializeAndLeaveLocked
去初始化類
3. 查詢方法
for (unsigned attempts = unreasonableClassCount();;) {
// 如果存在共享緩存,則先去緩存中查找
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
imp = cache_getImp(curClass, sel);
if (imp) goto done_unlock;
curClass = curClass->cache.preoptFallbackClass();
#endif
} else {
// curClass method list.
method_t *meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp(false);
goto done;
}
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = forward_imp;
break;
}
}
// Halt if there is a cycle in the superclass chain.
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
if (fastpath(imp)) {
// Found the method in a superclass. Cache it in this class.
goto done;
}
}
- 先判斷是否有共享緩存,如果存在先去緩存中查找,然后再去列表中查詢
// curClass method list.
method_t *meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp(false);
goto done;
}
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = forward_imp;
break;
}
- 先在當前類中去查詢,如果查詢到了,則直接跳轉至
done
。如果沒有查到,則設置curClass = curClass->getSuperclass()
進入到父類中。我們看一下getMethodNoSuper_nolock
是如何查詢的
getMethodNoSuper_nolock
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
auto const methods = cls->data()->methods();
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
{
method_t *m = search_method_list_inline(*mlists, sel);
if (m) return m;
}
return nil;
}
- 通過
cls
去獲取methods
列表。這個列表是一個二維數組,如果對這一塊不夠了解,可以查看這篇文章Runtime源碼解析-類中bits。然后我們去遍歷這個二維數組,通過search_method_list_inline
方法去查詢每一個一維數組中方法。
ALWAYS_INLINE static method_t *
search_method_list_inline(const method_list_t *mlist, SEL sel)
{
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->isExpectedSize();
if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) {
return findMethodInSortedMethodList(sel, mlist);
} else {
// Linear search of unsorted method list
if (auto *m = findMethodInUnsortedMethodList(sel, mlist))
return m;
}
return nil;
}
- 一般情況下,都會走
findMethodInSortedMethodList
方法
template<class getNameFunc>
ALWAYS_INLINE static method_t *
findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
{
ASSERT(list);
// 第一個方法位置
auto first = list->begin();
auto base = first;
decltype(first) probe;
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
// count = 數組的個數
// count >>= 1 等價于 count = count / 2;
for (count = list->count; count != 0; count >>= 1) {
// 獲取當前偏移值
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)getName(probe);
// 如果相等,匹配成功
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)getName((probe - 1))) {
probe--;
}
return &*probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
- 通過二分查找的方式,來查詢方法列表中的方法。如果沒有找到這返回
nil
- 如果當前類沒有找到,則把
curClass = curClass->getSuperclass()
更新為當前類的父類
// Halt if there is a cycle in the superclass chain.
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// 先去父類的緩存中查找
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
// 在緩存中找到實現
if (fastpath(imp)) {
// Found the method in a superclass. Cache it in this class.
goto done;
}
- 先去父類的緩存中查找,如果沒有找到,就進入下一次循環。去父類的方法列表中去查找
- 如果在緩存中找到,則跳轉至
done
4. 方法未找到
- 如果上面的查找流程未能找到,則說明當前類和父類確實沒有該方法,則進入動態決議過程
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
- 這方法我們再下一節具體講解
5. 方法找到
done:
if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
cls = cls->cache.preoptFallbackClass();
}
#endif
log_and_fill_cache(cls, imp, sel, inst, curClass);
}
- 如果方法找到了,我們會調用
log_and_fill_cache
方法,把它插入到類對應的緩存中
static void
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
if (slowpath(objcMsgLogEnabled && implementer)) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
cls->cache.insert(sel, imp, receiver);
}
- 該方法內部調用了
cls->cache.insert(sel, imp, receiver);
,這就我們在講類的緩存時,著重講解了如何插入的。這里我們也就明白了,方法是合適插入到緩存中的。
resolveMethod_locked
- 我們在上面
lookUpImpOrForward
方法中,去類的方法列表,以及它對應的父類直達根類去查找對應方法的實現。如果找到了就直接返回,找不到的話就會進入到resolveMethod_locked
流程。
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
runtimeLock.unlock();
// 判斷是否是元類
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
resolveInstanceMethod(inst, sel, cls);
}
else { // 如果是元類,說明調用類方法
// try [nonMetaClass resolveClassMethod:sel]
// and [cls resolveInstanceMethod:sel]
resolveClassMethod(inst, sel, cls);
// 如果沒有找到,在元類的對象方法中查找,類方法相當于元類中的對象方法
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
// 返回前面通過resolveInstanceMethod或resolveClassMethod添加的sel對應方法
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
- 根據是否是元類,調用不同的動態方法決議
對象方法動態決議
- 如果是對象方法,調用
resolveInstanceMethod
方法
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
// Resolver not implemented.
return;
}
// 調用resolveInstanceMethod方法
// 通過 objc_msgSend 發送消息 接收者是cls
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveInstanceMethod adds to self a.k.a. cls
// 去查找sel對應的實現
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
- 首先創建一個方法名為
resolveInstanceMethod
的SEL resolve_sel
。 - 直接調用
objc_msgSend
給類發送消息,從這里可以看出resolveInstanceMethod
是類方法 - 然后通過
lookUpImpOrNilTryCache
去緩存和方法列表中去查看,是否實現了resolveInstanceMethod
該方法。
IMP lookUpImpOrNilTryCache(id inst, SEL sel, Class cls, int behavior)
{
return _lookUpImpTryCache(inst, sel, cls, behavior | LOOKUP_NIL);
}
static IMP _lookUpImpTryCache(id inst, SEL sel, Class cls, int behavior)
{
runtimeLock.assertUnlocked();
if (slowpath(!cls->isInitialized())) {
// see comment in lookUpImpOrForward
return lookUpImpOrForward(inst, sel, cls, behavior);
}
// 在緩存中查找sel對應imp
IMP imp = cache_getImp(cls, sel);
if (imp != NULL) goto done;
#if CONFIG_USE_PREOPT_CACHES
if (fastpath(cls->cache.isConstantOptimizedCache(/* strict */true))) {
imp = cache_getImp(cls->cache.preoptFallbackClass(), sel);
}
#endif
// 緩存沒有查詢到imp,進入方法列表中查詢
if (slowpath(imp == NULL)) {
return lookUpImpOrForward(inst, sel, cls, behavior);
}
done:
if ((behavior & LOOKUP_NIL) && imp == (IMP)_objc_msgForward_impcache) {
return nil;
}
return imp;
}
- 先在緩存中查找,如果
imp
存在則直接跳轉done
流程 - 沒有查找到
imp
,進入方法列表進行查詢。注意,此次再進入方法列表查詢,behavior
=4
,4
&2
=0
,不會再次進入動態方法決議 -
done
流程,如果imp
=_objc_msgForward_impcache
,說明緩存中的是forward_imp
,就直接返回nil
,否者返回的imp
類方法動態決議
- 如果是類方法,則調用
resolveClassMethod
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
if (!lookUpImpOrNilTryCache(inst, @selector(resolveClassMethod:), cls)) {
// Resolver not implemented.
return;
}
// 獲取元類
Class nonmeta;
{
mutex_locker_t lock(runtimeLock);
nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
// +initialize path should have realized nonmeta already
if (!nonmeta->isRealized()) {
_objc_fatal("nonmeta class %s (%p) unexpectedly not realized",
nonmeta->nameForLogging(), nonmeta);
}
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
// 向元類發送消息
bool resolved = msg(nonmeta, @selector(resolveClassMethod:), sel);
// Cache the result (good or bad) so the resolver doesn't fire next time.
// +resolveClassMethod adds to self->ISA() a.k.a. cls
// 去元類中查找
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveClassMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
- 該流程與
resolveInstanceMethod
方法類似。 - 直接調用
objc_msgSend
給元類發送消息,可能resolveClassMethod
實現了sel
對應的imp
- 然后通過
lookUpImpOrNilTryCache
去緩存和方法列表中去查看,是否實現了resolveClassMethod
該方法。
消息轉發
- 如果沒有實現動態決議,這個時候消息會進入轉發流程。分為快速轉發和慢速轉發
快速轉發
- 快速轉發是通過
forwardingTargetForSelector
實現,簡單理解指定一個對象,讓這個對象去接收消息。 - 首先定義一個
TestMain
類和Test
類,然后在main
函數中調用test
方法。TestMain
沒有實現test
方法,Test
類實現test
方法,TestMain
類和Test
類可以不是繼承關系
- (id)forwardingTargetForSelector:(SEL)aSelector{
if (aSelector == @selector(test)) {
return [[Test alloc] init];
}
return nil;
}
- 這樣就可以讓你所指定的類去實現這個方法,如果沒有重寫該方法,說明沒有指定的類可以去實現該方法,則快速轉發也不行了。就進入到慢速轉發流程
慢速轉發
- 慢速轉發是通過
methodSignatureForSelector
和forwardInvocation
配合實現的。
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
if (aSelector == @selector(test)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:@"];
}
return [super methodSignatureForSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
[anInvocation invokeWithTarget:[[Test alloc] init]];
}
- 如果
methodSignatureForSelector
返回nil
,慢速轉發時,程序會直接崩潰。
總結
快速轉發:通過
forwardingTargetForSelector
實現,如果此時有指定的對象去接收這個消息,就會走之指定對象的查找流程,如果返回是nil
,進入慢速轉發流程慢速轉發:通過
methodSignatureForSelector
和forwardInvocation
共同實現,如果methodSignatureForSelector
返回值是nil
,慢速查找流程結束,如果有返回值forwardInvocation
的事務處理不處理都不會崩潰。
總結
- 消息發送流程總結,總共經歷了三個階段:
- 消息發送階段:負責從類及父類的緩存列表及方法列表查找方法。
- 動態解析階段:如果消息發送階段沒有找到方法,則會進入動態解析階段,負責動態的添加方法實現。
- 消息轉發階段:如果也沒有實現動態解析方法,則會進行消息轉發階段,將消息轉發給可以處理消息的接受者來處理。