建议先看下
IOS底层(十三): 消息流程之快速查找
-
sel
: 方法编号, 可以理解成一本书的目录, 可通过对应名称找到页码 -
imp
: 函数指针地址, 可以理解成书的页码, 方便找到具体实现的函数
objc_msgSend 慢速查找流程分析
之前我们接触过, 如果方法在cache
中找到对应的sel
, imp
则直接CacheHit缓存命中
, 如果没有找到则会走一个__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
这个源码, 其中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
接下来我们验证下慢速查找, 是否会按照我们想的走lookUpImpOrForward
这个方法
定义一个sayHello实例方法, 并加个断点
main断到后, 开启汇编模式(Debug → Debug worlflow → Always show Disassembly
), 可看到走了objc_msgsend
方法
objc_msgsend
处加断点, control + stepinto
, 进入objc_msgsend
0x7FFFFFFFFFF8
其中这里是个掩码, 这块就是操作缓存, 发现没有找到, 排除一些objc_debug_taggedpointer_classes
影响, 往下走可看到走了objc_msgsend_uncached
方法
同理断点objc_msgsend_uncached
, control + stepinto
进入, 可看到走了lookUpImpOrForward
方法
lookUpImpOrForward
看下消息转发的核心lookUpImpOrForward
源码, (lookUpImpOrForward
涉及内容比较多, 这里我分2篇文章进行描述)
// 消息转发核心方法
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
// 源码分析见: 详细分析一: _objc_msgForward_impcach
// 这里首先定义一个forward_imp,
// 后面当前类链, 父类链都找不到我们要找的imp时候, 会令 imp = forward_imp
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
// 初始定义个imp设置为 null
IMP imp = nil;
// 定义个变量类 curClass, 后面会给赋值
Class curClass;
runtimeLock.assertUnlocked();
// 快速查找, 判断是否有 +new or +alloc, or +self 初始化或者实现
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;
// 按位或的结果为: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.
// 判断当前类是否是已经被认可(已知)的类,即已经加载的类
// 源码分析见: 详细分析二: checkIsKnownClass
checkIsKnownClass(cls);
// 判断当前类是否实现/初始化, 如果没有, 做一个初始化方法
// 同时底层还做了递归实现, 把整个 继承链 都确定下来(父类, 元类的都有)
// supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
// metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
// 源码分析见: 详细分析三: realizeAndInitializeIfNeeded_locked
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
// runtimeLock may have been dropped but is now locked again
runtimeLock.assertLocked();
// 初始化/实现 之后令 curClass 等于 cls
curClass = cls;
// 下面比较重点以及核心
// 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
// 目的:防止多线程操作时,刚好调用函数,此时缓存进来了
// 判断当前类是否有缓存优化, 其实可以理解成是否以及配置了缓存
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
// 如果当前类配置了缓存, 直接走cache_getImp方法根据sel获取imp
imp = cache_getImp(curClass, sel);
if (imp) goto done_unlock;
curClass = curClass->cache.preoptFallbackClass();
#endif
} else {
// 缓存没有方法
// curClass method list.
// 做个比喻: 如果我自己可以就不麻烦爸爸了, 去自己类的方法列表里面去找(查找class → data)
// 根据sel查找自己的 method list(采用二分查找算法), 如果有, 则返回imp
// 源码分析见: 详细分析四: 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就是那个报错方法
break;
}
}
// Halt if there is a cycle in the superclass chain.
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
// 自己方法没有找到去父类方法查找, 所以之前要确定继承链, 方便父类递归查找继续调用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);
// 父类查找缓存
// 源码分析见: 详细分析五: cache_getImp
// 依次查找父类的缓存是否有指定方法
imp = cache_getImp(curClass, sel);
// 如果还是没有找到, 走下面2个判断
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.
// 当imp = forward_imp时(即父类也没查找到), 会走这里的return方法
// RESOLVER就是动态方法决议
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
// 如果找到了imp 直接存入缓存, 方便下次快速查找
// 底层关键代码 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
// 二分查找之后, 存入缓存, 方便之后的快速调用
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;
}
详细分析一: _objc_msgForward_impcache
_objc_msgForward_impcache 源码实现
/********************************************************************
*
* 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. 进入__objc_msgForward_impcache 之后发现走 __objc_msgForward
// 2. 进入__objc_msgForward 之后走 __objc_forward_handler
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
// 关键代码__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找不到时候必然会走这里, 报这个错误(就是我们常见的方法找不到错误)
// 同时看到这里面"+", "-"是苹果人为给写的, 方法底层根本不存在什么类方法, 实例方法
// 类方法对于元类来说就是实例方法, 实例方法对于类来说也是实例方法
_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;
这里普及个查询源码小知识点
-
C/C++
中调用汇编
, 去查找汇编
时,C/C++
调用的方法需要多加一个下划线 -
汇编
中调用C/C++
方法时, 去查找C/C++
方法, 需要将汇编
调用的方法去掉一个下划线
详细分析二: checkIsKnownClass
// 判断当前的类是否是被认可的类
// 判断方法列表, 属性列表等是否已缓存形式写入内存或者加载到已知类中
// 查到才能有相应缓存结构
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);
}
详细分析三: realizeAndInitializeIfNeeded_locked
// 如果尚未实现,则实现给定的类;如果尚未初始化,则初始化该类。
/***********************************************************************
* 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();
// 如果类存在,没有实现,需要先实现,此时的目的是为了确定父类链,方法后续的循环
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
先看下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
// 重点: 实现当前的类方法
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 (如果文件只读, 那么这部分内存就属于 clean memory)
// 其中rw 是dirty memory
// 普及些小知识点
// clean memory 指的是能被重新创建的内存,它主要包含这几类:
// 1.app 的二进制可执行文件
// 2.framework 中的 _DATA_CONST 段
// 3.文件映射的内存
// 4.未写入数据的内存
// 内存映射的文件指的是当 app 访问一个文件时,系统会将文件映射加载到内存中,如果文件只读,那么这部分内存就属于 clean memory。
// 另外需要注意的是, framework 中 _DATA_CONST 并不绝对属于 clean memory, 当 app 使用到 framework 时, 就会变成 dirty memory。
// 所有不属于 clean memory 的内存都是 dirty memory。
// dirty memory这部分内存并不能被系统重新创建, 所以 dirty memory 会始终占据物理内存, 直到物理内存不够用之后,系统便会开始清理。
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.
// 递归实现, 把整个继承链 继承下来(父类, 元类的)
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
// 关联父类, 子类
// 此时class是双向链表结构, 父子关系都确定
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 这个方法主要是, 把所有的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
}
接下来看下是当前类没有初始话, 走的方法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 发送消息, 就是自动调用类中 initialize方法, 比如类中自定义initialize, 调用时候回打印123, 其实跟 load 方法类似, 系统默认为我们调用
// + (void)initialize {
// NSLog(@"123");
// }
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
asm("");
}
详细分析四: getMethodNoSuper_nolock
这个方法其实就是cls
→ data
→ methods
取到methods
, 然后二分查找,核心是方法是findMethodInSortedMethodList
, 源码下面也有2个例子便于理解。
/***********************************************************************
* 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 为初始下标, count为个数, probe是中间二分下标 不断的递归二分实现找到值
// 下边有例子
for (count = list->count; count != 0; count >>= 1) {
//从首地址+下标 --> 移动到中间位置(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是, 排除分类重名
// 判断 当前我已经找到的方法, 发现前面有和他一样的方法, 即分类
while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
// 分类永远在主类前面
// 排除分类重名方法(由于方法的存储是先存储类方法,在存储分类---按照先进后出的原则,分类方法最先出,而我们要取的类方法,所以需要先排除分类方法)
// 如果是两个分类,就看谁先进行加载
probe--;
}
return &*probe;
}
// 不匹配, 如果keyValue 大于 probeValue
// base = probe + 1 就往probe即中间位置的右边查找
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
二分查找例子:
例子1: 一个类里面有8个方法: 01, 02, 03, 04, 05, 06, 07, 08
base = first = 01, count = 8, 比如我们想找第2个, 进行循环
-
第一次循环
probe = 01 + 8 >>1 = 01 右移 4位 = 05 ( count >> 1右移1, 8 (1000)右移1位变为4 (0100) )- 此时02 < 05
- keyValue == probeValue: 不满足
- keyvalue > probeValue: 不满足
-
第二次循环
- base 不变还为01, 但count, 进行了一次判断循环, 现在为4
probe = 01 + 4>>1 = 03 - 此时02 < 03
- keyValue == probeValue: 不满足
- keyvalue > probeValue: 不满足
- base 不变还为01, 但count, 进行了一次判断循环, 现在为4
-
第三次循环
- base 不变还为01, 但count, 进行了一次判断循环, 现在为4
probe = 01 + 2>>1 = 02 - 此时02 == 02
- keyValue == probeValue: 满足, 判断下是否有分类重名, 已找到结束循环返回
- base 不变还为01, 但count, 进行了一次判断循环, 现在为4
例子2: 还是之前例子, 我们想找第7个
base = first = 01, count = 8, 比如我们想找第2个, 进行循环
-
第一次循环
probe = 01 + 8 >>1 = 01 右移 4位 = 05 ( count >> 1右移1, 8 (1000)右移1位变为4 (0100) )- 此时07 > 05
- keyValue == probeValue: 不满足
- keyvalue > probeValue: 满足, base = 05 + 1 = 06, count = 7
-
第二次循环
- base 不变还为01, 但count = 7, 右移1位, 变3
probe = 06 + 3>>1 = 067 - 此时07 == 07
- keyValue == probeValue: 满足判断下是否有分类重名, 已找到结束循环返回
- base 不变还为01, 但count = 7, 右移1位, 变3
详细分析五: cache_getImp
// cache_getImp方法是通过汇编_cache_getImp实现
// 如果父类缓存中找到了方法实现,则跳转至CacheHit即命中,则直接返回imp
// 如果在父类缓存中,没有找到方法实现,则跳转Miss
// 这里普及个知识点, 跟缓存相关的就是汇编, 因为汇编比C/C++快
STATIC_ENTRY _cache_getImp
GetClassFromIsa_p16 p0, 0
// 去执行 CacheLookup 方法, 里面传4个参数
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
// 这段是关键, 当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
- 进入
_cache_getImp
, 可看到内部执行调用CacheLookup 方法
, 里面传4个参数, 需要留意第一个参数GETIMP
- 关键代码
.if \Mode == GETIMP b.ne \MissLabelConstant // cache miss
, 当Mode == GETIMP, 返回MissLabelConstant 即返回走 LGetImpMissConstant 方法 -
LGetImpMissConstant: mov p0, p2 ret
, 这里可看到令p0 = p2(即将p2存到寄存器p0), ret = return返回 -
_cache_getImp
无递归, 只是依次查找父类的缓存是否有指定方法, 直到NSObject
父类nil
总结
慢速查找流程
针对于快速查找
没有找到走慢速查找
流程
-
对于
实例方法
,即在类
中查找,对应慢速查找
的父类链
为:类
→类
→根类
→nil
-
对于
类方法
,即在元类
中查找,对应慢速查找
的父类链
为:元类
→根元类
→根类
→nil
-
如果
慢速查找
也没有找到,则尝试动态方法决议
-
如果
动态方法决议
仍然没有找到,则进行消息转发
-
如果都没有找到就
imp = forward_imp
走经典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'
网友评论