消息发送和消息转发机制¶
objc_msgSend¶
方法的本质是消息
消息发送:通过sel找imp
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 编译 上层 代码 得到一个解释
// 调用方法 = 消息发送 : objc_msgSend(消息的接受者,消息的主体(sel + 参数))
LGPerson *person = [LGPerson alloc];
LGTeacher *teach = [LGTeacher alloc];
[person sayNB];
// objc_msgSend(person, @selector(sayNB));
// 子类是不是没有 -> 父类
[person sayHello];
}
return 0;
}
clang -rewrite-objc main.m
,生成一个.cpp文件
底层代码
调用方法 = 消息发送:objc_msgSend(消息接收者, 消息的方法名sel, 方法的参数);
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
//类方法
LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc"));
LGTeacher *teach = ((LGTeacher *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGTeacher"), sel_registerName("alloc"));
//实例方法
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayHello"));
}
return 0;
}
objc_msgSendSuper¶
从当前类的父类中查找方法的实现:
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
方法的快速查找¶
objc_msgSend¶
在OC中,所有的消息调用最后都会通过 objc_msgSend 方法进行访问。为了加快执行速度,这个方法在runtime源码中是用汇编实现的。参数在寄存器中。
汇编会在函数前面加一个下划线,为了防止符号(汇编和C语言符号)冲突。
文件位置:objc-msg-arm64.s
,arm64
是真机。
//进入objc_msgSend流程
ENTRY _objc_msgSend
//流程开始,无需frame
UNWIND _objc_msgSend, NoFrame
//判断p0(消息接受者)是否存在,不存在则重新开始执行objc_msgSend
cmp p0, #0 // nil check and tagged pointer check
//如果支持小对象类型。返回小对象或空
#if SUPPORT_TAGGED_POINTERS
//b是进行跳转,b.le是小于判断,也就是小于的时候LNilOrTagged
b.le LNilOrTagged // (MSB tagged pointer looks negative)
#else
//等于,如果不支持小对象,就LReturnZero
b.eq LReturnZero
#endif
//通过p13取isa
ldr p13, [x0] // p13 = isa
//通过isa取class并保存到p16寄存器中
GetClassFromIsa_p16 p13, 1, x0 // p16 = class
//LGetIsaDone是一个入口
LGetIsaDone:
// calls imp or objc_msgSend_uncached
//进入到缓存查找或者没有缓存查找方法的流程
CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
#if SUPPORT_TAGGED_POINTERS
LNilOrTagged:
// nil check判空处理,直接退出
b.eq LReturnZero // nil check
GetTaggedClass
b LGetIsaDone
// SUPPORT_TAGGED_POINTERS
#endif
CacheLookup¶
//在cache中通过sel查找imp的核心流程
.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
//从x16中取出class移到x15中
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表示p16寄存器存储的值,当前是Class
//#数值表示一个值,这里的CACHE经过全局搜索发现是2倍的指针地址,也就是16个字节
//#define CACHE (2 * __SIZEOF_POINTER__)
//经计算,p10就是cache
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
//真机64位看这个
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
//CACHE 16字节,也就是通过isa内存平移获取cache,然后cache的首地址就是 (bucket_t *)
ldr p11, [x16, #CACHE] // p11 = mask|buckets
#if CONFIG_USE_PREOPT_CACHES
//获取buckets
#if __has_feature(ptrauth_calls)
tbnz p11, #0, LLookupPreopt\Function
and p10, p11, #0x0000ffffffffffff // p10 = buckets
#else
//and表示与运算,将与上mask后的buckets值保存到p10寄存器
and p10, p11, #0x0000fffffffffffe // p10 = buckets
//p11与#0比较,如果p11不存在,就走Function,如果存在走LLookupPreopt
tbnz p11, #0, LLookupPreopt\Function
#endif
//按位右移7个单位,存到p12里面,p0是对象,p1是_cmd
eor p12, p1, p1, LSR #7
and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask
#else
and p10, p11, #0x0000ffffffffffff // p10 = buckets
//LSR表示逻辑向右偏移
//p11, LSR #48表示cache偏移48位,拿到前16位,也就是得到mask
//这个是哈希算法,p12存储的就是搜索下标(哈希地址)
//整句表示_cmd & mask并保存到p12
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
//去除掩码后bucket的内存平移
//PTRSHIFT经全局搜索发现是3
//LSL #(1+PTRSHIFT)表示逻辑左移4位,也就是*16
//通过bucket的首地址进行左平移下标的16倍数并与p12相与得到bucket,并存入到p13中
add p13, p10, p12, LSL #(1+PTRSHIFT)
// p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
// do {
//ldp表示出栈,取出bucket中的imp和sel分别存放到p17和p9
1: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
//cmp表示比较,对比p9和p1,如果相同就找到了对应的方法,返回对应imp,走CacheHit
cmp p9, p1 // if (sel != _cmd) {
//b.ne表示如果不相同则跳转到2f
b.ne 3f // scan more
// } else {
2: CacheHit \Mode // hit: call or return imp
// }
//向前查找下一个bucket,一直循环直到找到对应的方法,循环完都没有找到就调用_objc_msgSend_uncached
3: cbz p9, \MissLabelDynamic // if (sel == 0) goto Miss;
//通过p13和p10来判断是否是第一个bucket
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
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
// sel_offs is 26 bits because it needs to address a 64 MB buffer (~ 20 MB as of writing)
// keep the remaining 38 bits for the IMP offset, which may need to reach
// across the shared cache. This offset needs to be shifted << 2. We did this
// to give it even more reach, given the alignment of source (the class data)
// and destination (the IMP)
ldr x17, [x10, x9, LSL #3] // x17 == (sel_offs << 38) | imp_offs
cmp x12, x17, LSR #38
.if \Mode == GETIMP
b.ne \MissLabelConstant // cache miss
sbfiz x17, x17, #2, #38 // imp_offs = combined_imp_and_sel[0..37] << 2
sub x0, x16, x17 // imp = isa - imp_offs
SignAsImp x0
ret
.else
b.ne 5f // cache miss
sbfiz x17, x17, #2, #38 // imp_offs = combined_imp_and_sel[0..37] << 2
sub x17, x16, x17 // 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
objc_msgSend(receiver,sel,...)流程¶
- cmp 比较指令,判断消息接收者receiver是否存在
- 通过消息接收者receiver的isa找到对应的class
- class通过内存平移找到cache
- 从cache里面找buckets
- 遍历bucket对比sel
- 如果buckets有对应的sel,则cacheHit命中cache,返回imp
- buckets没有对应的sel,则调用__objc_msgSend_uncached
调用方法的时候底层会objc_msgSend查找方法的imp
发送消息的是一个对象。如果要找类对象需要通过对象的isa指针与mask查找到类对象。
objc_msgSend:发送消息。 isa-->消息的接受者-->类(对象方法)或元类(类方法)
万物皆对象 类也是对象 objc_class继承objc_object
方法有方法缓存
方法缓存存在cache_t中,cache_t存在类对象objc_class结构体中。
在objc_class结构体中根据偏移计算相应结构体指针。
cache_t是一个结构体 里面有一个存储方法缓存的bucket_t数组,bucket_t
里面有_imp
,_sel
。
根据sel和imp在方法缓存中,如果缓存中找不到(缓存没有命中),则来到__objc_msgSend_uncached方法中进行没有方法缓存的查找,才从methodList中找(遍历方法列表)。
总结¶
objc_msgSend在查找的时候先进行一个缓存的命中,通过地址的与运算,拿到当前的缓存,和当前要查找的sel进行比对,如果比对上就命中了,直接使用。没有比对上则进行__objc_msgSend_uncached。
__objc_msgSend_uncached¶
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
__objc_msgSend_uncached会调用MethodTableLookup
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。
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior){} 从汇编到C++。
方法的慢速查找¶
lookUpImpOrForward¶
在快速查找流程中,方法缓存未命中。底层就走到慢速查找流程,并一路从汇编走到lookUpImpOrForward函数。
缓存没有就去找methodList,从类的data_bits中的data数据中找(data数据中有 属性列表 成员变量 方法列表 协议列表)。
lookUpImpOrForward :methodList 自己找不到 找到getSuperClass() 接着循环找。
调用 lookUpImpOrForward 方法,返回值是个 IMP 指针,如果查找到了调用函数的 IMP ,则进行方法的访问
- 先当前类对象的方法列表method_t中遍历方法列表。
- 沿着当前继承链当中的superClass,指针的指向,
- 先找父类的cahe。
-
然后再找父类的method_t来进行方法遍历查找。
-
一直遍历到Root Class(NSObject)的父类nil。然后消息转发。
- 上面只要找到之后,就存到cache中,下次就是快速查找,不用再慢速查找了。
在objc-runtime-new文件中找到lookUpImpOrForward的定义。
//返回IMP
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;
}
runtimeLock.lock();
checkIsKnownClass(cls);
cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
// runtimeLock may have been dropped but is now locked again
runtimeLock.assertLocked();
curClass = cls;
//死循环
for (unsigned attempts = unreasonableClassCount();;) {
if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
//再一次从cache里面找imp
//目的:防止多线程操作时,刚好调用函数,此时缓存进来了
#if CONFIG_USE_PREOPT_CACHES
imp = cache_getImp(curClass, sel);
if (imp) goto done_unlock;
curClass = curClass->cache.preoptFallbackClass();
#endif
} else {
// curClass method list.
//从当前类的方法列表中找
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;
}
}
// Halt if there is a cycle in the superclass chain.
if (slowpath(--attempts == 0)) {
_objc_fatal("Memory corruption in class list.");
}
// 从父类中找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:
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);//里面会调用cache的insert方法
}
done_unlock:
runtimeLock.unlock();
if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
return nil;
}
return imp;
}
子类调用父类方法,缓存放在父类里,因为curClass变为了父类curClass = curClass->getSuperclass()
。
二分查找¶
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根据设置的值自动分配类型
auto base = first;
decltype(first) probe;
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);//右移一位就是除以2
uintptr_t probeValue = (uintptr_t)getName(probe);
if (keyValue == probeValue) {
// `probe` is a match.
// Rewind looking for the *first* occurrence of this value.
// This is required for correct category overrides.
//为了优先调用分类的方法,分类的方法在类的方法method_t的前面
while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
probe--;
}
return &*probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
先遍历当前类的方法列表,传参是class和sel。
方法列表是一个哈希表。方法列表是一个数组,数组里面的每一个index是经过哈希函数计算得到。index对应一个链表。
遍历列表查找的是method_t结构体,在里面折半查找。
分类也是有方法的,分类的同名方法放在前面。
本质上方法列表是一个数组,分类编译进来的时候,分类会把方法数组的前面腾空,分类的方法放在前面。
所以折半查找到的方法可能不是最终要用的方法。所以需要再进行向前查找。直到找到合适的method_t。
方法的本质是method_t
Method_t结构体中有三个成员变量:
- SEL name 方法选择器,表示方法名
- char *types 表示方法的类型编码Type Encoding,以返回值类型+参数类型的组合编码
- IMP imp 表示方法的实现,
- 第一个参数:消息的接收者
- 第二个参数:方法的
- ...代表可选参数,前面的id代表返回值
struct method_t {
SEL name; //sel 函数名
const char *types; //方法签名:方法编号 返回值 参数.v@: v是void @是对象 :是SEL
MethodListIMP imp; //imp
struct SortBySELAddress :
public std::binary_function<const method_t&,
const method_t&, bool>
{
bool operator() (const method_t& lhs,
const method_t& rhs)
{ return lhs.name < rhs.name; }
};
};
如果查找到是一个类方法:元类的方法列表中查找。
快速查找和慢速查找都没有找到,则进行动态方法解析
消息转发机制 动态特性¶
如果没有查到对于方法的 IMP 指针,则进行消息转发机制
_objc_msgForward消息转发做的几件事
1. 动态方法解析¶
动态添加方法的实现,不是消息转发。
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 (!lookUpImpOrNilTryCache(inst, sel, cls)) {
resolveInstanceMethod(inst, sel, cls);//还会掉一次实例方法的动态实现
}
}
// chances are that calling the resolver have populated the cache
// so attempt using it
return lookUpImpOrForwardTryCache(inst, sel, cls, behavior);
}
resolveMethod_locked(id inst, SEL sel, Class cls, int behavior)
拦截处理。add_method 重新给实例对象(类对象)添加方法
里面有两个方法
resolveInstanceMethod(inst, sel, cls);
resolveClassMethod(inst, sel, cls);
调用 resolveInstanceMethod:
或resolveClassMethod:
,允许用户在此时为该Class动态添加方法实现。如果实现了,则调用并返回YES,那么重新开始objc_msgSend流程。这一次对象会响应这个选择器,一般是因为它已经调用过class_addMethod。
class_addMethod本质上是在方法列表中建立SEL和新的IMP关系,新的IMP是自己做的。如果仍没实现,继续下面的动作。
动态解析的方法,同样会缓存在cache_t。
2. 消息转发流程¶
快速转发 慢速转发
a. 快速转发¶
重定向 forward(备用接收者阶段,obj_msgForward 方法的转发)找一个备用的接收者来处理消息。
如果第一层转发返回 NO ,则会进行第二层转发,调用forwardingTargetForSelector:
,把调用转发到另一个对象,这是类级别的转发,调用另一个类的相同的方法。
消息缓存在备用接收者里。
实例方法是- (id)forwardingTargetForSelector:(SEL)aSelector
类方法是+ (id)forwardingTargetForSelector:(SEL)aSelector
注意:这里不要返回self,否则会形成死循环。
b. 慢速转发 完整的消息转发¶
obj_msgForward标准消息转发阶段,消息拦截自己处理。
如果上面转发返回 nil ,则会进入这一层处理
这层会调用 methodSignatureForSelector:
尝试获得一个方法签名。如果获取不到则直接调用doesNotRecognizeSelector:
抛出异常。如果能获取到,则返回非nil,创建一个NSInvocation并传给forwardInvocation:
。
调用forwardInvocation:
方法,将上面获取到的方法签名包装成Invocation传入,如何处理就在这里面了。
通过方法签名锁定forwardInvocation 谁能处理谁处理
Invocation会绑定 target aseletor.
这是完整的消息转发,因为可以返回方法签名、动态指定调用方法的Target
好处:
还可以继续重定向,还可以修改方法主体。操作的可自由度高。
例:aspect 切面
c. 如果转发都失败¶
调用doesNotRecognizeSelector:
,crash抛出异常。
总结¶
_objc_msgForward在进行消息转发的过程中会涉及以下几个方法:
- 动态方法解析:resolveInstanceMethod:(resolveClassMethod:)
class_addMethod返回YES则处理成功
返回NO则往下走
- 快速转发:forwardingTargetForSelector:
找一个备用接受者
- 慢速转发:methodSignatureForSelector: 和 forwardInvocation:
完整消息转发
- doesNotRecognizeSelector:
一旦调用_objc_msgForward将会跳过查找IMP的过程,直接触发消息转发。
forwardInvocation和forwardingTargetForSelector的区别¶
forwardingTargetForSelector只能转一个
forwardInvocation可以转多个