上篇文章分析到了_obje_msgSend
查找cache
消息快速查找,最终会从汇编代码进入_lookUpImpOrForward
进行慢速查找。这篇文章将详细分析这个流程。
一、汇编中找不到缓存
在汇编代码中只有_lookUpImpOrForward
的调用而没有实现,代码中直接搜这个也是搜不到的。因为实现在c/c++
代码中,需要搜索lookUpImpOrForward
。声明如下:
extern IMP lookUpImpOrForward(id obj, SEL, Class cls, int behavior);
那么参数肯定也就是汇编中传过来的,汇编中调用如下:
.macro MethodTableLookup
SAVE_REGS MSGSEND
// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
// receiver and selector already in x0 and x1
//x2 = cls
mov x2, x16
//x3 = LOOKUP_INITIALIZE|LOOKUP_RESOLVER //是否初始化,imp没有实现尝试resolver
//_lookUpImpOrForward(receiver,selector,cls,LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
mov x3, #3
bl _lookUpImpOrForward
// IMP in x0
mov x17, x0
RESTORE_REGS MSGSEND
.endmacro
- 前
3
个参数没有什么好说的,behavior
为LOOKUP_INITIALIZE | LOOKUP_RESOLVER
。那就证明lookUpImpOrForward
是有查找模式的。 - 调用完
_lookUpImpOrForward
后有mov x17, x0
说明是有返回值的,与c/c++
中lookUpImpOrForward
的声明对应上了。
那么就有一个问题了,为什么
cache
查找要使用汇编?
1.汇编更接近机器语言,执行速度快。为了快速找到方法,优化方法查找时间。
2.消息发送参数是未知参数(比如可变参数),c
参数必须明确,汇编相对能够更加动态化。
3.更安全。
二、 慢速查找流程
慢速查找就是不断遍历methodlist
的过程,遍历是一个耗时的过程,所以是使用c/c++
来实现的。
2.1 lookUpImpOrForward
首先明确慢速查找流程的目标是找到sel
对应的imp
。所以核心就是lookUpImpOrForward
中返回imp
的逻辑,精简后源码如下:
NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
//forward_imp赋值
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
//要返回的imp
IMP imp = nil;
//当前查找的cls
Class curClass;
//初始化的一些处理,如果类没有初始化behavior会增加 LOOKUP_NOCACHE,判断是否初始化取的是data()->flags的第29位。
if (slowpath(!cls->isInitialized())) {
behavior |= LOOKUP_NOCACHE;
}
//类是否已经注册,注册后会加入allocatedClasses表中
checkIsKnownClass(cls);
//初始化需要的类。由于要去类中查找方法,如果rw,ro没有准备好那就没有办法查了。也就是为后面的查找代码做好准备。LOOKUP_INITIALIZE用在了这里
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
//赋值要查找的类
curClass = cls;
//死循环,除非return/break
for (unsigned attempts = unreasonableClassCount();;) {//……}
//参数LOOKUP_RESOLVER用在了这里,动态方法决议
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
//没有初始化LOOKUP_NOCACHE就有值了,也就是查完后不要插入缓存。在这个流程中是插入
if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
//共享缓存
while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
cls = cls->cache.preoptFallbackClass();
}
#endif
//填充缓存,这里填充的是`cls`。也就是父类如果有缓存也会被加进子类。
log_and_fill_cache(cls, imp, sel, inst, curClass);
}
done_unlock:
runtimeLock.unlock();
//forward_imp 并且有 LOOKUP_NIL 的时候直接返回nil。也就是不进行forward_imp
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
- 先给
forward_imp
赋值_objc_msgForward_impcache
,这个函数的实现是在汇编中。 -
imp
和curClass
定义。 -
cls->isInitialized()
类没有初始化则behavior
增加LOOKUP_NOCACHE
,类有没有初始化时由data()->flags
的第29
位决定的。
bool isInitialized() {
//#define uint32_t RW_INITIALIZED (1<<29)
return getMeta()->data()->flags & RW_INITIALIZED;
}
-
checkIsKnownClass
判断类是否已经注册,注册后会加入allocatedClasses
表中。 -
realizeAndInitializeIfNeeded_locked
初始化需要的类,由于要去类中查找方法,如果rw
ro
没有准备好那就没有办法查了(methods
就存在其中)。也就是为后面的查找代码做好准备。汇编中调用的时候传递的behavior
中LOOKUP_INITIALIZE
用在了这里。它的流程会在后面介绍。 - 进入
for
死循环查找imp
,核心肯定就是找imp
赋值的地方了。那么就只有break
、return
、goto
才能停止循环,否则一直查找。 - 如果上面
imp
没有找到,LOOKUP_RESOLVER
是有值的,会进入动态方法决议。 - 如果找到
imp
会跳转到done
,判断是否需要插入缓存会调用log_and_fill_cache
最终调用到cache.insert
。父类如果有缓存找到也会加入到子类,这里是因为写入的时候参数是cls
。 - 根据
LOOKUP_NIL
判断是否需要forward
,不需要直接返回nil
,需要返回imp
。
2.1.1 behavior 说明
在从汇编调入lookUpImpOrForward
的时候传入的behavior
参数是LOOKUP_INITIALIZE
和LOOKUP_RESOLVER
。
behavior
类型如下:
/* method lookup */
enum {
LOOKUP_INITIALIZE = 1,
LOOKUP_RESOLVER = 2,
LOOKUP_NIL = 4,
LOOKUP_NOCACHE = 8,
};
根据上面的分析可以得到大致结论:
-
LOOKUP_INITIALIZE
: 控制是否去进行类的初始化。有值初始化,没有不初始化。 -
LOOKUP_RESOLVER
:是否进行动态方法决议。有值决议,没有值不决议。 -
LOOKUP_NIL
:是否进行forward
。有值不进行,没有值进行。 -
LOOKUP_NOCACHE
:是否插入缓存。有值不插入缓存,没有值插入。
2.2 realizeAndInitializeIfNeeded_locked
在这里主要进行类的实例化和初始化,有两个分支:Realize
和Initialize
。
2.2.1 Realize
(这个分支一般在_read_images
的时候就处理好了)
在进行类的实例化的时候调用流程是这样的realizeAndInitializeIfNeeded_locked->realizeClassMaybeSwiftAndLeaveLocked->realizeClassMaybeSwiftMaybeRelock->realizeClassWithoutSwift
,最终会调用realizeClassWithoutSwift
,swift
会调用realizeSwiftClass
。这个不是这篇文章的重点,分析下主要代码如下:
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
class_rw_t *rw;
Class supercls;
Class metacls;
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 = 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);
}
//赋类和元类的操作
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
//关联类
cls->setSuperclass(supercls);
cls->initClassIsa(metacls);
return cls;
}
- 对类的
ro
以及rw
进行处理。 - 循环调用了父类和元类的
realizeClassWithoutSwift
。 - 关联了父类和元类。
当对象调用方法的时候判断类是否初始化,如果初始化了再判断类的父类以及元类,相当于是递归操作了,一直到NSObject->nil
为止。也就是说只要有一个类进行初始化它的上层(也就是父类和元类)都会进行初始化,是一个连锁反应。
⚠️为什么这么操作?
就是为了查找方法。类没有实例方法的话会找父类,类没有类方法会找元类,所以需要这么操作。
2.2.2 Initialized
realizeAndInitializeIfNeeded_locked->initializeAndLeaveLocked->initializeAndMaybeRelock->initializeNonMetaClass
。在initializeNonMetaClass
中调用了callInitialize(cls)
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
asm("");
}
系统直接objc_msgSend
发送了initialize
消息。所以initialize
是在类第一个方法被调用的时候进行调用的。也就是发送第一个消息的时候:
消息慢速查找开始前进行类初始化的时候发送的initialize
消息。
三、循环查找
对于慢速查找流程,我们想到的就是先查自己然后再查父类一直找到NSObject->nil
。
慢速查找流程应该是这样:
1.查自己methodlist
->(sel,imp)。
2.查父类->NSObject->nil ->跳出来
。
查看源码:
//死循环,除非return/break
for (unsigned attempts = unreasonableClassCount();;) {
//先去共享缓存查找,防止这个时候共享缓存中已经写入了该方法。
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
//这里也是调用到了`_cache_getImp`汇编代码,最终调用了`CacheLookup`查找共享缓存。
imp = cache_getImp(curClass, sel);
//找到后直接跳转done_unlock
if (imp) goto done_unlock;
curClass = curClass->cache.preoptFallbackClass();
#endif
} else {
// curClass method list.进行循环查找
Method meth = getMethodNoSuper_nolock(curClass, sel);
//找到method
if (meth) {
//返回imp
imp = meth->imp(false);
//跳转done
goto done;
}
//这里curClass 会赋值,直到找到 NSObject->nil就会返回forward_imp
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
imp = forward_imp;
break;
}
}
// Superclass cache.
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
break;
}
if (fastpath(imp)) {
goto done;
}
}
- 可以看到这是一个死循环。
- 如果有共享缓存,先查找共享缓存,因为前面做了很多准备工作,防止这个时候共享缓存中已经写入了该方法(在汇编中已经查过一次了)。
- 否则就进行二分查找流程,核心逻辑是在
getMethodNoSuper_nolock
中调用的,查找完成返回。 - 如果找到
method
则获取imp
跳转done
,如果没有找到将父类赋值给curClass
,父类不存在则imp = forward_imp;
。- 找到则进入找到
imp
done
的逻辑。-
log_and_fill_cache
插入缓存,也就是调用cls->cache.insert
与分析cache
的时候逻辑对上了。 - 返回
imp
。
-
- 没有找到则
curClass
赋值superclass
,没有superclass
也就是找到了NSObject->nil
的情况下imp = forward_imp
。 - 没有找到并且有父类的情况下通过
cache_getImp
去父类的cache
中查找。这里与共享缓存的cache_getImp
是一个逻辑,最终都是调用汇编_cache_getImp->CacheLookup
。
父类也有快速和慢速查找。
- 找到则进入找到
- 如果父类中也没有找到,则进入递归。直到
imp
找到或者变为forward_imp
才结束循环。
_cache_getImp 说明
源码:
STATIC_ENTRY _cache_getImp
GetClassFromIsa_p16 p0, 0
CacheLookup GETIMP, _cache_getImp, LGetImpMissDynamic, LGetImpMissConstant
LGetImpMissDynamic:
mov p0, #0
ret
LGetImpMissConstant:
mov p0, p2
ret
END_ENTRY _cache_getImp
最终也是调用CacheLookup
进行缓存查找。但是第三个参数是LGetImpMissDynamic
实现是mov p0, #0 ret
也就是找不到就返回了。不会去走__objc_msgSend_uncached
逻辑。
⚠️ 找到父类缓存会插入自己的缓存。
3.1 二分查找流程
3.1.1 getMethodNoSuper_nolock
首先进入的是getMethodNoSuper_nolock
,实现如下:
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
//获取methods
auto const methods = cls->data()->methods();
//循环,这个时候找的是methodlist存的是method_list_t,有可能是二维数据。动态加载方法和类导致的
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;
}
- 这里只是普通的循环,因为
methods
获取的数据类型是method_array_t
,它存储的是method_list_t
。这里的数据结构有可能是二维数据,因为动态加载方法和类导致。 - 核心逻辑是调用
search_method_list_inline
实现的
3.1.2 search_method_list_inline
ALWAYS_INLINE static method_t *
search_method_list_inline(const method_list_t *mlist, SEL sel)
{
//methodlist是否已经修复
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
。 - 无序进入循环查找
findMethodInUnsortedMethodList
。
⚠️ 那么就有个问题,排序是什么时候完成的?
既然是method_t
相关类型那就进去搜一下sort
相关的关键字。发现了如下方法:
struct SortBySELAddress :
public std::binary_function<const struct method_t::big&,
const struct method_t::big&, bool>
{
bool operator() (const struct method_t::big& lhs,
const struct method_t::big& rhs)
{ return lhs.name < rhs.name; }
};
打断点后发现了如下调用栈:
是在
_read_images
类加载映射的时候注册调用的。又见到了_read_images
,这个方法将在后面继续研究。
结论:类在加载实例化的时候进行的排序,是按照sel address
进行排序的。
3.1.3 findMethodInSortedMethodList 二分查找
findMethodInSortedMethodList
会根据架构最终进入各自的findMethodInSortedMethodList
方法,这里以x86
为例进行分析:
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;
//method list count
uint32_t count;
//count >>= 1 相当于除以2。加入count为8
for (count = list->count; count != 0; count >>= 1) {//7 >> 1 = 3,前一个已经比较了4,这里就完美的避开了4。
//base是为了配合少查找
//probe中间元素,第一次 probe = 0 + 8 >> 1 = 4
probe = base + (count >> 1);
//sel
uintptr_t probeValue = (uintptr_t)getName(probe);
//与要查找的sel是否匹配
if (keyValue == probeValue) {
// `probe` is a match.
// Rewind looking for the *first* occurrence of this value.
// This is required for correct category overrides.
//查找分类同名sel。如果匹配了就找分类中的。因为分类是在前面的,所以一直找到最开始的位置。
while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
probe--;
}
//匹配则返回。
return &*probe;
}
//没有匹配
if (keyValue > probeValue) {//大于的情况下,在后半部分
//没有匹配的情况下,如果sel在后半部分,这里base指向了上次查找的后面一个位置。
base = probe + 1;//5
//count对应减1
count--;//7 -- 操作为了少做比较,因为已经比较过了
}
//在前半部分不进行额外操作。
}
return nil;
}
- 首先是一个循环比较,条件是
count >>= 1
,这里是对count
进行减半,相当于二分。 -
base
是为了少做比较,相当于是一个基线,当要继续往后查找的时候base
为当前查找元素的下一个元素。 - 当要继续往后查找的时候
count
进行了--
操作,这一步是为了count >>= 1
不包含已经比较过的范围。 - 找到值的时候会循环继续往前查找,因为存在分类与类中方法同名的情况(分类方法放在类中同名方法前面),一直找到不同名为止。
⚠️根据源码可以得到以下结论:
1.分类方法调用先找类中方法,再逐次找到分类方法,直到找到第一个。
2.因为判断条件是当前命中元素与前一个元素比较,sel
相同的情况下才继续查找,那就说明分类的方法是插入类中方法列表中的,都在对应类中方法的前面。
- 查找完毕后会返回
lookUpImpOrForward
这里以有8
个方法为类来分析查找流程,过程如下:
比较开始值:count = 8 base = 0 probe = 4
- 第一次:比较 probe = 4
- keyValue > probeValue count = 3(先--,再>>1) base = 5 probe = 6
第二次: 比较 probe = 6
- keyValue > probeValue count = 1(先--,再>>1) base = 7 probe = 7
第三次:比较 probe = 7
- keyValue > probeValue count = 0(先--,再>>1) base = 8 probe = 8(count == 0)
- keyValue < probeValue count = 0(>>1) base = 7 probe = 7 (count == 0)
- keyValue < probeValue count = 1(>>1) base = 5 probe = 5
第三次:比较 probe = 5
- keyValue > probeValue count = 0(先--,再>>1) base = 6 probe = 6 (count == 0)
- keyValue < probeValue count = 0(>>1) base = 5 probe = 5(count == 0)
- keyValue < probeValue count = 4(>>1) base = 0 probe = 2
第二次:比较 probe = 2
- keyValue > probeValue count = 1(先--,再>>1) base = 3 probe = 3
第三次:比较 probe = 3
- keyValue > probeValue count = 0(先--,再>>1) base = 4 --(count == 0)
- keyValue < probeValue count = 0(>>1) base = 3 -- (count == 0)
- keyValue < probeValue count = 2(>>1) base = 1 probe = 1
第三次:比较 probe = 1
- keyValue > probeValue count = 0(先--,再>>1) base = 0 --(count == 0)
- keyValue < probeValue count = 1(>>1) base = 0 probe = 0
第四次:比较 probe = 0
- keyValue > probeValue count = 0(先--,再>>1) base = 1 --(count == 0)
- keyValue < probeValue count = 0(>>1) base = 0 -- (count == 0)
代码模拟:
int testFindSortedMethods(int methodCount,int findKey) {
int base = 0;
int probe = 0;
int round = 0;
printf("查找key:%d\n",findKey);
for (int count = methodCount; count != 0; count >>= 1) {
round++;
probe = base + (count >> 1);
printf("\t第%d轮 scan count :%d, base:%d,probe:%d\n",round,count,base,probe);
if (findKey == probe) {
printf("\tfound prode:%d\n",probe);
return probe;
}
if (findKey > probe) {
base = probe + 1;
count--;
}
}
printf("\tnot found -1\n");
return -1;
}
调用:
testFindSortedMethods(8, 0);
testFindSortedMethods(8, 1);
testFindSortedMethods(8, 2);
testFindSortedMethods(8, 3);
testFindSortedMethods(8, 4);
testFindSortedMethods(8, 5);
testFindSortedMethods(8, 6);
testFindSortedMethods(8, 7);
testFindSortedMethods(8, 8);
testFindSortedMethods(8, 9);
输出:
查找key:0
第1轮 scan count :8, base:0,probe:4
第2轮 scan count :4, base:0,probe:2
第3轮 scan count :2, base:0,probe:1
第4轮 scan count :1, base:0,probe:0
found prode:0
查找key:1
第1轮 scan count :8, base:0,probe:4
第2轮 scan count :4, base:0,probe:2
第3轮 scan count :2, base:0,probe:1
found prode:1
查找key:2
第1轮 scan count :8, base:0,probe:4
第2轮 scan count :4, base:0,probe:2
found prode:2
查找key:3
第1轮 scan count :8, base:0,probe:4
第2轮 scan count :4, base:0,probe:2
第3轮 scan count :1, base:3,probe:3
found prode:3
查找key:4
第1轮 scan count :8, base:0,probe:4
found prode:4
查找key:5
第1轮 scan count :8, base:0,probe:4
第2轮 scan count :3, base:5,probe:6
第3轮 scan count :1, base:5,probe:5
found prode:5
查找key:6
第1轮 scan count :8, base:0,probe:4
第2轮 scan count :3, base:5,probe:6
found prode:6
查找key:7
第1轮 scan count :8, base:0,probe:4
第2轮 scan count :3, base:5,probe:6
第3轮 scan count :1, base:7,probe:7
found prode:7
查找key:8
第1轮 scan count :8, base:0,probe:4
第2轮 scan count :3, base:5,probe:6
第3轮 scan count :1, base:7,probe:7
not found -1
查找key:9
第1轮 scan count :8, base:0,probe:4
第2轮 scan count :3, base:5,probe:6
第3轮 scan count :1, base:7,probe:7
not found -1
可以看到输出与验证的结论一致。
流程图:
二分查找案例示意图
四、案例分析慢速查找流程
定义一个HPObject
以及它的子类HPSubObject
,
HPObject
定义和实现如下:
@interface HPObject : NSObject
- (void)instanceMethod1;
- (void)instanceMethod2;
+ (void)classMethod;
@end
@implementation HPObject
- (void)instanceMethod1 {
NSLog(@"%s",__func__);
}
+ (void)classMethod {
NSLog(@"%s",__func__);
}
@end
HPSubObject
定义和实现如下:
@interface HPSubObject : HPObject
- (void)subInstanceMethod;
@end
@implementation HPSubObject
- (void)subInstanceMethod {
NSLog(@"%s",__func__);
}
@end
根据前面分析的方法查找逻辑测试代码:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
HPSubObject *subObject = [HPSubObject new];
//对象方法 根据慢速查找分析是能找到的
[subObject subInstanceMethod];
[subObject instanceMethod1];
[subObject instanceMethod2];
#pragma clang diagnostic pop
输出:
-[HPSubObject subInstanceMethod]
-[HPObject instanceMethod1]
-[HPSubObject instanceMethod2]: unrecognized selector sent to instance 0x1006addc0
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[HPSubObject instanceMethod2]: unrecognized selector sent to instance 0x1006addc0'
-
subInstanceMethod
与instanceMethod1
符合预期。 -
instanceMethod2
找不到报错unrecognized selector sent to instance
,为什么报这个错误呢?
查看调用堆栈如下:
image.png
那么去源码中搜索错误信息找到以下内容:
// Replaced by CF (throws an NSException)
+ (void)doesNotRecognizeSelector:(SEL)sel {
_objc_fatal("+[%s %s]: unrecognized selector sent to instance %p",
class_getName(self), sel_getName(sel), self);
}
// Replaced by CF (throws an NSException)
- (void)doesNotRecognizeSelector:(SEL)sel {
_objc_fatal("-[%s %s]: unrecognized selector sent to instance %p",
object_getClassName(self), sel_getName(sel), self);
}
// 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;
那么调用的是哪个呢?断点后并没有进入。根据上面的分析imp
找不到的时候会有两个选项resolveMethod_locked
或者_objc_msgForward_impcache
。
_objc_msgForward_impcache
的汇编实现如下:
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
内部直接调用了__objc_msgForward
:
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr p17, [x17, __objc_forward_handler@PAGEOFF]
TailCallFunctionPointer x17
END_ENTRY __objc_msgForward
可以看到__objc_msgForward
的实现是调用__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;
这也就是报错信息的原因,里面进行了格式化的错误信息打印。
接着增加一个NSObject
的分类:
@interface NSObject (Additions)
- (void)categoryInstanceMethod;
@end
@implementation NSObject (Additions)
- (void)categoryInstanceMethod {
NSLog(@"%s",__func__);
}
@end
调用:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wundeclared-selector"
[HPSubObject classMethod];
[HPSubObject performSelector:@selector(categoryInstanceMethod)];
#pragma clang diagnostic pop
输出:
+[HPObject classMethod]
-[NSObject(Additions) categoryInstanceMethod]
-
classMethod
类方法能找到符合预期。 - 为什么
HPSubObject
能调用categoryInstanceMethod
实例方法?
这就涉及到了类的继承链,NSObject元类的父类是NSObject类,所以能找到。
再次说明在OC
的底层没有实例和类方法的区分,类方法和实例方法是人为加上去的。我们只是为了配合OC
的演出视而不见。
五、 总结
慢速查找流程:
-
checkIsKnownClass
检查注册类。 -
realizeAndInitializeIfNeeded_locked
初始化类,为方法查找做好准备。 - 递归查找
imp
,会涉及到动态缓存库的二次确认以及父类的快速慢速查找。- 查找过程会进行二分查找/递归查找。
- 是否二分要看方法列表是否已经排序,排序操作是在类加载实例化的时候完成的。
- 二分查找算法很经典,充分利用
>>1
以及--
不多浪费一次机会。
- 找到
imp
直接跳转返回。根据LOOKUP_NOCACHE
判断是否插入缓存。 - 没有找到则判断是否进行动态方法决议。
- 不进行动态方法决议则判断是否要
forward
整个逻辑流程图:
lookUpImpOrForward慢速消息查找
网友评论