快速查找
runtime将方法调用转换为objc_msgSend函数,尽管每个方法的返回值,参数可能不一样,
但是objc_msgSend可以做类型转换.
这个函数没有C++实现,直接是汇编代码实现.位于.s文件,不同的架构有不同的文件以及不同的汇编代码.
除了objc_msgSend还有几个相关的方法objc_msgSendSuper等.
以ENTRY为入口.
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
NilTest NORMAL
GetIsaFast NORMAL // r10 = self->isa
// calls IMP on success
CacheLookup NORMAL, CALL, _objc_msgSend
NilTestReturnZero NORMAL
GetIsaSupport NORMAL
// cache miss: go search the method lists
LCacheMiss_objc_msgSend:
// isa still in r10
jmp __objc_msgSend_uncached
//...
这是x86_64的objc_msgSend,
NilTest,GetIsaFast,CacheLookup等等这些都是定义在当前文件中的其他代码段.
NilTest用于判断receive是否为空.
GetIsaFast获取isa
接下来CacheLookup就是快速查找IMP.
如果没找到,进入__objc_msgSend_uncached
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
cmp p0, #0 // nil check and tagged pointer check
#if SUPPORT_TAGGED_POINTERS
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
b.eq LReturnZero
#endif
ldr p13, [x0] // p13 = isa
GetClassFromIsa_p16 p13, 1, x0 // p16 = class
LGetIsaDone:
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
b.eq LReturnZero // nil check
GetTaggedClass
b LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
ret
END_ENTRY _objc_msgSend
上面这段是arm64的_objc_msgSend.
x86_64没有SUPPORT_TAGGED_POINTERS的情况,arm64有.
首先也是查看receiver是否为空,如果是空,分成两种情况,一是tagged pointer,走LNilOrTagged,二是不支持tagged pointer,走LReturnZero.
LNilOrTagged和LReturnZero就在上面代码的最后部分.
ldr p13, [x0]
GetClassFromIsa_p16 p13, 1, x0
//p13拿到isa 通过isa拿到class放入p16寄存器
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
//接下来走CacheLookup
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
#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
这里有一个CACHE_MASK_STORAGE,
#if defined(__arm64__) && __LP64__
#if TARGET_OS_OSX || TARGET_OS_SIMULATOR
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
#else
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_HIGH_16
#endif
#elif defined(__arm64__) && !__LP64__
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_LOW_4
#else
#define CACHE_MASK_STORAGE CACHE_MASK_STORAGE_OUTLINED
#endif
所以arm64真机是CACHE_MASK_STORAGE_HIGH_16
p1的类型是SEL,也就是objc_msgSend传进来的参数_cmd, p16是刚才的isa.
ldr p11, [x16, #CACHE] // 这里p11 = mask|buckets,也就是bucket的首地址,
这里CONFIG_USE_PREOPT_CACHES是1,并且我们看带签名的部分,也就是__has_feature(ptrauth_calls)是true.
tbnz p11, #0, LLookupPreopt\Function //判断cache_t存在
and p10, p11, #0x0000fffffffffff //取出后48位,这部分是buckets,从前面的内容我们知道bucket是连续存储的.
eor p12, p1, p1, LSR #7 //eor异或,LSR #7 是右移7位,这就是前面说到的cache_hash函数在CONFIG_USE_PREOPT_CACHES为1的时候的算法,是通过这个hash算法获得SEL的hash值,也就是在buckets中的顺位.
上面说到p1是SEL,所以这个就是p12 = SEL ^ (SEL >> 7),在cache_hash函数里是这么写的value ^= value >> 7.
and p12, p12, p11, LSR #48 // 不过这里最后还要&上mask,最终得到下标.
add p13, p10, p12, LSL #(1+PTRSHIFT)// 刚才p12是下标,p10是bucket首地址,PTRSHIFT是3,这里是首地址加上下标左移4位.指向了一个bucket内存.
1: ldp p17, p9, [x13], #-BUCKET_SIZE
cmp p9, p1
b.ne 3f
2: CacheHit \Mode
3: cbz p9, \MissLabelDynamic
cmp p13, p10
b.hs 1b
这一段是do while,
ldp p17, p9, [x13], #-BUCKET_SIZE //p13是刚才指向的bucket,-BUCKET_SIZE是一个bucket的大小,意思是指向前面一个bucket.放在p9,
这一步对标cache_next函数.
cmp p9, p1 //如果p9不是_cmd.
b.ne 3f //那么去执行3
CacheHit \Mode //p9就是_cmd,就执行CacheHit
cbz p9, \MissLabelDynamic //如果p9是空的,执行MissLabelDynamic函数.
cmp p13, p10 //如果p13大于p10,也就是说bucket不是第0个.
b.hs 1b //就执行1,向前移动.
add p13, p10, p11, LSR #(48 - (1+PTRSHIFT)) //如果向前移动到了首地址,还没匹配到,那就把p13移动到最后一个bucket
add p12, p10, p12, LSL #(1+PTRSHIFT) //刚才cmp p13, p10是从中间到首地址,现在修改p10,到刚才的p13后面一个,意思就是已经查看过的部分就不再查看了
4: ldp p17, p9, [x13], #-BUCKET_SIZE // 取出sel
cmp p9, p1
b.eq 2b
cmp p9, #0
ccmp p13, p12, #0, ne
b.hi 4b
还是do while,如果相等,就执行2, 否则就继续移动,直到走到新的首地址.
上面使用了和cache_hash和cache_next相同的算法来查找,也就是存的取的方法是一样的,只要存了,那一定取的到,
如果没存,那么在ldp p17, p9, [x13], #-BUCKET_SIZE这一步,p9就是空的,
然后cbz p9, \MissLabelDynamic就会跳转到MissLabelDynamic.
这个MissLabelDynamic是什么呢.
/*
* CacheLookup NORMAL|GETIMP|LOOKUP <function> MissLabelDynamic MissLabelConstant
*/
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
CacheLookup是这么定义的,MissLabelConstant是参数,是一个函数
回到前面看objc_msgSend调用cacheLookup的地方
GetClassFromIsa_p16 p13, 1, x0 // p16 = class
LGetIsaDone:
// calls imp or objc_msgSend_uncached
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
MissLabelConstant传的是objc_msgSend_uncached,所以我们去看这个函数
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
MethodTableLookup
TailCallFunctionPointer x17
END_ENTRY __objc_msgSend_uncached
STATIC_ENTRY __objc_msgLookup_uncached
UNWIND __objc_msgLookup_uncached, FrameWithNoSaves
MethodTableLookup
ret
END_ENTRY __objc_msgLookup_uncached
里面执行的是MethodTableLookup
.macro MethodTableLookup
SAVE_REGS MSGSEND
// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
// receiver and selector already in x0 and x1
mov x2, x16
mov x3, #3
bl _lookUpImpOrForward
// IMP in x0
mov x17, x0
RESTORE_REGS MSGSEND
.endmacro
MethodTableLookup里面执行的是lookUpImpOrForward函数,
传参是lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER).
这个函数就是在快速查找失败的时候会执行的,它是C++实现.位于objc-runtime-new.mm
还是CacheLookup的定义
#define NORMAL 0
#define GETIMP 1
#define LOOKUP 2
.macro CacheHit
.if $0 == NORMAL
TailCallCachedImp x17, x10, x1, x16 // authenticate and call imp
.elseif $0 == GETIMP
mov p0, p17
cbz p0, 9f // don't ptrauth a nil imp
AuthAndResignAsIMP x0, x10, x1, x16 // authenticate imp and re-sign as IMP
9: ret // return IMP
.elseif $0 == LOOKUP
// No nil check for ptrauth: the caller would crash anyway when they
// jump to a nil IMP. We don't care if that jump also fails ptrauth.
AuthAndResignAsIMP x17, x10, x1, x16 // authenticate imp and re-sign as IMP
cmp x16, x15
cinc x16, x16, ne // x16 += 1 when x15 != x16 (for instrumentation ; fallback to the parent class)
ret // return imp via x17
.else
mode有三种,NORMAL,GETIMP,和LOOKUP
objc_msgSend里面传的就是NORMAL.是验证并调用imp
当传入GETIMP时,就是_cache_getImp这个函数的实现,
IMP cache_getImp(Class cls, SEL sel, IMP value_on_constant_cache_miss = nil);
这个函数也是频繁的被使用.用于获取imp.
LOOKUP模式是验证并重签名imp,不会调用.
慢速查找
上面讲到,快速查找失败会执行lookUpImpOrForward函数,这个函数位于objc-runtime-new.mm
enum {
LOOKUP_INITIALIZE = 1,
LOOKUP_RESOLVER = 2,
LOOKUP_NIL = 4,
LOOKUP_NOCACHE = 8,
};
NEVER_INLINE
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
IMP imp = nil;
Class curClass;
runtimeLock.assertUnlocked();
if (slowpath(!cls->isInitialized())) {
behavior |= LOOKUP_NOCACHE;
}
这个方法是在汇编中调用的时候,传参是这样的
// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
// receiver and selector already in x0 and x1
mov x2, x16
mov x3, #3
bl _lookUpImpOrForward
obj是x0,sel是x1,cls是x2,最后的参数behavior是x3,传的是3.
首先判断类是否初始化isInitialized,这个条件一般不会进来,如果类没有初始化,behavior = 0011 | 1000 = 1011.
checkIsKnownClass(cls);
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
runtimeLock.assertLocked();
curClass = cls;
判断类是否被添加到类表中,也就是存不存在这个类,如果不存在,就会触发断言.
然后调用realizeAndInitializeIfNeeded_locked初始化类,behavior & LOOKUP_INITIALIZE是0011 & 0001 或者1011 & 0001总之一定是true.
for (unsigned attempts = unreasonableClassCount();;) {
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
#if CONFIG_USE_PREOPT_CACHES
imp = cache_getImp(curClass, sel);
if (imp) goto done_unlock;
curClass = curClass->cache.preoptFallbackClass();
#endif
} else {
method_t *meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
imp = meth->imp(false);
goto done;
}
if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
imp = forward_imp;
break;
}
}
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
imp = cache_getImp(curClass, sel);
if (slowpath(imp == forward_imp)) {
break;
}
if (fastpath(imp)) {
// Found the method in a superclass. Cache it in this class.
goto done;
}
}
这是一个最多执行attempts次的for循环,或者说是无限循环,当(--attempts == 0)时会直接触发断言.
attempts是一个相当大的数,它是这么实现的
static unsigned unreasonableClassCount()
{
runtimeLock.assertLocked();
int base = NXCountMapTable(gdb_objc_realized_classes) +
getPreoptimizedClassUnreasonableCount();
return (base + 1) * 16;
}
它是以静态类表的大小加上dyld的类的个数为基础,乘上16得到的一个不真实的数字,因为那个for循环不需要真实的循环次数,只要足够就行.
isConstantOptimizedCache用于判断是否缓存优化.
CONFIG_USE_PREOPT_CACHES在arm64并且iOS时是1.
cache_getImp前面说到了是cahcelookup的getImp mode,用来查找imp.
如果满足这些条件,就会走快速查找,不过cahcelookup的getImp mode失败了不走lookUpImpOrForward,所以不会循环.
另一种情况则是去rw中找method_t, getMethodNoSuper_nolock函数是从类本身查找.
前面curClass首先指向传进来的cls.
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
auto const methods = cls->data()->methods();
for (auto mlists = methods.beginLists(),
end = methods.endLists();
mlists != end;
++mlists)
{
method_t *m = search_method_list_inline(*mlists, sel);
if (m) return m;
}
return nil;
}
从class获取methods(),这是一个list_array_tt,可能会有两层结构,内层结构用search_method_list_inline来迭代.
search_method_list_inline这个函数在上一篇有说明.
如果从类自己身上没找到的话,curClass = curClass->getSuperclass(),
curClass会指向父类,顺便判断如果没有父类了,就赋值imp为forward_imp,这个等下再说.
接下来是前面有提到的for循环最大执行次数.
然后就是从父类查找,如果找到了就跳到done,没有就继续循环.
if (slowpath(behavior & LOOKUP_RESOLVER)) {
behavior ^= LOOKUP_RESOLVER;
return resolveMethod_locked(inst, sel, cls, behavior);
}
done:
if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
#if CONFIG_USE_PREOPT_CACHES
while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
cls = cls->cache.preoptFallbackClass();
}
#endif
log_and_fill_cache(cls, imp, sel, inst, curClass);
}
done_unlock:
runtimeLock.unlock();
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
如果for循环中找到了imp,就会跳转到done或者done_unlock,
如果没有找到imp,但是从for循环break了,就会走resolveMethod_locked.
但是它是有条件的,为什么要设置条件呢,因为resolveMethod_locked的流程中也会调用lookUpImpOrForward,
这行限制改变了behavior,第二次进来的时候就不满足条件了.
static NEVER_INLINE IMP
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
{
if (! cls->isMetaClass()) {
resolveInstanceMethod(inst, sel, cls);
}
else {
resolveClassMethod(inst, sel, cls);
if (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);
}
}
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
这个过程叫做动态方法决议.
做了两件事,
1.如果是元类就调用resolveClassMethod,如果是类对象就调用resolveInstanceMethod
2.调用lookUpImpOrForwardTryCache
lookUpImpOrForwardTryCache就是_lookUpImpTryCache,它里面干了两件事,
一是快速查找,二是慢速查找.
为什么要再来次,这是因为步骤1,也就是动态决议,给了程序一个临时添加方法的机会,添加之后,再走一次查找流程.
static void resolveInstanceMethod(id inst, SEL sel, Class cls)
{
SEL resolve_sel = @selector(resolveInstanceMethod:);
if (!lookUpImpOrNilTryCache(cls, resolve_sel, cls->ISA(/*authenticated*/true))) {
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
bool resolved = msg(cls, resolve_sel, sel);
IMP imp = lookUpImpOrNilTryCache(inst, sel, cls);
if (resolved && PrintResolving) {
if (imp) {
_objc_inform("RESOLVE: method %c[%s %s] "
"dynamically resolved to %p",
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel), imp);
}
else {
// Method resolver didn't add anything?
_objc_inform("RESOLVE: +[%s resolveInstanceMethod:%s] returned YES"
", but no new implementation of %c[%s %s] was found",
cls->nameForLogging(), sel_getName(sel),
cls->isMetaClass() ? '+' : '-',
cls->nameForLogging(), sel_getName(sel));
}
}
}
首先获取了一个SEL,resolveInstanceMethod,并且判断class是否实现了这个sel.
如果实现了,就给class发消息,调用resolveInstanceMethod这个方法.
然后调用lookUpImpOrNilTryCache查询一次sel是否存在了.
void newFunc(){
NSLog(@"新添加的method6");
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
NSLog(@"未知的方法 %@",NSStringFromSelector(sel));
if([NSStringFromSelector(sel) isEqualToString:@"myMethod6"]){
class_addMethod(self, sel, (IMP)newFunc, "");
return true;
}
return [super resolveInstanceMethod:sel];
}
//main.m
id myObj = [MyClass alloc];
MyClass *my = (MyClass *)[myObj init];
[my performSelector:NSSelectorFromString(@"myMethod6")];
写一个demo,可以通过在bool resolved = msg(cls, resolve_sel, sel);断点,
看到消息发送之后,输出了"未知的方法 myMethod6".
放开断点输出"新添加的method6".
在done的位置还调用了log_and_fill_cache函数
log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer)
{
#if SUPPORT_MESSAGE_LOGGING
if (slowpath(objcMsgLogEnabled && implementer)) {
bool cacheIt = logMessageSend(implementer->isMetaClass(),
cls->nameForLogging(),
implementer->nameForLogging(),
sel);
if (!cacheIt) return;
}
#endif
cls->cache.insert(sel, imp, receiver);
}
这个函数的作用是给cache_t添加数据.这部分的内容在上一篇.
上面的logMessageSend是输出方法调用的信息.
void instrumentObjcMessageSends(BOOL flag)
通过这个函数可以设置是否输出.
前面提到了一个forward_imp.
const IMP forward_imp = (IMP)_objc_msgForward_impcache;
_objc_msgForward_impcache没有c++的实现,但是可以在.s里找到汇编实现
STATIC_ENTRY __objc_msgForward_impcache
// No stret specialization.
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,然后__objc_msgForward的实现紧接着在下面,
调用__objc_forward_handler,最终返回一个x17.
__objc_forward_handler又回到了c++代码
// 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 sent to instance ...
老朋友了属于是.
这个函数是可以修改的.
void objc_setForwardHandler(void *fwd, void *fwd_stret)
{
_objc_forward_handler = fwd;
if SUPPORT_STRET
_objc_forward_stret_handler = fwd_stret;
endif
}
网友评论