objc_msgSend探索

1,198 阅读13分钟

在上一篇cache_t探索文章中,我们探索了cache_t的作用,是去进行方法缓存,其目的就是当方法再次调用时能更快的进行响应.,接下来我们探究一下如何从cache_t中读取方法. 我们在源码中 obcj-cache.m中会找到这样一个注释

* Cache readers (PC-checked by collecting_in_critical())
 * objc_msgSend*
 * cache_getImp

这说明cache读取时通过objc_msgSendcache_getImp这两个函数进行读取.

我们都知道OC是一门动态的语言,动态语言就是指我们的程序在运行的过程中可以对于我们的类、对象、属性、方法、变量进行修改,可以改变他们的数据结构,可以添加或者删除一些函数,可以去改变一些变量的值等等的操作.我们经常提到的runtime就是可以实现语言动态的一组api,runtime所有都是围绕两个核心
1:类的各方面的动态配置(使用runtime的api动态的修改我们的类或者对象的信息,为类添加属性方法,修改成员变量的值)
2:消息传递(消息的发送和消息的转发,消息的发送就是runtime通过sel找imp,然后实现对应的方法)

我们的消息发送在编译的时候,编译器就会把这个方法转换成为objc_msgSend这么一个函数,为了验证这件事情,我们在main中加入熟悉的person

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        WTPerson *p = [WTPerson alloc];
        [p say:@"hello"];
        [p run];
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

然后在终端中输入 xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc main.m,我们获取到.cpp文件,在.cpp文件中我们可以找到相应的编译器编译后的代码

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        WTPerson *p = ((WTPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("WTPerson"), sel_registerName("alloc"));
        ((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)p, sel_registerName("say:"), (NSString *)&__NSConstantStringImpl__var_folders_x6_75ftxbbd4t97nl_2t2sh7kww0000gn_T_main_bd8849_mi_0);
        ((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("run"));
    }
    return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}

我们发现,alloc这种类方法和sayrun这种对象方法都被包装成了((void (*)(id, SEL))(void *)objc_msgSend)(receiver, sel)格式.
当我们的方法都没有参数,可转换成objc_msgSend后都会带有两个默认的参数消息的接收者receiver、消息的方法名sel.类方法的接收者是类对象,他就会通过类对象的isa指针找到我们的元类,从元类中找对应的方法;实例对象方法的接收者是实例对象,他就会通过实例对象的isa指针找到我们的类对象,从类对象中找对应的方法.
当我们的方法有参数时,我们的参数会放在objc_msgSend的第三个参数及以后中,第一个和第二个参数是不变的,也就是说,objc_msgSend的参数是两个默认的参数加上方法本身的参数.

下面我们试一下直接调用objc_msgSend函数 QQ20220505-143111@2x.png 发现同样调用类say方法,也就是说我们可以直接手动去调用objc_msgSend函数.
需要注意:手动调用需要导入头文件#import <objc/message.h>

在.cpp文件中,我们可以看到objc_msgSend其实是有很多种类的

__OBJC_RW_DLLIMPORT void objc_msgSend(void); //
__OBJC_RW_DLLIMPORT void objc_msgSendSuper(void); //给父类发消息
__OBJC_RW_DLLIMPORT void objc_msgSend_stret(void); //返回值是结构体
__OBJC_RW_DLLIMPORT void objc_msgSendSuper_stret(void); //
__OBJC_RW_DLLIMPORT void objc_msgSend_fpret(void); //返回值是浮点类型

接下来我们详细了解一下objc_msgSendSuperobjc_msgSend的区别

objc_msgSendSuper

我们创建一个WTStudent类继承WTPerson,重写其init方法

- (instancetype)init {
    if (self = [super init]) {
        NSLog(@"%@", [self class]);
        NSLog(@"%@", [super class]);
    }
    return self;
}

面试的摧残让我们都知道两个打印的都是WTStudent,但是为什么呢?让我们来研究一下selfsuper这两个关键字有什么区别:
我们用clang命令编译WTStudent.m,得到如下编译代码

static instancetype _I_WTStudent_init(WTStudent * self, SEL _cmd) {
    if (self = ((WTStudent *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("WTStudent"))}, sel_registerName("init"))) {
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_x6_75ftxbbd4t97nl_2t2sh7kww0000gn_T_WTStudent_8d6cff_mi_0, ((Class (*)(id, SEL))(void *)objc_msgSend)((id)self, sel_registerName("class")));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_x6_75ftxbbd4t97nl_2t2sh7kww0000gn_T_WTStudent_8d6cff_mi_1, ((Class (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("WTStudent"))}, sel_registerName("class")));
    }
    return self;
}

我们发现self编译后是objc_msgSend,super编译后是objc_msgSendSuper,objc_msgSendSuper的第一个参数不再是self,而是__rw_objc_super的结构体指针,包括消息的接收者:self和开始搜索方法实现的超类super:(id)class_getSuperclass(objc_getClass("WTStudent")).
我们看到objc_msgSendSuper的消息的接收者仍然是self,所以[super class]打印出来的一样是self这个对象所指的类,也就是WTStudent.

通过这里,我们可以得出结论:objc_msgSendobjc_msgSendSuper唯一的区别只有他们查找方法的出发点不同,objc_msgSendSuper是从方法实现的超类super也就是类的父类开始查找,objc_msgSend是从本身开始查找.我们可以直接实现super关键字

struct objc_super {
    /// Specifies an instance of a class.
    __unsafe_unretained _Nonnull id receiver;
    /// Specifies the particular superclass of the instance to message. 
#if !defined(__cplusplus)  &&  !__OBJC2__
    /* For compatibility with old objc-runtime.h header */
    __unsafe_unretained _Nonnull Class class;
#else
    __unsafe_unretained _Nonnull Class super_class;
#endif
    /* super_class is the first class to search */
};

QQ20220505-155127@2x.png 我们可以看到,我们成功的调用了superrun方法,如果super_class改成WTStudent,调用的方法是study方法的话,不会因为super没有study方法崩溃,而会因为一直调用[WTStudent study]死循环崩溃.如果super_class改成改成NSObject则会因为NSObject没有run方法崩溃.

objc_msgSend

接下来我们来进行探索一下objc_msgSend的流程,在源码我们找到了各种系统的实现,最后我们锁定到了objc-msg-arm64.s文件中,以.s结尾的都是汇编写的文件,而arm64是我们的真机架构,objc_msgSend为什么使用汇编实现,因为汇编比C语言更快,可以免去局部变量的copy操作,参数直接被存放寄存器中,可以直接使用

//进入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
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

这一部分代码,实际上就是先检测我们调用_objc_msgSend的调用方,是否为空,如果为空,则直接去进行方法的调用,直接返回。否则就将调用方的isa指针存到p13寄存器里面去,其中比较重要的一个方法是GetClassFromIsa_p16他是通过isa来获取class,下面我们来重点看下他

.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */
#if SUPPORT_INDEXED_ISA
	// Indexed isa
	mov	p16, \src			// optimistically set dst = src
	tbz	p16, #ISA_INDEX_IS_NPI_BIT, 1f	// done if not non-pointer isa
	// isa in p16 is indexed
	adrp	x10, _objc_indexed_classes@PAGE
	add	x10, x10, _objc_indexed_classes@PAGEOFF
	ubfx	p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS  // extract index
	ldr	p16, [x10, p16, UXTP #PTRSHIFT]	// load class from array
1:

// **因为是真机环境,所以走这里**
#elif __LP64__
//如果needs_auth参数等于0,暂时不用管他的含义,在objc_msgSend中,这里传入的是1
.if \needs_auth == 0 // _cache_getImp takes an authed class already
        // ** 将src,也就是我们的入参isa指针,存放到寄存器P16中**
	mov	p16, \src
.else
	// 64-bit packed isa
        //**调用ExtractISA**
	ExtractISA p16, \src, \auth_address
.endif
#else
	// 32-bit raw isa
	mov	p16, \src

#endif
.endmacro

//**这个方法有两个实现,一个是针对A12芯片以上的手机,我们这里看A12以下的**
.macro ExtractISA
        //**实际上就是将传入的参数,对象的isa与isa_mask按位与,也就是得到Class**
	and    $0, $1, #ISA_MASK
.endmacro

这一段内容,简单来说,就是将对象的isa传入GetClassFromIsa_p16然后,这个方法针对不同的isa类型做了不同的处理,最终得到了类对象Class。在下面就是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

这一段主要是获取到我们缓存也就是之前讲过的cache_t的首地址,并且获取到bucketsmask。接着继续往下走

//去除掩码后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

这一段是查找缓存中最核心的代码,通过循环来查找方法在缓存中的位置,如果找到了则调用CacheHit \Mode,否则调用MissLabelDynamic。接下来我们看看CacheHit的实现

// CacheHit: x17 = cached IMP, x10 = address of buckets, x1 = SEL, x16 = isa
.macro CacheHit
//**objs_msgSend中$0==NORMAL**
.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
.abort oops
.endif
.endmacro

//**依旧有两个实现,我们看A12以下的**
.macro TailCallCachedImp
	// $0 = cached imp, $1 = address of cached imp, $2 = SEL, $3 = isa
        //**$0(imp) ^ $3(isa)**
        //**实际上就是一个解码的过程**
	eor	$0, $0, $3
        //**跳转到$0(imp)的地址,就是调用IMP**
	br	$0
.endmacro

比较简单的实现,就是去调用查找到的SEL对应的IMP,实现方法的调用。到此,objc_msgSend调用方法,在缓存中查找方法的流程就全部结束了,这个流程我们也称之为方法的快速查找流程,和我们之前的找到cache_t中方法的流程是一样的,只是我们之前用的是lldb进行的查找,objc_msgSend直接使用编译器方法进行的查找

_lookUpImpOrForward

快速查找未找到会跳转__objc_msgSend_uncached,在其中会跳转到MethodTableLookup,在MethodTableLookup中执行了_lookUpImpOrForward,这里进行慢速查找流程

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
STATIC_ENTRY _cache_getImp
GetClassFromIsa_p16 p0, 0
CacheLookup GETIMP, _cache_getImp, LGetImpMissDynamic, LGetImpMissConstant

.macro MethodTableLookup
SAVE_REGS MSGSEND
// lookUpImpOrForward(obj, sel, cls, 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

在源码objc-runtime-new.m中,我们找到了他的实现

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior){
    // 定义消息转发的imp
    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.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 {
            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)) {
            goto 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;
}

static method_t *getMethodNoSuper_nolock(Class cls, SEL sel){
    runtimeLock.assertLocked();
    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;
}

findMethodInSortedMethodList(SEL key, const method_list_t *list){
    if (list->isSmallList()) {
        if (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS && objc::inSharedCache((uintptr_t)list)) {
            return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSEL(); });
        } else {
            return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSELRef(); });
        }
    } else {
        return findMethodInSortedMethodList(key, list, [](method_t &m) { return m.big().name; });
    }
}

其实现就是从先判断是否缓存过,缓存过再次查找cache,未缓存就从当前类的方法列表中查找方法的实现,最后根据findMethodInSortedMethodList这个二分查找流程查找方法实现.

static method_t * findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName){
    ASSERT(list);
    auto first = list->begin();
    auto base = first; // 0
    decltype(first) probe;
    uintptr_t keyValue = (uintptr_t)key;
    uint32_t count;
    // count 16
    // 小了的情况
    // 第一次 probe = 0 + count >> 1 = 8 base = 9 count-- = 15 
    // 第二次 probe = 12 base = 13 count >>= 1 = 7 count >> 1 = 3 count-- = 6 
    // 第三次 probe = 14 count >>=1 = 3 count >> 1 = 1
    
    // 大了的情况
    // 第一次 probe = 0 + count >> 1 = 8 base = 0 count = 16 
    // 第二次 probe = 4 base = 0 count >>= 1 = 8 count >> 1 = 4 
    // 第三次 probe = 2 count >>=1 = 2 count >> 1 = 0
    for (count = list->count; count != 0; count >>= 1) {
        probe = base + (count >> 1);
        uintptr_t probeValue = (uintptr_t)getName(probe);
        if (keyValue == probeValue) {
            while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) { // 查找第一次出现的地方,为了调用分类的方法
                probe--;
            }
            return &*probe;
        }
        if (keyValue > probeValue) {
            base = probe + 1;
            count--;
        }
    }
    return nil;
}

当我们在慢速查找找到方法时,会调用log_and_fill_cache,其内部调用cls->cache.insert(sel, imp, receiver);将方法存入cache中,下次调用会进行快速查找流程,子类调用父类方法,缓存仍然会缓存到子类,因为在for循环中,如果子类没找到时curClass = curClass->getSuperclass()会将curClass更新成父类,log_and_fill_cache的写入与curClass无关,而是写入cls的cache,cls一直是不变的.

以上是找到了imp,当未找到imp,则会将最初定义消息转发的imp赋值给当前imp,进行消息转发流程

总结

objc_msgSend其具体实现如下:

1.receiver是否存在
2.reciver - isa - class
3.class - 内存偏移 - cache
4.cache - buckets - 对应sel
5.buckets 有对应的sel - cacheHit - 调用imp - 方法的快速查找流程
6.buckets 没有对应的sel - __objc_msgSend_uncached - 方法的慢速查找流程
7._lookUpImpOrForward - 先找当前类的methodlist - 再找父类的cache - 父类的methodlist - 父类为nil - forward的imp