前言
在上篇文章中,我们结合源码分析了方法的快速查找(缓存查找)
,在最后,我们分析到如果缓存查找不到方法就会跳转至__objc_msgSend_uncached
,进入方法的慢速查找,接下来我们就探究一下方法的慢速查找流程。(本次探究源码基于objc781).
准备工作
依旧是老样子我们定义一个ZGPerson
类,代码如下:
@interface ZGPerson : NSObject{
NSInteger age;
}
@property (nonatomic,copy) NSString *name;
@property (nonatomic,copy) NSString *nickName;
- (void)test1;
- (void)test2;
- (void)test3;
- (void)test4;
+ (void)testClass;
········分割线
@implementation ZGPerson
- (void)test1{
NSLog(@"%s",__func__);
}
- (void)test2{
NSLog(@"%s",__func__);
}
- (void)test3{
NSLog(@"%s",__func__);
}
- (void)test4{
NSLog(@"%s",__func__);
}
+ (void)testClass{
NSLog(@"%s",__func__);
}
@end
在main.m
中调用一下test1
方法
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
ZGPerson *person = [ZGPerson alloc];
[person test1];
}
return 0;
}
找寻慢速查找入口
我们在Xcode - Debug - Debug WorkFlow
中将Always show Disamessbly
打开,跟着断点调试
接着我们按着
control
,点击红圈选择键进行单步调试
发现
方法缓存查找不到之后会调用
__objc_msgSend_uncached
继续单步走
发现走到了lookUpImpOrForward
方法,在源码objc-runtime-new.mm
文件中的6095
行。
同样的我们通过汇编代码
执行顺序依然可以验证__objc_msgSend_uncached - MethodTableLookup - _lookUpImpOrForward
,这里我就不再走一遍了。
接下来我们迫不及待的进入lookUpImpOrForward
方法一探究竟
.
lookUpImpOrForward
源码(重点)
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();
// Optimistic cache lookup
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
// 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.
//
// TODO: this check is quite costly during process startup.
checkIsKnownClass(cls);
if (slowpath(!cls->isRealized())) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
// runtimeLock may have been dropped but is now locked again
}
if (slowpath((behavior & LOOKUP_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
}
runtimeLock.assertLocked();
curClass = cls;
// The code used to lookpu 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();;) {
// curClass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp;
goto done;
}
if (slowpath((curClass = curClass->superclass) == 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.
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
log_and_fill_cache(cls, imp, sel, inst, curClass);
runtimeLock.unlock();
done_nolock:
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
这里我们详细的分析一下
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
runtimeLock.assertUnlocked();
// Optimistic cache lookup
if (fastpath(behavior & LOOKUP_CACHE)) {
imp = cache_getImp(cls, sel);
if (imp) goto done_nolock;
}
//加锁,目的是保证读取的线程安全
runtimeLock.lock();
这里主要初始化了一些变量,其中forward_imp
的值就是未找到imp
的函数实现。需要注意的是,这里我们又从缓存中
查找了一次imp = cache_getImp(cls, sel);
,原因是因为有可能我们在慢速查找
的时候,有可能通过之前分析的汇编代码
缓存,又缓存了这个方法(因为汇编代码的执行效率比较高),所以需要再查找一次缓存。
checkIsKnownClass
源码
ALWAYS_INLINE
static void
checkIsKnownClass(Class cls)
{
if (slowpath(!isKnownClass(cls))) {
_objc_fatal("Attempt to use unknown class %p.", cls);
}
}
这里是检测传入的类是否是已知类,只有合法的类
才能进行下面的方法查找
// 如果类没有实现
if (slowpath(!cls->isRealized())) {
// 检查类是不是swift类(如果是,就返回swift类)
// 在赋值时会解开运行时锁,在返回时会继续加上运行时锁
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
// runtimeLock may have been dropped but is now locked again
}
这里类没有实现,我们就需要去做类的实现,我们一步步跟入到oc类
最终的实现方法realizeClassWithoutSwift
,而Swift
类的话会调用cls = realizeSwiftClass(cls);
方法,两者实现的功能基本一致,这里我们以oc
的为例展开分析
realizeClassWithoutSwift
源码
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
runtimeLock.assertLocked();
class_rw_t *rw;
Class supercls;
Class metacls;
...省略一些代码
// 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->superclass), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
// Update superclass and metaclass in case of remapping
//更新继承关系
cls->superclass = supercls;
//更新isa指向
cls->initClassIsa(metacls);
...省略一些代码
// Attach categories
methodizeClass(cls, previously);
return cls;
}
这里主要是为我们查找方法做准备工作:
- 一方面将类中的
ro,rw以及data()
中的数据进行赋值,确保数据无误
- 另一方面将我们的
传入的类
的继承关系
和isa的指向关系
进行更新
,同时与该类的categories
绑定,接着将传入的类的父类一直到nil
进行相同的操作,确保我们在查找继承链
中方法的时候不会出错
这里realizeClassWithoutSwift
方法的递归调用
也是值得我们学习的地方
if (slowpath((behavior & LOOKUP_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
}
这里调用我们熟悉的initialize
初始化方法,也就解释了initialize
会自动调用的原因,原来是系统为我们调用的。
查找方法
// unreasonableClassCount -- 表示类的迭代的上限
for (unsigned attempts = unreasonableClassCount();;) {
// curClass method list.
//在本类中查找
Method meth = getMethodNoSuper_nolock(curClass, sel);
//如果找到,goto done;
if (meth) {
imp = meth->imp;
goto done;
}
//如果没有找到, curClass赋值为curClass的父类,如果父类为nil,说明未找到该imp
if (slowpath((curClass = curClass->superclass) == nil)) {
// No implementation found, and method resolver didn't help.
// Use forwarding.
imp = forward_imp;
break;
}
//--attempts=0 说明循环已到上限,进行报错
// 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)) {
//一直找不到直接break退出for循环
// 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)) {
//找到了此方法,将其存储到cache中
// Found the method in a superclass. Cache it in this class.
goto done;
}
}
// No implementation found. Try method resolver once.
//没找到方法实现,尝试方法决议,只一次
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
//加入缓存,方便下次直接取
log_and_fill_cache(cls, imp, sel, inst, curClass);
runtimeLock.unlock();
done_nolock:
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
这里我加了一些注释方便大家理解
其中查找本类的getMethodNoSuper_nolock
方法,为二分查找
,最后会调用到findMethodInSortedMethodList
方法,这里我们进入源码看下
findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
ASSERT(list);
const method_t * const first = &list->first;
const method_t *base = first;//首地址
const method_t *probe;
uintptr_t keyValue = (uintptr_t)key;//需要查找方法的地址
uint32_t count;
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);//count右移一位,即除以2,二分查找
uintptr_t probeValue = (uintptr_t)probe->name;
// 如果找到方法通过while-- 排除分类重名方法
if (keyValue == probeValue) {
// `probe` is a match.
// Rewind looking for the *first* occurrence of this value.
// This is required for correct category overrides.
//排除分类重名方法(方法的存储是先存储类方法,在存储分类---按照先进后出的原则,分类方法最先出,而我们要取的类方法,所以需要先排除分类方法)
//如果是两个分类,就看谁先进行加载
while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
probe--;
}
return (method_t *)probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
其中cache_getImp
方法为汇编代码,源码如下:
STATIC_ENTRY _cache_getImp
GetClassFromIsa_p16 p0
CacheLookup GETIMP, _cache_getImp
.macro GetClassFromIsa_p16 /* src */
__LP64__
// 64-bit packed isa
and p16, $0, #ISA_MASK
.endmacro
这里是有必要解释一下,cache_getImp
是从父类的缓存中
找我们传入的sel
方法,如果父类中缓存没有找到,就break
跳出for
循环。如果父类中缓存中找到了这个方法,就加到本类的缓存
中,方便下次直接快速查找
。(细心的你可能已经发现for循环
里只有一个条件,相当于是一个死循环
)
查找流程总结
- 通过
getMethodNoSuper_nolock
在本类中查找meth
,如果找到meth
进goto done
缓存起来- 在上面没找到meth,通过
curClass = curClass->superclass
把curClass指向父类
,如果curClass
为nil
,说明找不到方法,imp = forward_imp
然后结束循环--attempts=0
说明循环已到上限,进行报错- 通过
cache_getImp(curClass, sel)
方法在父类缓存中查找,如果找不到就break
跳出for
循环。如果父类中缓存中找到了这个方法,就加到本类的缓存
中,方便下次直接快速查找
- 如果
imp == forward_imp
,结束循环- 如果没找到方法,循环结束后会进入
resolveMethod_locked
去做一次动态决议。
动态决议resolveMethod_locked
// No implementation found. Try method resolver once.
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
...分割线
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 (!lookUpImpOrNil(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForward(inst, sel, cls, behavior | LOOKUP_CACHE);
}
这里实例方法会调用resolveInstanceMethod
,类方法会调用resolveClassMethod
resolveInstanceMethod
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
SEL resolve_sel = @selector(resolveInstanceMethod:);
// 1\. 判断系统是否实现SEL_resolveInstanceMethod方法
// 即+(BOOL)resolveInstanceMethod:(SEL)sel,
// 继承自NSObject的类,默认实现,返回NO
if (!lookUpImpOrNil(cls, resolve_sel, cls->ISA())) {
// Resolver not implemented.
// 不是NSObject的子类,也未实现+(BOOL)resolveInstanceMethod:(SEL)sel,
// 直接返回,没有动态解析的必要
return;
}
//系统给你一次机会 - 你要不要针对 sel 来操作一下下
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
//再次寻找IMP
IMP imp = lookUpImpOrNil(inst, sel, cls);
// 只有这用了resolved, 所以返回NO或YES不影响forward
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));
}
}
}
resolveClassMethod
static void resolveClassMethod(id inst, SEL sel, Class cls)
{
runtimeLock.assertUnlocked();
ASSERT(cls->isRealized());
ASSERT(cls->isMetaClass());
if (!lookUpImpOrNil(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 = lookUpImpOrNil(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));
}
}
}
由此:我们可以在+(BOOL)resolveInstanceMethod:(SEL)sel
方法中对未实现的方法指定已实现方法的IMP
,并添加到类中,实现方法动态
解析,防止系统崩溃
。
我们将ZGPerson
中test4
的实现删除,然后利用动态决议对test4
处理,然后在main
中调用test4
。
- (void)test1{
NSLog(@"%s",__func__);
}
- (void)test2{
NSLog(@"%s",__func__);
}
- (void)test3{
NSLog(@"%s",__func__);
}
+ (void)testClass{
NSLog(@"%s",__func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == @selector(test4)) {
NSLog(@"%@ 来了",NSStringFromSelector(sel));
IMP imp = class_getMethodImplementation(self, @selector(test1));
Method sayMMethod = class_getInstanceMethod(self, @selector(test1));
const char *type = method_getTypeEncoding(sayMMethod);
return class_addMethod(self, sel, imp, type);
}
return [super resolveInstanceMethod:sel];
}
...分割线
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
ZGPerson *person = [ZGPerson alloc];
// [person test1];
[person test4];
}
return 0;
}
执行结果如下
并没有崩溃,而是调用了我们写的test1
的实现,由此验证了上面的结论,
objc_msgForward_impcache做了什么?
如果我们方法决议仍然没有处理,那么const IMP forward_imp = (IMP)_objc_msgForward_impcache;
,走到objc_msgForward_impcache
方法,objc_msgForward_impcache
是汇编实现
,我们仍然以arm64
的为例
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
// 跳转到__objc_msgForward
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
_objc_msgForward_impcache
方法 调用 __objc_msgForward
方法,__objc_msgForward
方法 调用 __objc_forward_handler
方法,
这里我们锁定了汇编方法__objc_forward_handler
,全局搜索发现找不到,原来又是c++
,去掉一个_
,最终找到了__objc_forward_handler
方法的具体实现。
// Default forward handler halts the process.
__attribute__((noreturn, cold)) void
objc_defaultForwardHandler(id self, SEL sel)
{
_objc_fatal("%c[%s %s]: unrecognized selector sent to instance %p "
"(no message forward handler is installed)",
class_isMetaClass(object_getClass(self)) ? '+' : '-',
object_getClassName(self), sel_getName(sel), self);
}
void *_objc_forward_handler = (void*)objc_defaultForwardHandler;
就是我们在开发中经常遇到的错误unrecognized selector...
总结
- 当在·
objc_msgSend
缓存中没有找到方法,就会来到CheckMiss或者 JumpMiss -> __objc_msgSend_uncached -> MethodTableLookup -> lookUpImpOrForward
进行慢速查找
流程。 - 方法慢速查找会先在
本类方法
列表中找,找不到再去父类缓存
中找,父类缓存
没有就去父类方法列表找,一直找到父类为nil
- 如果
慢速查找
都没有找到,就会进行动态决议_class_resolveMethod
,这是苹果给我们的最后一次
处理机会。 - 动态决议不处理,最后就会走到 将
imp
置换成forward_imp
,最终到_objc_forward_handler
方法 崩溃报错unrecognized selector sent to instance ...