一. 三大阶段
1. 音讯发送
2. 动态办法解析
3. 音讯转发
二. 整体分析
可能是由于objc_msgSend调用次数太多,苹果为了效率,运用了更底层的汇编来完结objc_msgSend。
ENTRY便是个宏,界说了objc_msgSend的入口,下面代码便是objc_msgSend的底层完结。咱们一步一步解析objc_msgSend底层做了什么事。
ENTRY _objc_msgSend
UNWIND _objc_msgSend, NoFrame
MESSENGER_START
//x0是寄存器:里边放的是receiver,便是person目标
cmp x0, #0 //过程1:判别它是否小于等于0,假如小于等于0,便是person不存在,就跳LNilOrTagged
b.le LNilOrTagged //跳LNilOrTagged
ldr x13, [x0] // x13 = isa
and x16, x13, #ISA_MASK // x16 = class
LGetIsaDone:
CacheLookup NORMAL //过程3:receiver假如receiver不为空,调用CacheLookup,传入NORMAL,查找缓存
LNilOrTagged:
b.eq LReturnZero //过程2:receiver不存在就回来0
// tagged
mov x10, #0xf000000000000000
cmp x0, x10
b.hs LExtTag
adrp x10, _objc_debug_taggedpointer_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_classes@PAGEOFF
ubfx x11, x0, #60, #4
ldr x16, [x10, x11, LSL #3]
b LGetIsaDone
LExtTag:
// ext tagged
adrp x10, _objc_debug_taggedpointer_ext_classes@PAGE
add x10, x10, _objc_debug_taggedpointer_ext_classes@PAGEOFF
ubfx x11, x0, #52, #8
ldr x16, [x10, x11, LSL #3]
b LGetIsaDone
LReturnZero:
// x0 is already zero
mov x1, #0
movi d0, #0
movi d1, #0
movi d2, #0
movi d3, #0
MESSENGER_END_NIL
ret
END_ENTRY _objc_msgSend
上面的汇编主要是判别音讯接受者是否为空,假如为空就回来0,假如不为空,进入下面,查找缓存:
看到这儿,我想到了曾经我回调署理的时分都会加这个判别“if (self.delegate && [self.delegate respondsToSelector]) {}”,我同事说没必要加这个判别,现在我了解了,不加判别的确也没事,由于直接回来0,就退出了,可是我还是习气加个判别。
//过程4:
.macro CacheLookup
// x1 = SEL, x16 = isa
ldp x10, x11, [x16, #CACHE] // x10 = buckets, x11 = occupied|mask
and w12, w1, w11 // x12 = _cmd & mask //过程5:依据办法名&mask得到索引,进行查找缓存
add x12, x10, x12, LSL #4 // x12 = buckets + ((_cmd & mask)<<4)
ldp x9, x17, [x12] // {x9, x17} = *bucket
1: cmp x9, x1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp
2: // not hit: x12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0
cmp x12, x10 // wrap if bucket == buckets
b.eq 3f
ldp x9, x17, [x12, #-16]! // {x9, x17} = *--bucket
b 1b // loop
3: // wrap: x12 = first bucket, w11 = mask
add x12, x12, w11, UXTW #4 // x12 = buckets+(mask<<4)
// Clone scanning loop to miss instead of hang when cache is corrupt.
// The slow path may detect any corruption and halt later.
ldp x9, x17, [x12] // {x9, x17} = *bucket
1: cmp x9, x1 // if (bucket->sel != _cmd)
b.ne 2f // scan more
CacheHit $0 // call or return imp //过程6:假如查找到缓存就回来
2: // not hit: x12 = not-hit bucket
CheckMiss $0 // miss if bucket->sel == 0 //过程7:查找不到缓存就跳CheckMiss
cmp x12, x10 // wrap if bucket == buckets
b.eq 3f
ldp x9, x17, [x12, #-16]! // {x9, x17} = *--bucket
b 1b // loop
3: // double wrap
JumpMiss $0
.endmacro
上面汇编主要判别能不能查到缓存办法,查到缓存办法就取出,回来办法完结,查不到缓存就进入如下代码:
//过程7.5:查不到缓存
.macro CheckMiss
// miss if bucket->sel == 0
.if $0 == GETIMP
cbz x9, LGetImpMiss
.elseif $0 == NORMAL //过程8:调用__objc_msgSend_uncached
cbz x9, __objc_msgSend_uncached
.elseif $0 == LOOKUP
cbz x9, __objc_msgLookup_uncached
.else
.abort oops
.endif
.endmacro
上面汇编,再进入如下代码:
//过程9:
STATIC_ENTRY __objc_msgSend_uncached
UNWIND __objc_msgSend_uncached, FrameWithNoSaves
// THIS IS NOT A CALLABLE C FUNCTION
// Out-of-band x16 is the class to search
//过程10:调用MethodTableLookup
MethodTableLookup
br x17
END_ENTRY __objc_msgSend_uncached
上面汇编,再进入如下代码:
//办法表格查找
//过程11:
.macro MethodTableLookup
// push frame
stp fp, lr, [sp, #-16]!
mov fp, sp
// save parameter registers: x0..x8, q0..q7
sub sp, sp, #(10*8 + 8*16)
stp q0, q1, [sp, #(0*16)]
stp q2, q3, [sp, #(2*16)]
stp q4, q5, [sp, #(4*16)]
stp q6, q7, [sp, #(6*16)]
stp x0, x1, [sp, #(8*16+0*8)]
stp x2, x3, [sp, #(8*16+2*8)]
stp x4, x5, [sp, #(8*16+4*8)]
stp x6, x7, [sp, #(8*16+6*8)]
str x8, [sp, #(8*16+8*8)]
// receiver and selector already in x0 and x1
mov x2, x16
//过程12:
//跳转调用__class_lookupMethodAndLoadCache3
//汇编没完结这个办法,所以查找c言语_class_lookupMethodAndLoadCache3,(汇编的多了一个_)
bl __class_lookupMethodAndLoadCache3
// imp in x0
mov x17, x0
// restore registers and return
ldp q0, q1, [sp, #(0*16)]
ldp q2, q3, [sp, #(2*16)]
ldp q4, q5, [sp, #(4*16)]
ldp q6, q7, [sp, #(6*16)]
ldp x0, x1, [sp, #(8*16+0*8)]
ldp x2, x3, [sp, #(8*16+2*8)]
ldp x4, x5, [sp, #(8*16+4*8)]
ldp x6, x7, [sp, #(8*16+6*8)]
ldr x8, [sp, #(8*16+8*8)]
mov sp, fp
ldp fp, lr, [sp], #16
.endmacro
上面汇编跳转到c函数_class_lookupMethodAndLoadCache3,如下:
IMP _class_lookupMethodAndLoadCache3(id obj, SEL sel, Class cls)
{
//过程13:
return lookUpImpOrForward(cls, sel, obj,
YES/*initialize*/, NO/*cache*/, YES/*resolver*/);
}
再跳转到函数:lookUpImpOrForward(查找办法完结或许音讯转发),如下:
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = nil;
bool triedResolver = NO;
runtimeLock.assertUnlocked();
//过程14:
//刚才汇编里边现已查找过缓存了,这儿传的是NO
// Optimistic cache lookup
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
// 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.read();
if (!cls->isRealized()) {
// Drop the read-lock and acquire the write-lock.
// realizeClass() checks isRealized() again to prevent
// a race while the lock is down.
runtimeLock.unlockRead();
runtimeLock.write();
realizeClass(cls);
runtimeLock.unlockWrite();
runtimeLock.read();
}
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
// 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
}
//过程41:动态办法解析完结会再次回到这儿,再次走一遍objc_msgSend的第一个阶段:音讯发送阶段,假如你动态增加办法,这次就一定会调用成功
retry:
runtimeLock.assertReading();
// Try this class's cache.
//过程15:
//再找一次缓存,怕你动态增加了一些办法
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists.
{
//过程16:
//将类目标/元类目标和办法名放进去
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
//过程21:假如找到这个办法,就填充缓存
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
//过程25:假如自己类目标里边没找到办法
// Try superclass caches and method lists.去父类的缓存和办法列表里边找
{
unsigned attempts = unreasonableClassCount();
//这个for循环便是经过superclass遍历所有的父类
for (Class curClass = cls->superclass; //过程26:拿到父类
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);//过程27:去父类缓存里边找
if (imp) { //假如找到办法
if (imp != (IMP)_objc_msgForward_impcache) {
//过程28:将父类缓存里的办法填充到当时类目标的缓存里边,现在便是父类缓存和当时类目标缓存都有这个办法
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}
//过程29:去父类的办法列表里边找
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
//过程30:假如父类办法列表里边找到,又去填充缓存
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done; //找到,就去done
}
}
}
//过程32:到上面的for循环完毕objc_msgSend的第一个阶段:音讯发送阶段,现已完结
// No implementation found. Try method resolver once.动态办法解析阶段
//假如曾经没有进行动态解析过才进行动态解析
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);//过程33:进入动态办法解析阶段
runtimeLock.read();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
//过程39.5:第33步完结就会标记为YES(不管你有没有自己完结动态解析的那两个办法),下次就不会动态解析了
triedResolver = YES;
goto retry;//过程40:动态办法解析完结,再次进入上面retry
}
// No implementation found, and method resolver didn't help.
// Use forwarding.运用音讯转发
//过程42:没找到办法,并且动态办法解析过了,会进入到音讯转发阶段
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
//过程31:
//假如找到办法,就直接return imp,然后回来到汇编代码里边去调用
done:
runtimeLock.unlockRead();
return imp;
}
看上面函数名也能看出来,lookUpImpOrForward函数便是objc_msgSend函数的核心完结,包含了音讯发送,动态办法解析,音讯转发。
下面咱们一步一步分析:
三. 详细分析
1. 音讯发送
① 先查找当时类目标或许元类目标
// Try this class's cache.
//过程15:
//再找一次缓存,怕你动态增加了一些办法
imp = cache_getImp(cls, sel);
if (imp) goto done;
// Try this class's method lists.
{
//过程16:
//将类目标/元类目标和办法名放进去
Method meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
//过程21:假如找到这个办法,就填充缓存
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
}
能够看出,尽管汇编代码里边现已查过缓存了,可是这儿又查了一次缓存,便是怕你动态增加了一些办法。然后进入getMethodNoSuper_nolock函数,这个函数便是在当时类目标或许元类目标的办法列表(methods数组)里边查找办法(注意是当时),假如查到办法了就拿到办法的imp,goto done回来到汇编。
下面进入getMethodNoSuper_nolock函数看看究竟是怎样在类目标或许元类目标的办法列表里边查找办法的,如下:
getMethodNoSuper_nolock(Class cls, SEL sel)
{
runtimeLock.assertLocked();
assert(cls->isRealized());
// fixme nil cls?
// fixme nil sel?
//很明显是拿到class_rw_t里边的methods数组进行查找
for (auto mlists = cls->data()->methods.beginLists(),
end = cls->data()->methods.endLists();
mlists != end;
++mlists)
{
//过程17:查找
method_t *m = search_method_list(*mlists, sel);
if (m) return m;
}
return nil;
}
上面代码,很明显是先拿到class_rw_t里边的methods数组,然后进入search_method_list:
static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
//过程18:假如办法排好序了先去排好序的办法里边找
return findMethodInSortedMethodList(sel, mlist);
} else {
//过程20:
//没有排好序就线性比较,依据办法名,一个一个比较
// Linear search of unsorted method list
for (auto& meth : *mlist) {
if (meth.name == sel) return &meth;
}
}
}
上面代码,假如办法列表没排序就遍历,假如办法列表是排序过的,就进入findMethodInSortedMethodList
static method_t *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;
//过程19:
//排好序的用二分查找,效率更高
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)probe->name;
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;
}
上面函数运用了二分法查找办法。
现在咱们知道了,假如办法列表排序过了就会运用二分法查找,更快速,假如没排序过就直接遍历。
假如在当时类目标或许元类目标找到办法,就会填充缓存,然后把imp回来给汇编。 假如在当时类目标或许元类目标没有找到办法,就会循环遍历父类,如下:
② 再循环遍历父类
//过程25:假如自己类目标里边没找到办法
// Try superclass caches and method lists.去父类的缓存和办法列表里边找
{
unsigned attempts = unreasonableClassCount();
//这个for循环便是经过superclass遍历所有的父类
for (Class curClass = cls->superclass; //过程26:拿到父类
curClass != nil;
curClass = curClass->superclass)
{
// Halt if there is a cycle in the superclass chain.
if (--attempts == 0) {
_objc_fatal("Memory corruption in class list.");
}
// Superclass cache.
imp = cache_getImp(curClass, sel);//过程27:去父类缓存里边找
if (imp) { //假如找到办法
if (imp != (IMP)_objc_msgForward_impcache) {
//过程28:将父类缓存里的办法填充到当时类目标的缓存里边,现在便是父类缓存和当时类目标缓存都有这个办法
// Found the method in a superclass. Cache it in this class.
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
// Found a forward:: entry in a superclass.
// Stop searching, but don't cache yet; call method
// resolver for this class first.
break;
}
}
//过程29:去父类的办法列表里边找
// Superclass method list.
Method meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
//过程30:假如父类办法列表里边找到,又去填充缓存
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done; //找到,就去done
}
}
}
//过程32:到上面的for循环完毕objc_msgSend的第一个阶段:音讯发送阶段,现已完结
上面的代码,循环遍历父类,拿到一个父类: 先从父类的缓存里边找,假如找到办法,就把父类缓存里边的办法填入当时类目标或许元类目标的缓存,然后完毕。 再从父类的办法列表里边找(和当时类目标或许元类目标相同,也是调用getMethodNoSuper_nolock函数),假如找到办法,就把父类的办法列表里边的办法填入当时类目标或许元类目标的缓存,然后完毕。
假如上面整个循环都完结,还是没有找到办法就会进入第二阶段:动态办法解析
2. 动态办法解析
// No implementation found. Try method resolver once.动态办法解析阶段
//假如曾经没有进行动态解析过才进行动态解析
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);//过程33:进入动态办法解析阶段
runtimeLock.read();
// Don't cache the result; we don't hold the lock so it may have
// changed already. Re-do the search from scratch instead.
//过程39.5:第33步完结就会标记为YES(不管你有没有自己完结动态解析的那两个办法),下次就不会动态解析了
triedResolver = YES;
goto retry;//过程40:动态办法解析完结,再次进入上面retry
}
上面函数传入了triedResolver,默以为NO,表明没进行过动态办法解析,然后进入动态办法解析,解析完结后会设置triedResolver = YES;表明动态办法解析过,这样下次就不会再进入动态办法解析了,然后会goto retry,回到第一阶段:音讯发送阶段,从头查找一次办法(这时分假如咱们动态增加办法了,那么再来到阶段一就能查找到办法了)。
进入动态办法解析函数_class_resolveMethod:
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {
// try [cls resolveInstanceMethod:sel]
_class_resolveInstanceMethod(cls, sel, inst);//过程34:不是元类目标,会调用上面的办法,动态增加实例办法
}
else {
// try [nonMetaClass resolveClassMethod:sel]
_class_resolveClassMethod(cls, sel, inst);//过程37:是元类目标,会调用上面的办法,动态增加类办法,
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
上面代码,假如不是元类目标,会调用[cls resolveInstanceMethod:sel]办法,进行动态增加实例办法,这个办法是咱们自己完结的。 假如是元类目标,会调用[nonMetaClass resolveClassMethod:sel]办法,进行动态增加类办法,这个办法也是咱们自己完结的。
注意:动态增加办法是把办法增加到类目标或许元类目标的办法列表里边了(methods数组)。
自此,第二阶段完结。假如音讯发送没找到办法,并且动态办法解析过了也没找到办法,会进入第三阶段:音讯转发
3. 音讯转发
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
音讯转发会调用_objc_msgForward_impcache函数,之后再填充缓存,在汇编中找到_objc_msgForward_impcache函数的完结,如下:
STATIC_ENTRY __objc_msgForward_impcache
MESSENGER_START
nop
MESSENGER_END_SLOW
// No stret specialization.
b __objc_msgForward
END_ENTRY __objc_msgForward_impcache
上面函数会再调用__objc_msgForward
ENTRY __objc_msgForward
adrp x17, __objc_forward_handler@PAGE
ldr x17, [x17, __objc_forward_handler@PAGEOFF]
br x17
END_ENTRY __objc_msgForward
最后只找到_objc_forward_handler是个指向函数的指针,说明会调用这个指针指向的函数,可是指向哪个函数就不知道了,由于后边的代码不开源了。
void *_objc_forward_handler = nil;
或许你能够打断点,一步一步查看汇编,看音讯转发底层是怎样完结的,可是这样难度比较大还比较费事。这儿有一份国外开发者依据汇编代码加上自己的了解变成C言语完结的一份伪代码:__forwarding__.c文件,可是这个文件还是比较难以了解,这儿有一份精简版:__forwarding__clean.c文件。
int __forwarding__(void *frameStackPointer, int isStret) {
id receiver = *(id *)frameStackPointer;
SEL sel = *(SEL *)(frameStackPointer + 8);
const char *selName = sel_getName(sel);
Class receiverClass = object_getClass(receiver);
// 调用 forwardingTargetForSelector:
if (class_respondsToSelector(receiverClass, @selector(forwardingTargetForSelector:))) {
//拿到forwardingTarget
id forwardingTarget = [receiver forwardingTargetForSelector:sel];
if (forwardingTarget && forwardingTarget != receiver) {
//给forwardingTarget发送sel音讯
return objc_msgSend(forwardingTarget, sel, ...);
}
}
// 调用 methodSignatureForSelector 获取办法签名后再调用 forwardInvocation
if (class_respondsToSelector(receiverClass, @selector(methodSignatureForSelector:))) {
//获取办法签名
NSMethodSignature *methodSignature = [receiver methodSignatureForSelector:sel];
if (methodSignature && class_respondsToSelector(receiverClass, @selector(forwardInvocation:))) {
NSInvocation *invocation = [NSInvocation _invocationWithMethodSignature:methodSignature frame:frameStackPointer];
//假如办法签名不为空,会调用forwardInvocation
[receiver forwardInvocation:invocation];
void *returnValue = NULL;
[invocation getReturnValue:&value];
return returnValue;
}
}
//假如上面两个办法回来值都是nil,会调用doesNotRecognizeSelector办法
if (class_respondsToSelector(receiverClass,@selector(doesNotRecognizeSelector:))) {
[receiver doesNotRecognizeSelector:sel];
}
// The point of no return.
kill(getpid(), 9);
}
上面代码能够看出:
- 首要判别类的forwardingTargetForSelector办法是否有回来值,假如有回来值,就拿到这个办法的回来值和SEl传给objc_msgSend(其实便是调用forwardingTargetForSelector回来目标的实例办法或许类办法)。
- 假如forwardingTargetForSelector办法回来值为空,就调用methodSignatureForSelector办法,这个办法回来一个办法签名(回来值类型、参数类型)。假如办法签名不为空,就会调用forwardInvocation办法,在forwardInvocation办法里边咱们能够做任何咱们想做的事。
- 假如methodSignatureForSelector回来的也是空,就调用doesNotRecognizeSelector报错:unrecognized selector sent to instance/class。
小疑问:
上面伪代码首要会调用__forwarding__函数,为什么会先调用这个函数呢?当调用一个目标没完结的办法时会报如下错:
2019-12-09 10:10:29.712307+0800 Interview01-音讯转发[26773:2047187] -[MJPerson test]: unrecognized selector sent to instance 0x100601e80
2019-12-09 10:10:29.769039+0800 Interview01-音讯转发[26773:2047187] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MJPerson test]: unrecognized selector sent to instance 0x100601e80'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff53be2cfd __exceptionPreprocess + 256
1 libobjc.A.dylib 0x00007fff7e2aea17 objc_exception_throw + 48
2 CoreFoundation 0x00007fff53c5cb06 -[NSObject(NSObject) __retain_OA] + 0
3 CoreFoundation 0x00007fff53b84b8f ___forwarding___ + 1485
4 CoreFoundation 0x00007fff53b84538 _CF_forwarding_prep_0 + 120
6 libdyld.dylib 0x00007fff7fa7c3d5 start + 1
7 ??? 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)
能够发现会先调用CoreFoundation结构的_CF_forwarding_prep_0,然后再调用CoreFoundation结构的___forwarding___函数。
源码地址:objc4