建議先看下
IOS底層(十三): 消息流程之快速查找
sel
: 方法編號, 可以理解成一本書的目錄, 可通過對應(yīng)名稱找到頁碼imp
: 函數(shù)指針地址, 可以理解成書的頁碼, 方便找到具體實(shí)現(xiàn)的函數(shù)
objc_msgSend 慢速查找流程分析
之前我們接觸過, 如果方法在cache
中找到對應(yīng)的sel
, imp
則直接CacheHit緩存命中
, 如果沒有找到則會走一個(gè)__objc_msgSend_uncached
方法
看下__objc_msgSend_uncached
源碼, 其中MethodTableLookup
查詢方法列表`為核心方法
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
STATIC_ENTRY __objc_msgLookup_uncached
UNWIND __objc_msgLookup_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band p15 is the class to search
MethodTableLookup
ret
END_ENTRY __objc_msgLookup_uncached
接下來看下MethodTableLookup
這個(gè)源碼, 其中lookUpImpOrForward
為核心代碼
.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
接下來我們驗(yàn)證下慢速查找, 是否會按照我們想的走lookUpImpOrForward
這個(gè)方法
定義一個(gè)sayHello實(shí)例方法, 并加個(gè)斷點(diǎn)
斷到后, 開啟匯編模式(Debug → Debug worlflow → Always show Disassembly
), 可看到走了objc_msgsend
方法
objc_msgsend
處加斷點(diǎn), control + stepinto
, 進(jìn)入objc_msgsend
0x7FFFFFFFFFF8
其中這里是個(gè)掩碼, 這塊就是操作緩存, 發(fā)現(xiàn)沒有找到, 排除一些objc_debug_taggedpointer_classes
影響, 往下走可看到走了objc_msgsend_uncached
方法
同理斷點(diǎn)objc_msgsend_uncached
, control + stepinto
進(jìn)入, 可看到走了lookUpImpOrForward
方法
lookUpImpOrForward
看下消息轉(zhuǎn)發(fā)的核心lookUpImpOrForward
源碼, (lookUpImpOrForward
涉及內(nèi)容比較多, 這里我分2篇文章進(jìn)行描述)
// 消息轉(zhuǎn)發(fā)核心方法
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
// 源碼分析見: 詳細(xì)分析一: _objc_msgForward_impcach
// 這里首先定義一個(gè)forward_imp,
// 后面當(dāng)前類鏈, 父類鏈都找不到我們要找的imp時(shí)候, 會令 imp = forward_imp
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
// 初始定義個(gè)imp設(shè)置為 null
IMP imp = nil;
// 定義個(gè)變量類 curClass, 后面會給賦值
Class curClass;
runtimeLock.assertUnlocked();
// 快速查找, 判斷是否有 +new or +alloc, or +self 初始化或者實(shí)現(xiàn)
if (slowpath(!cls->isInitialized())) {
// The first message sent to a class is often +new or +alloc, or +self
// which goes through objc_opt_* or various optimized entry points.
//
// However, the class isn't realized/initialized yet at this point,
// and the optimized entry points fall down through objc_msgSend,
// which ends up here.
//
// We really want to avoid caching these, as it can cause IMP caches
// to be made with a single entry forever.
//
// Note that this check is racy as several threads might try to
// message a given class for the first time at the same time,
// in which case we might cache anyway.
// C語言中的 |= 意思為:按位或后賦值
// 例子:x = 0x02; x |= 0x01;
// 按位或的結(jié)果為:0x03 等同于0011
// LOOKUP_NOCACHE = 8,
behavior |= LOOKUP_NOCACHE;
}
// 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();
// We don't want people to be able to craft a binary blob that looks like
// a class but really isn't one and do a CFI attack.
//
// To make these harder we want to make sure this is a class that was
// either built into the binary or legitimately registered through
// objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair.
// 判斷當(dāng)前類是否是已經(jīng)被認(rèn)可(已知)的類,即已經(jīng)加載的類
// 源碼分析見: 詳細(xì)分析二: checkIsKnownClass
checkIsKnownClass(cls);
// 判斷當(dāng)前類是否實(shí)現(xiàn)/初始化, 如果沒有, 做一個(gè)初始化方法
// 同時(shí)底層還做了遞歸實(shí)現(xiàn), 把整個(gè) 繼承鏈 都確定下來(父類, 元類的都有)
// supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
// metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
// 源碼分析見: 詳細(xì)分析三: realizeAndInitializeIfNeeded_locked
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
// runtimeLock may have been dropped but is now locked again
runtimeLock.assertLocked();
// 初始化/實(shí)現(xiàn) 之后令 curClass 等于 cls
curClass = cls;
// 下面比較重點(diǎn)以及核心
// The code used to lookup the class's cache again right after
// we take the lock but for the vast majority of the cases
// evidence shows this is a miss most of the time, hence a time loss.
//
// The only codepath calling into this without having performed some
// kind of cache lookup is class_getInstanceMethod().
for (unsigned attempts = unreasonableClassCount();;) {
// inline bool isConstantOptimizedCache(bool strict = false, uintptr_t empty_addr = 0) const { return false; }
// 快速查找锌畸,如果找到則直接返回imp
// 目的:防止多線程操作時(shí)姐扮,剛好調(diào)用函數(shù)谣辞,此時(shí)緩存進(jìn)來了
// 判斷當(dāng)前類是否有緩存優(yōu)化, 其實(shí)可以理解成是否以及配置了緩存
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
// 如果當(dāng)前類配置了緩存, 直接走cache_getImp方法根據(jù)sel獲取imp
imp = cache_getImp(curClass, sel);
if (imp) goto done_unlock;
curClass = curClass->cache.preoptFallbackClass();
#endif
} else {
// 緩存沒有方法
// curClass method list.
// 做個(gè)比喻: 如果我自己可以就不麻煩爸爸了, 去自己類的方法列表里面去找(查找class → data)
// 根據(jù)sel查找自己的 method list(采用二分查找算法), 如果有, 則返回imp
// 源碼分析見: 詳細(xì)分析四: getMethodNoSuper_nolock
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
// 如果找到了就走后面的done 方法
// done:
// if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
//#if CONFIG_USE_PREOPT_CACHES
// while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
// cls = cls->cache.preoptFallbackClass();
// }
imp = meth->imp(false);
goto done;
}
// 這里面令curClass 為其父類, 如果父類也沒有令 imp = forward_imp
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = forward_imp; // forward就是那個(gè)報(bào)錯(cuò)方法
break;
}
}
// Halt if there is a cycle in the superclass chain.
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
// 自己方法沒有找到去父類方法查找, 所以之前要確定繼承鏈, 方便父類遞歸查找繼續(xù)調(diào)用lookup 方法
// returns:
// - the cached IMP when one is found
// - nil if there's no cached value and the cache is dynamic
// - `value_on_constant_cache_miss` if there's no cached value and the cache is preoptimized
// extern "C" IMP cache_getImp(Class cls, SEL sel, IMP value_on_constant_cache_miss = nil);
// 父類查找緩存
// 源碼分析見: 詳細(xì)分析五: cache_getImp
// 依次查找父類的緩存是否有指定方法
imp = cache_getImp(curClass, sel);
// 如果還是沒有找到, 走下面2個(gè)判斷
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.
// 當(dāng)imp = forward_imp時(shí)(即父類也沒查找到), 會走這里的return方法
// RESOLVER就是動態(tài)方法決議
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
// 如果找到了imp 直接存入緩存剖煌, 方便下次快速查找
// 底層關(guān)鍵代碼 cls->cache.insert(sel, imp, receiver);
// objc_msgsend → 先查找緩存 → 緩存中沒有再慢速查找 → 二分查找自己 → 找到存入緩存 → 下次objc_msgsend → 查找緩存
if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
cls = cls->cache.preoptFallbackClass();
}
#endif
// 二分查找之后, 存入緩存, 方便之后的快速調(diào)用
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;
}
詳細(xì)分析一: _objc_msgForward_impcache
_objc_msgForward_impcache 源碼實(shí)現(xiàn)
/********************************************************************
*
* id _objc_msgForward(id self, SEL _cmd,...);
*
* _objc_msgForward is the externally-callable
* function returned by things like method_getImplementation().
* _objc_msgForward_impcache is the function pointer actually stored in
* method caches.
*
********************************************************************/
這里需要在匯編查找
// 1. 進(jìn)入__objc_msgForward_impcache 之后發(fā)現(xiàn)走 __objc_msgForward
// 2. 進(jìn)入__objc_msgForward 之后走 __objc_forward_handler
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
// 關(guān)鍵代碼__objc_forward_handler
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
/***********************************************************************
* objc_setForwardHandler
**********************************************************************/
// _objc_forward_handler 方法
#if !__OBJC2__
// Default forward handler (nil) goes to forward:: dispatch.
void *_objc_forward_handler = nil;
void *_objc_forward_stret_handler = nil;
#else
// Default forward handler halts the process.
__attribute__((noreturn, cold)) void
objc_defaultForwardHandler(id self, SEL sel)
{
// 核心源碼部分
// 可看到每次imp找不到時(shí)候必然會走這里, 報(bào)這個(gè)錯(cuò)誤(就是我們常見的方法找不到錯(cuò)誤)
// 同時(shí)看到這里面"+", "-"是蘋果人為給寫的, 方法底層根本不存在什么類方法, 實(shí)例方法
// 類方法對于元類來說就是實(shí)例方法, 實(shí)例方法對于類來說也是實(shí)例方法
_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);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
這里普及個(gè)查詢源碼小知識點(diǎn)
-
C/C++
中調(diào)用匯編
, 去查找匯編
時(shí),C/C++
調(diào)用的方法需要多加一個(gè)下劃線 -
匯編
中調(diào)用C/C++
方法時(shí), 去查找C/C++
方法, 需要將匯編
調(diào)用的方法去掉一個(gè)下劃線
詳細(xì)分析二: checkIsKnownClass
// 判斷當(dāng)前的類是否是被認(rèn)可的類
// 判斷方法列表, 屬性列表等是否已緩存形式寫入內(nèi)存或者加載到已知類中
// 查到才能有相應(yīng)緩存結(jié)構(gòu)
static void
checkIsKnownClass(Class cls)
{
if (slowpath(!isKnownClass(cls))) {
_objc_fatal("Attempt to use unknown class %p.", cls);
}
}
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);
}
詳細(xì)分析三: realizeAndInitializeIfNeeded_locked
// 如果尚未實(shí)現(xiàn),則實(shí)現(xiàn)給定的類蔗喂;如果尚未初始化罕容,則初始化該類备恤。
/***********************************************************************
* realizeAndInitializeIfNeeded_locked
* Realize the given class if not already realized, and initialize it if
* not already initialized.
* inst is an instance of cls or a subclass, or nil if none is known.
* cls is the class to initialize and realize.
* initializer is true to initialize the class, false to skip initialization.
**********************************************************************/
static Class
realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize)
{
runtimeLock.assertLocked();
// 如果類存在,沒有實(shí)現(xiàn)锦秒,需要先實(shí)現(xiàn)露泊,此時(shí)的目的是為了確定父類鏈,方法后續(xù)的循環(huán)
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;
}
按順序看下旅择, 類沒有實(shí)現(xiàn)會走這個(gè)方法realizeClassMaybeSwiftAndLeaveLocked
先看下realizeClassMaybeSwiftAndLeaveLocked
源碼
static Class
realizeClassMaybeSwiftAndLeaveLocked(Class cls, mutex_t& lock)
{
return realizeClassMaybeSwiftMaybeRelock(cls, lock, true);
}
/***********************************************************************
* realizeClassMaybeSwift (MaybeRelock / AndUnlock / AndLeaveLocked)
* Realize a class that might be a Swift class.
* Returns the real class structure for the class.
* Locking:
* runtimeLock must be held on entry
* runtimeLock may be dropped during execution
* ...AndUnlock function leaves runtimeLock unlocked on exit
* ...AndLeaveLocked re-acquires runtimeLock if it was dropped
* This complication avoids repeated lock transitions in some cases.
**********************************************************************/
static Class
realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked)
{
lock.assertLocked();
if (!cls->isSwiftStable_ButAllowLegacyForNow()) {
// Non-Swift class. Realize it now with the lock still held.
// fixme wrong in the future for objc subclasses of swift classes
// 重點(diǎn): 實(shí)現(xiàn)當(dāng)前的類方法
realizeClassWithoutSwift(cls, nil);
if (!leaveLocked) lock.unlock();
} else {
// Swift class. We need to drop locks and call the Swift
// runtime to initialize it.
lock.unlock();
cls = realizeSwiftClass(cls);
ASSERT(cls->isRealized()); // callback must have provoked realization
if (leaveLocked) lock.lock();
}
return cls;
}
/***********************************************************************
* realizeClassWithoutSwift
* Performs first-time initialization on class cls,
* including allocating its read-write data.
* Does not perform any Swift-side initialization.
* Returns the real class structure for the class.
* Locking: runtimeLock must be write-locked by the caller
**********************************************************************/
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
runtimeLock.assertLocked();
class_rw_t *rw;
Class supercls;
Class metacls;
if (!cls) return nil;
if (cls->isRealized()) {
validateAlreadyRealizedClass(cls);
return cls;
}
ASSERT(cls == remapClass(cls));
// fixme verify class is not in an un-dlopened part of the shared cache?
// 拿到class中的data信息
// 其中ro 是clean memory (如果文件只讀, 那么這部分內(nèi)存就屬于 clean memory)
// 其中rw 是dirty memory
// 普及些小知識點(diǎn)
// clean memory 指的是能被重新創(chuàng)建的內(nèi)存惭笑,它主要包含這幾類:
// 1.app 的二進(jìn)制可執(zhí)行文件
// 2.framework 中的 _DATA_CONST 段
// 3.文件映射的內(nèi)存
// 4.未寫入數(shù)據(jù)的內(nèi)存
// 內(nèi)存映射的文件指的是當(dāng) app 訪問一個(gè)文件時(shí),系統(tǒng)會將文件映射加載到內(nèi)存中生真,如果文件只讀沉噩,那么這部分內(nèi)存就屬于 clean memory。
// 另外需要注意的是, framework 中 _DATA_CONST 并不絕對屬于 clean memory, 當(dāng) app 使用到 framework 時(shí), 就會變成 dirty memory柱蟀。
// 所有不屬于 clean memory 的內(nèi)存都是 dirty memory川蒙。
// dirty memory這部分內(nèi)存并不能被系統(tǒng)重新創(chuàng)建, 所以 dirty memory 會始終占據(jù)物理內(nèi)存, 直到物理內(nèi)存不夠用之后,系統(tǒng)便會開始清理长已。
auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
// 賦值 rw畜眨, ro 即給 dirty memory 賦值
rw = cls->data();
ro = cls->data()->ro();
ASSERT(!isMeta);
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// Normal class. Allocate writeable class data.
rw = objc::zalloc<class_rw_t>();
rw->set_ro(ro);
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
cls->setData(rw);
}
cls->cache.initializeToEmptyOrPreoptimizedInDisguise();
#if FAST_CACHE_META
if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif
// Choose an index for this class.
// Sets cls->instancesRequireRawIsa if indexes no more indexes are available
cls->chooseClassArrayIndex();
if (PrintConnecting) {
_objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
cls->nameForLogging(), isMeta ? " (meta)" : "",
(void*)cls, ro, cls->classArrayIndex(),
cls->isSwiftStable() ? "(swift)" : "",
cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
}
// Realize superclass and metaclass, if they aren't already.
// This needs to be done after RW_REALIZED is set above, for root classes.
// This needs to be done after class index is chosen, for root metaclasses.
// This assumes that none of those classes have Swift contents,
// or that Swift's initializers have already been called.
// fixme that assumption will be wrong if we add support
// for ObjC subclasses of Swift classes.
// 遞歸實(shí)現(xiàn), 把整個(gè)繼承鏈 繼承下來(父類, 元類的)
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
#if SUPPORT_NONPOINTER_ISA
if (isMeta) {
// Metaclasses do not need any features from non pointer ISA
// This allows for a faspath for classes in objc_retain/objc_release.
cls->setInstancesRequireRawIsa();
} else {
// Disable non-pointer isa for some classes and/or platforms.
// Set instancesRequireRawIsa.
bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
bool rawIsaIsInherited = false;
static bool hackedDispatch = false;
if (DisableNonpointerIsa) {
// Non-pointer isa disabled by environment or app SDK version
instancesRequireRawIsa = true;
}
else if (!hackedDispatch && 0 == strcmp(ro->getName(), "OS_object"))
{
// hack for libdispatch et al - isa also acts as vtable pointer
hackedDispatch = true;
instancesRequireRawIsa = true;
}
else if (supercls && supercls->getSuperclass() &&
supercls->instancesRequireRawIsa())
{
// This is also propagated by addSubclass()
// but nonpointer isa setup needs it earlier.
// Special case: instancesRequireRawIsa does not propagate
// from root class to root metaclass
instancesRequireRawIsa = true;
rawIsaIsInherited = true;
}
if (instancesRequireRawIsa) {
cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
}
}
// SUPPORT_NONPOINTER_ISA
#endif
// Update superclass and metaclass in case of remapping
// 關(guān)聯(lián)父類, 子類
// 此時(shí)class是雙向鏈表結(jié)構(gòu), 父子關(guān)系都確定
cls->setSuperclass(supercls); // 父
cls->initClassIsa(metacls); // 子
// Reconcile instance variable offsets / layout.
// This may reallocate class_ro_t, updating our ro variable.
if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro);
// Set fastInstanceSize if it wasn't set already.
cls->setInstanceSize(ro->instanceSize);
// Copy some flags from ro to rw
if (ro->flags & RO_HAS_CXX_STRUCTORS) {
cls->setHasCxxDtor();
if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
cls->setHasCxxCtor();
}
}
// Propagate the associated objects forbidden flag from ro or from
// the superclass.
if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
(supercls && supercls->forbidsAssociatedObjects()))
{
rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
}
// Connect this class to its superclass's subclass lists
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
// Attach categories
methodizeClass(cls, previously);
return cls;
}
// methodizeClass 這個(gè)方法主要是, 把所有的cls中所有的 method list, protocol list, and property list. 貼到rwe 中
/***********************************************************************
* methodizeClass
* Fixes up cls's method list, protocol list, and property list.
* Attaches any outstanding categories.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void methodizeClass(Class cls, Class previously)
{
runtimeLock.assertLocked();
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro();
auto rwe = rw->ext();
// Methodizing for the first time
if (PrintConnecting) {
_objc_inform("CLASS: methodizing class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
if (rwe) rwe->methods.attachLists(&list, 1);
}
property_list_t *proplist = ro->baseProperties;
if (rwe && proplist) {
rwe->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (rwe && protolist) {
rwe->protocols.attachLists(&protolist, 1);
}
// Root classes get bonus method implementations if they don't have
// them already. These apply before category replacements.
if (cls->isRootMetaclass()) {
// root metaclass
addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
}
// Attach categories.
if (previously) {
if (isMeta) {
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_METACLASS);
} else {
// When a class relocates, categories with class methods
// may be registered on the class itself rather than on
// the metaclass. Tell attachToClass to look for those.
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_CLASS_AND_METACLASS);
}
}
objc::unattachedCategories.attachToClass(cls, cls,
isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
#if DEBUG
// Debug: sanity-check all SELs; log method list contents
for (const auto& meth : rw->methods()) {
if (PrintConnecting) {
_objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-',
cls->nameForLogging(), sel_getName(meth.name()));
}
ASSERT(sel_registerName(sel_getName(meth.name())) == meth.name());
}
#endif
}
接下來看下是當(dāng)前類沒有初始話痰哨, 走的方法initializeAndLeaveLocked
, 主要就是一層層初始化所有類胶果, 父類, 元類......
// Locking: caller must hold runtimeLock; this may drop and re-acquire it
static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock)
{
return initializeAndMaybeRelock(cls, obj, lock, true);
}
/***********************************************************************
* class_initialize. Send the '+initialize' message on demand to any
* uninitialized class. Force initialization of superclasses first.
* inst is an instance of cls, or nil. Non-nil is better for performance.
* Returns the class pointer. If the class was unrealized then
* it may be reallocated.
* Locking:
* runtimeLock must be held by the caller
* This function may drop the lock.
* On exit the lock is re-acquired or dropped as requested by leaveLocked.
**********************************************************************/
static Class initializeAndMaybeRelock(Class cls, id inst,
mutex_t& lock, bool leaveLocked)
{
lock.assertLocked();
ASSERT(cls->isRealized());
if (cls->isInitialized()) {
if (!leaveLocked) lock.unlock();
return cls;
}
// Find the non-meta class for cls, if it is not already one.
// The +initialize message is sent to the non-meta class object.
Class nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
// Realize the non-meta class if necessary.
if (nonmeta->isRealized()) {
// nonmeta is cls, which was already realized
// OR nonmeta is distinct, but is already realized
// - nothing else to do
lock.unlock();
} else {
nonmeta = realizeClassMaybeSwiftAndUnlock(nonmeta, lock);
// runtimeLock is now unlocked
// fixme Swift can't relocate the class today,
// but someday it will:
cls = object_getClass(nonmeta);
}
// runtimeLock is now unlocked, for +initialize dispatch
ASSERT(nonmeta->isRealized());
// 初始化元類
initializeNonMetaClass(nonmeta);
if (leaveLocked) runtimeLock.lock();
return cls;
}
/***********************************************************************
* class_initialize. Send the '+initialize' message on demand to any
* uninitialized class. Force initialization of superclasses first.
**********************************************************************/
void initializeNonMetaClass(Class cls)
{
ASSERT(!cls->isMetaClass());
Class supercls;
bool reallyInitialize = NO;
// Make sure super is done initializing BEFORE beginning to initialize cls.
// See note about deadlock above.
supercls = cls->getSuperclass();
if (supercls && !supercls->isInitialized()) {
initializeNonMetaClass(supercls);
}
// Try to atomically set CLS_INITIALIZING.
SmallVector<_objc_willInitializeClassCallback, 1> localWillInitializeFuncs;
{
monitor_locker_t lock(classInitLock);
if (!cls->isInitialized() && !cls->isInitializing()) {
cls->setInitializing();
reallyInitialize = YES;
// Grab a copy of the will-initialize funcs with the lock held.
localWillInitializeFuncs.initFrom(willInitializeFuncs);
}
}
if (reallyInitialize) {
// We successfully set the CLS_INITIALIZING bit. Initialize the class.
// Record that we're initializing this class so we can message it.
_setThisThreadIsInitializingClass(cls);
if (MultithreadedForkChild) {
// LOL JK we don't really call +initialize methods after fork().
performForkChildInitialize(cls, supercls);
return;
}
for (auto callback : localWillInitializeFuncs)
callback.f(callback.context, cls);
// Send the +initialize message.
// Note that +initialize is sent to the superclass (again) if
// this class doesn't implement +initialize. 2157218
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: calling +[%s initialize]",
objc_thread_self(), cls->nameForLogging());
}
// Exceptions: A +initialize call that throws an exception
// is deemed to be a complete and successful +initialize.
//
// Only __OBJC2__ adds these handlers. !__OBJC2__ has a
// bootstrapping problem of this versus CF's call to
// objc_exception_set_functions().
#if __OBJC2__
@try
#endif
{
callInitialize(cls);
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
objc_thread_self(), cls->nameForLogging());
}
}
#if __OBJC2__
@catch (...) {
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: +[%s initialize] "
"threw an exception",
objc_thread_self(), cls->nameForLogging());
}
@throw;
}
@finally
#endif
{
// Done initializing.
lockAndFinishInitializing(cls, supercls);
}
return;
}
else if (cls->isInitializing()) {
// We couldn't set INITIALIZING because INITIALIZING was already set.
// If this thread set it earlier, continue normally.
// If some other thread set it, block until initialize is done.
// It's ok if INITIALIZING changes to INITIALIZED while we're here,
// because we safely check for INITIALIZED inside the lock
// before blocking.
if (_thisThreadIsInitializingClass(cls)) {
return;
} else if (!MultithreadedForkChild) {
waitForInitializeToComplete(cls);
return;
} else {
// We're on the child side of fork(), facing a class that
// was initializing by some other thread when fork() was called.
_setThisThreadIsInitializingClass(cls);
performForkChildInitialize(cls, supercls);
}
}
else if (cls->isInitialized()) {
// Set CLS_INITIALIZING failed because someone else already
// initialized the class. Continue normally.
// NOTE this check must come AFTER the ISINITIALIZING case.
// Otherwise: Another thread is initializing this class. ISINITIALIZED
// is false. Skip this clause. Then the other thread finishes
// initialization and sets INITIALIZING=no and INITIALIZED=yes.
// Skip the ISINITIALIZING clause. Die horribly.
return;
}
else {
// We shouldn't be here.
_objc_fatal("thread-safe class init in objc runtime is buggy!");
}
}
// 向類里面的 initialize 發(fā)送消息, 就是自動調(diào)用類中 initialize方法斤斧, 比如類中自定義initialize, 調(diào)用時(shí)候回打印123霎烙, 其實(shí)跟 load 方法類似撬讽, 系統(tǒng)默認(rèn)為我們調(diào)用
// + (void)initialize {
// NSLog(@"123");
// }
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
asm("");
}
詳細(xì)分析四: getMethodNoSuper_nolock
這個(gè)方法其實(shí)就是cls
→ data
→ methods
取到methods
蕊连, 然后二分查找,核心是方法是findMethodInSortedMethodList
, 源碼下面也有2個(gè)例子便于理解游昼。
/***********************************************************************
* getMethodNoSuper_nolock
* fixme
* Locking: runtimeLock must be read- or write-locked by the caller
**********************************************************************/
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
ASSERT(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
auto const methods = cls->data()->methods();
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
{
// <rdar://problem/46904873> getMethodNoSuper_nolock is the hottest
// caller of search_method_list, inlining it turns
// getMethodNoSuper_nolock into a frame-less function and eliminates
// any store from this codepath.
method_t *m = search_method_list_inline(*mlists, sel);
if (m) return m;
}
return nil;
}
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;
}
#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;
}
// 二分查找核心方法
/***********************************************************************
* search_method_list_inline
**********************************************************************/
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;
// base 為初始下標(biāo), count為個(gè)數(shù), probe是中間二分下標(biāo) 不斷的遞歸二分實(shí)現(xiàn)找到值
// 下邊有例子
for (count = list->count; count != 0; count >>= 1) {
//從首地址+下標(biāo) --> 移動到中間位置(count >> 1 右移1位即 count/2 = 4)
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)getName(probe);
//如果查找的key的keyvalue剛好等于中間位置(probe)的probeValue甘苍,則直接返回中間位置
if (keyValue == probeValue) {
// `probe` is a match.
// Rewind looking for the *first* occurrence of this value.
// This is required for correct category overrides.
// 這里是做的while是, 排除分類重名
// 判斷 當(dāng)前我已經(jīng)找到的方法, 發(fā)現(xiàn)前面有和他一樣的方法, 即分類
while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
// 分類永遠(yuǎn)在主類前面
// 排除分類重名方法(由于方法的存儲是先存儲類方法,在存儲分類---按照先進(jìn)后出的原則烘豌,分類方法最先出载庭,而我們要取的類方法,所以需要先排除分類方法)
// 如果是兩個(gè)分類廊佩,就看誰先進(jìn)行加載
probe--;
}
return &*probe;
}
// 不匹配, 如果keyValue 大于 probeValue
// base = probe + 1 就往probe即中間位置的右邊查找
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
二分查找例子:
例子1: 一個(gè)類里面有8個(gè)方法: 01, 02, 03, 04, 05, 06, 07, 08
base = first = 01, count = 8, 比如我們想找第2個(gè), 進(jìn)行循環(huán)
-
第一次循環(huán)
probe = 01 + 8 >>1 = 01 右移 4位 = 05 ( count >> 1右移1, 8 (1000)右移1位變?yōu)? (0100) )- 此時(shí)02 < 05
- keyValue == probeValue: 不滿足
- keyvalue > probeValue: 不滿足
-
第二次循環(huán)
- base 不變還為01, 但count, 進(jìn)行了一次判斷循環(huán), 現(xiàn)在為4
probe = 01 + 4>>1 = 03 - 此時(shí)02 < 03
- keyValue == probeValue: 不滿足
- keyvalue > probeValue: 不滿足
- base 不變還為01, 但count, 進(jìn)行了一次判斷循環(huán), 現(xiàn)在為4
-
第三次循環(huán)
- base 不變還為01, 但count, 進(jìn)行了一次判斷循環(huán), 現(xiàn)在為4
probe = 01 + 2>>1 = 02 - 此時(shí)02 == 02
- keyValue == probeValue: 滿足, 判斷下是否有分類重名, 已找到結(jié)束循環(huán)返回
- base 不變還為01, 但count, 進(jìn)行了一次判斷循環(huán), 現(xiàn)在為4
例子2: 還是之前例子, 我們想找第7個(gè)
base = first = 01, count = 8, 比如我們想找第2個(gè), 進(jìn)行循環(huán)
-
第一次循環(huán)
probe = 01 + 8 >>1 = 01 右移 4位 = 05 ( count >> 1右移1, 8 (1000)右移1位變?yōu)? (0100) )- 此時(shí)07 > 05
- keyValue == probeValue: 不滿足
- keyvalue > probeValue: 滿足, base = 05 + 1 = 06, count = 7
-
第二次循環(huán)
- base 不變還為01, 但count = 7, 右移1位, 變3
probe = 06 + 3>>1 = 067 - 此時(shí)07 == 07
- keyValue == probeValue: 滿足判斷下是否有分類重名, 已找到結(jié)束循環(huán)返回
- base 不變還為01, 但count = 7, 右移1位, 變3
詳細(xì)分析五: cache_getImp
// cache_getImp方法是通過匯編_cache_getImp實(shí)現(xiàn)
// 如果父類緩存中找到了方法實(shí)現(xiàn)囚聚,則跳轉(zhuǎn)至CacheHit即命中,則直接返回imp
// 如果在父類緩存中标锄,沒有找到方法實(shí)現(xiàn)顽铸,則跳轉(zhuǎn)Miss
// 這里普及個(gè)知識點(diǎn), 跟緩存相關(guān)的就是匯編料皇, 因?yàn)閰R編比C/C++快
STATIC_ENTRY _cache_getImp
GetClassFromIsa_p16 p0, 0
// 去執(zhí)行 CacheLookup 方法, 里面?zhèn)?個(gè)參數(shù)
CacheLookup GETIMP, _cache_getImp, LGetImpMissDynamic, LGetImpMissConstant
LGetImpMissDynamic:
mov p0, #0
ret
LGetImpMissConstant:
mov p0, p2
ret
END_ENTRY _cache_getImp
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
//
// Restart protocol:
//
// As soon as we're past the LLookupStart\Function label we may have
// loaded an invalid cache pointer or mask.
//
// When task_restartable_ranges_synchronize() is called,
// (or when a signal hits us) before we're past LLookupEnd\Function,
// then our PC will be reset to LLookupRecover\Function which forcefully
// jumps to the cache-miss codepath which have the following
// requirements:
//
// GETIMP:
// The cache-miss is just returning NULL (setting x0 to 0)
//
// NORMAL and LOOKUP:
// - x0 contains the receiver
// - x1 contains the selector
// - x16 contains the isa
// - other registers are set as per calling conventions
//
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
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
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
#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
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
// wrap-around:
// p10 = first bucket
// p11 = mask (and maybe other bits on LP64)
// p12 = _cmd & mask
//
// A full cache can happen with CACHE_ALLOW_FULL_UTILIZATION.
// So stop when we circle back to the first probed bucket
// rather than when hitting the first bucket again.
//
// Note that we might probe the initial bucket twice
// when the first probed slot is the last entry.
#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
LLookupEnd\Function:
LLookupRecover\Function:
b \MissLabelDynamic
#if CONFIG_USE_PREOPT_CACHES
#if CACHE_MASK_STORAGE != CACHE_MASK_STORAGE_HIGH_16
#error config unsupported
#endif
LLookupPreopt\Function:
#if __has_feature(ptrauth_calls)
and p10, p11, #0x007ffffffffffffe // p10 = buckets
autdb x10, x16 // auth as early as possible
#endif
// x12 = (_cmd - first_shared_cache_sel)
adrp x9, _MagicSelRef@PAGE
ldr p9, [x9, _MagicSelRef@PAGEOFF]
sub p12, p1, p9
// w9 = ((_cmd - first_shared_cache_sel) >> hash_shift & hash_mask)
#if __has_feature(ptrauth_calls)
// bits 63..60 of x11 are the number of bits in hash_mask
// bits 59..55 of x11 is hash_shift
lsr x17, x11, #55 // w17 = (hash_shift, ...)
lsr w9, w12, w17 // >>= shift
lsr x17, x11, #60 // w17 = mask_bits
mov x11, #0x7fff
lsr x11, x11, x17 // p11 = mask (0x7fff >> mask_bits)
and x9, x9, x11 // &= mask
#else
// bits 63..53 of x11 is hash_mask
// bits 52..48 of x11 is hash_shift
lsr x17, x11, #48 // w17 = (hash_shift, hash_mask)
lsr w9, w12, w17 // >>= shift
and x9, x9, x11, LSR #53 // &= mask
#endif
ldr x17, [x10, x9, LSL #3] // x17 == sel_offs | (imp_offs << 32)
cmp x12, w17, uxtw
// 這段是關(guān)鍵, 當(dāng)Mode == GETIMP,返回cache miss, 即MissLabelConstant
.if \Mode == GETIMP
b.ne \MissLabelConstant // cache miss
sub x0, x16, x17, LSR #32 // imp = isa - imp_offs
SignAsImp x0
ret
.else
b.ne 5f // cache miss
sub x17, x16, x17, LSR #32 // imp = isa - imp_offs
.if \Mode == NORMAL
br x17
.elseif \Mode == LOOKUP
orr x16, x16, #3 // for instrumentation, note that we hit a constant cache
SignAsImp x17
ret
.else
.abort unhandled mode \Mode
.endif
5: ldursw x9, [x10, #-8] // offset -8 is the fallback offset
add x16, x16, x9 // compute the fallback isa
b LLookupStart\Function // lookup again with a new isa
.endif
#endif // CONFIG_USE_PREOPT_CACHES
.endmacro
- 進(jìn)入
_cache_getImp
, 可看到內(nèi)部執(zhí)行調(diào)用CacheLookup 方法
, 里面?zhèn)?個(gè)參數(shù), 需要留意第一個(gè)參數(shù)GETIMP
- 關(guān)鍵代碼
.if \Mode == GETIMP b.ne \MissLabelConstant // cache miss
, 當(dāng)Mode == GETIMP, 返回MissLabelConstant 即返回走 LGetImpMissConstant 方法 -
LGetImpMissConstant: mov p0, p2 ret
, 這里可看到令p0 = p2(即將p2存到寄存器p0), ret = return返回 -
_cache_getImp
無遞歸, 只是依次查找父類的緩存是否有指定方法, 直到NSObject
父類nil
總結(jié)
慢速查找流程
針對于快速查找
沒有找到走慢速查找
流程
對于
實(shí)例方法
谓松,即在類
中查找,對應(yīng)慢速查找
的父類鏈
為:類
→類
→根類
→nil
對于
類方法
践剂,即在元類
中查找鬼譬,對應(yīng)慢速查找
的父類鏈
為:元類
→根元類
→根類
→nil
如果
慢速查找
也沒有找到,則嘗試動態(tài)方法決議
如果
動態(tài)方法決議
仍然沒有找到逊脯,則進(jìn)行消息轉(zhuǎn)發(fā)
如果都沒有找到就
imp = forward_imp
走經(jīng)典carsh方法
+[SAStudent sayGunDan]: unrecognized selector sent to class XXX
Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: '+[SAStudent sayGunDan]: unrecognized selector sent to class XXX'