您当前的位置:首页 > 其他新闻 >

Ios runtime「ios上网」

2023-05-16 来源:搜狐 分类:其他新闻

什么是Runtime

Objective-C是一门动态性比较强的编程语言,跟C、C等语言有着很大的不同;Objective-C的动态性是由Runtime API来支撑的

Runtime API提供的接口基本都是C语言的,源码由CC汇编语言编写

方法类型的底层结构

在Class对象的底层结构objc_class中,我们知道通过bits & FAST_DATA_MASK就可以得到class_rw_t类型的表结构

class_rw_t里面的methods、properties、protocols都是二维数组,是可读可写的,包含了类的初始内容、分类的内容

以method_array_t举例,里面的元素都是method_list_t类型的二维数组,每一个二维数组又是method_t类型的元素,表示每一个方法类型

class method_array_t : public list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>{typedef list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> Super; public:method_array_t() : Super() { }method_array_t(method_list_t *l) : Super(l) { }....};

class_ro_t里面的baseMethodList、baseProtocols、ivars、baseProperties是一维数组,是只读的,包含了类的初始内容

在objc源码里的头文件objc-runtime-new.mm,通过查看函数realizeClassWithoutSwift的实现,程序运行时会将class_ro_t里面的数据和分类里面的数据信息全部合并到一起放到class_rw_t里面来

static Class realizeClassWithoutSwift(Class cls, Class previously){....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 {// 分配内存空间,将ro的数据放到rwrw = objc::zalloc<class_rw_t>();rw->set_ro(ro);rw->flags = RW_REALIZED|RW_REALIZING|isMeta;cls->setData(rw);}cls->cache.initializeToEmptyOrPreoptimizedInDisguise();....}method_t

method_t是对方法函数的封装,里面包含了函数名,编码信息以及函数地址

struct method_t {static const uint32_t smallMethodListFlag = 0x80000000;method_t(const method_t &other) = delete;struct big {SEL name; // 函数名const char *types; // 编码(返回值类型、参数类型)MethodListIMP imp; // 指向函数的指针};}

IMP代表函数的具体实现,指向着该函数的地址

typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...);

SEL代表方法函数名,一般叫做选择器,底层结构跟char *类似,可以通过@selector()和sel_registerName()获得

typedef struct objc_selector *SEL;

不同类中相同名字的方法,所对应的方法选择器是相同的,可以通过sel_getName()和NSStringFromSelector()转成字符串

types包含了函数返回值、参数编码的字符串,排列顺序如下

iOS中提供了一个叫做@encode的指令,可以将具体的类型表示成字符串编码

一个函数默认会带有两个参数id _Nonnull和SEL _Nonnull,之后才是写入的参数

下面举例说明,函数的types是多少

- (int)test:(int)age height:(float)height{NSLog(@"%s", __func__);return 0;}// 该函数types为i24@0:8i16f20// i 返回值int类型// 24 几个返回值类型占据的大小总和(8 8 4 4)// @ id类型// 0 表示从第0位开始// : SEL类型// 8 从第8位开始// i 参数int类型// 16 从第16位开始// f 参数float类型// 20 从第20位开始方法缓存

Class内部结构中有个方法缓存cache_t,用散列表(哈希表)来缓存曾经调用过的方法,可以提高方法的查找速度

struct cache_t {private:explicit_atomic<uintptr_t> _bucketsAndMaybeMask;union { struct { explicit_atomic<mask_t>_maybeMask; // 散列表的长度 - 1#if __LP64__ uint16_t _flags;#endif uint16_t _occupied; // 已经缓存的方法数量 }; explicit_atomic<preopt_cache_t *> _originalPreoptCache;};....mask_t mask() const; public:unsigned capacity() const;struct bucket_t *buckets() const; // 散列表Class cls() const;....}

散列表bucket_t内部有SEL作为key和函数地址IMP一一对应

struct bucket_t {private:// IMP-first is better for arm64e ptrauth and no worse for arm64.// SEL-first is better for armv7* and i386 and x86_64.#if __arm64__explicit_atomic<uintptr_t> _imp; // 函数的内存地址explicit_atomic<SEL> _sel; // sel为key#elseexplicit_atomic<SEL> _sel;explicit_atomic<uintptr_t> _imp;#endif....}

在objc-cache.mm文件里可以查看cache_t::insert函数,是通过一套哈希算法计算出索引,然后根据索引在散列表数组里直接插入数据进行缓存

void cache_t::insert(SEL sel, IMP imp, id receiver) {....mask_t newOccupied = occupied() 1;unsigned oldCapacity = capacity(), capacity = oldCapacity;if (slowpath(isConstantEmptyCache())) {// Cache is read-only. Replace it.if (!capacity) capacity = INIT_CACHE_SIZE;reallocate(oldCapacity, capacity, /* freeOld */false);}else if (fastpath(newOccupied CACHE_END_MARKER <= cache_fill_ratio(capacity))) {// Cache is less than 3/4 or 7/8 full. Use it as-is.}#if CACHE_ALLOW_FULL_UTILIZATIONelse if (capacity <= FULL_UTILIZATION_CACHE_SIZE && newOccupied CACHE_END_MARKER <= capacity) {// Allow 100% cache utilization for small buckets. Use it as-is.}#endifelse {// 如果空间已满,那么就进行扩容,乘以2倍capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;if (capacity > MAX_CACHE_SIZE) {capacity = MAX_CACHE_SIZE;}// 将旧的缓存释放,清空缓存,然后设置最新的mask值reallocate(oldCapacity, capacity, true);}bucket_t *b = buckets();mask_t m = capacity - 1;// 通过 sel&mask 计算出索引(哈希算法)mask_t begin = cache_hash(sel, m);mask_t i = begin;do {// 通过索引找到的该SEL为空,那么就插入bucket_tif (fastpath(b[i].sel() == 0)) {incrementOccupied();b[i].set<Atomic, Encoded>(b, sel, imp, cls());return;}// 用索引从bucket里面取sel和传进来的sel做比较,如果一样证明已经存有,直接返回if (b[i].sel() == sel) {return;}// 从散列表里查找,如果上述条件不成立(索引冲突),那么通过cache_next计算出新的索引再查找插入} while (fastpath((i = cache_next(i, m)) != begin));bad_cache(receiver, (SEL)sel);#endif // !DEBUG_TASK_THREADS}

下面是cache_t::insert的一些详细调用解析

当存储空间已满时,会进行扩容,并且将旧的缓存全部释放清空,然后设置最新的mask值,mask值是散列表的存储容量-1,也正好对应散列表的索引值

void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld) {bucket_t *oldBuckets = buckets();bucket_t *newBuckets = allocateBuckets(newCapacity);ASSERT(newCapacity > 0);ASSERT((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1);setBucketsAndMask(newBuckets, newCapacity - 1); // 将旧的缓存和mask释放if (freeOld) {collect_free(oldBuckets, oldCapacity);}}

cache_hash的哈希算法就是将mask进行一次位运算,所得的索引值只会小于等于mask值

static inline mask_t cache_hash(SEL sel, mask_t mask) {uintptr_t value = (uintptr_t)sel;#if CONFIG_USE_PREOPT_CACHESvalue ^= value >> 7;#endifreturn (mask_t)(value & mask);}

如果计算出的索引在散列表中已经有了缓存数据,那么就通过cache_next更新下索引值,再去对应的位置插入缓存数据

通过源码可以看到计算方式如下:

有冲突的索引如果不为0就直接索引值减1,然后再根据新的索引值去散列表中对应插入

如果冲突的索引为0,那么直接就将mask赋值给新的索引值,再去对应查找插入

#if CACHE_END_MARKERstatic inline mask_t cache_next(mask_t i, mask_t mask) {return (i 1) & mask;}#elif __arm64__// arm64架构下如果索引非0,就是i-1,索引为0返回maskstatic inline mask_t cache_next(mask_t i, mask_t mask) {return i ? i-1 : mask;}

缓存的数据在散列表中都对应着一定的空间,所以这套查找算法就是利用了空间换时间,来增加效率

方法调用的底层结构

我们先将下面的代码通过Clang的命令生成C代码,如下所示

@interface Person : NSObject- (void)test;(void)test;@end@implementation Person- (void)test{NSLog(@"%s", __func__);}(void)test{NSLog(@"%s", __func__);}@endint main(int argc, const char * argv[]) {@autoreleasepool {Person *person = [[Person alloc] init]; [person test];[Person test];}return 0;}// 转换成C文件后的两个调用方法为:((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("test"));((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("test"));

发现函数调用最后都是转化为objc_msgSend,尤其我们可以推断出方法的调用本质就是objc_msgSend消息发送

由于sel_registerName和@selector返回值都是SEL,我们通过打印两个方法的地址是一样的,可以确定两个方法是可以等同的

NSLog(@"%p, %p", @selector(test), sel_registerName("test"));// 打印结果都是0x100003f66

我们对两个C函数简化之后,就得到以下两个方法

objc_msgSend(person, @selector(test));// 消息接收者(receiver):person// 消息名称:testobjc_msgSend([Person class], @selector(test));// 消息接收者(receiver):[Person class]// 消息名称:test

而且第一个参数分别可以写为person和[Person class],也可以称为消息的接收者

第二个参数都是test,SEL就是消息的名称

方法调用的执行流程

objc_msgSend的执行流程可以分为3大阶段

消息发送动态方法解析消息转发消息发送

我们在objc源码里全局搜索关键字objc_msgSend可以发现,消息发送的入口一开始是在objc-msg-arm64.s中通过汇编来实现第一步的

下面我们开始分析源码

【第一步】 这里主要就是判断receiver是否有值,然后对应的跳转,isa指针和ISA_MASK进行位运算获取到Class数据,并且要先去查找是否有缓存方法,如果没有则要去更深的Class数据中查找了

// MARK: _objc_msgSend的实现:汇编入口(ENTRY是入口的意思)ENTRY _objc_msgSend// 无窗口UNWIND _objc_msgSend, NoFrame// p0寄存器里存储的值(也就是参数receiver)和0做对比(cmp是比较的意思)cmpp0, #0 // nil check and tagged pointer check// 支持TAGGED_POINTERS的流程#if SUPPORT_TAGGED_POINTERS// 如果小于等于0,则跳转LNilOrTaggedb.leLNilOrTagged //(MSB tagged pointer looks negative)#else// 如果等于0,则跳转LReturnZerob.eqLReturnZero#endif// 根据对象拿出isa ,即从x0寄存器指向的地址 取出 isa,存入 p13寄存器ldrp13, [x0]// p13 = isa// 在64位架构下通过 p16 = isa(p13) & ISA_MASK,拿出shiftcls信息,得到class信息GetClassFromIsa_p16 p13, 1, x0// p16 = classLGetIsaDone:// calls imp or objc_msgSend_uncached// 如果从缓存中找不到,则跳转__objc_msgSend_uncachedCacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached#if SUPPORT_TAGGED_POINTERSLNilOrTagged:// 如果等于0,则跳转LReturnZerob.eqLReturnZero// nil checkGetTaggedClassbLGetIsaDone// SUPPORT_TAGGED_POINTERS#endifLReturnZero:// 下面几个寄存器都归零// x0 is already zeromovx1, #0movid0, #0movid1, #0movid2, #0movid3, #0retEND_ENTRY _objc_msgSendENTRY _objc_msgLookupUNWIND _objc_msgLookup, NoFramecmpp0, #0// nil check and tagged pointer check#if SUPPORT_TAGGED_POINTERSb.leLLookup_NilOrTagged//(MSB tagged pointer looks negative)#elseb.eqLLookup_Nil#endifldrp13, [x0]// p13 = isaGetClassFromIsa_p16 p13, 1, x0// p16 = classLLookup_GetIsaDone:// returns impCacheLookup LOOKUP, _objc_msgLookup, __objc_msgLookup_uncached#if SUPPORT_TAGGED_POINTERSLLookup_NilOrTagged:b.eqLLookup_Nil// nil checkGetTaggedClassbLLookup_GetIsaDone// SUPPORT_TAGGED_POINTERS#endifLLookup_Nil:adrx17, __objc_msgNilSignAsImp x17retEND_ENTRY _objc_msgLookupSTATIC_ENTRY __objc_msgNil// x0 is already zeromovx1, #0movid0, #0movid1, #0movid2, #0movid3, #0retEND_ENTRY __objc_msgNilENTRY _objc_msgSendSuperUNWIND _objc_msgSendSuper, NoFrameldpp0, p16, [x0]// p0 = real receiver, p16 = classb L_objc_msgSendSuper2_bodyEND_ENTRY _objc_msgSendSuper

下面是_objc_msgSend的一些详细调用解析

GetClassFromIsa_p16里是isa指针进行&ISA_MASK的运算过程

// MARK: GetClassFromIsa_p16// 宏.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */#if SUPPORT_INDEXED_ISA// 将isa的值存入p16寄存器movp16, src// optimistically set dst = srctbzp16, #ISA_INDEX_IS_NPI_BIT, 1f// done if not non-pointer isa// isa in p16 is indexed// 将_objc_indexed_classes所在的页的基址 读入x10寄存器adrpx10, _objc_indexed_classes@PAGE// x10 = x10 _objc_indexed_classes(page中的偏移量)(x10基址 根据 偏移量 进行 内存偏移)addx10, x10, _objc_indexed_classes@PAGEOFF// 从p16的第ISA_INDEX_SHIFT位开始,提取 ISA_INDEX_BITS 位 到 p16寄存器,剩余的高位用0补充ubfxp16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS// extract indexldrp16, [x10, p16, UXTP #PTRSHIFT]// load class from array1:#elif __LP64__.if needs_auth == 0 // _cache_getImp takes an authed class alreadymovp16, src.else// 64-bit packed isaExtractISA p16, src, auth_address.endif#else// 32-bit raw isamovp16, src#endif.endmacro

在CacheLookup里进行缓存查找的过程,主要就是SEL & MASK得出一个索引,根据索引去buckets散列表中取对应的方法数据

// MARK: CacheLookup.macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant//// Restart protocol://// As soon as we're past the LLookupStartFunction label we may have// loaded an invalid cache pointer or mask.//// When task_restartable_ranges_synchronize() is called,// (or when a signal hits us) before we're past LLookupEndFunction,// then our PC will be reset to LLookupRecoverFunction which forcefully// jumps to the cache-miss codepath which have the following// requirements://// GETIMP:// The cache-miss is just returning NULL (setting x0 to 0)//// NORMAL and LOOKUP:// - x0 contains the receiver// - x1 contains the selector// - x16 contains the isa// - other registers are set as per calling conventions//movx15, x16// stash the original isaLLookupStartFunction:// p1 = SEL, p16 = isa// mac os或者模拟器#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRSldrp10, [x16, #CACHE]// p10 = mask|bucketslsrp11, p10, #48// p11 = maskandp10, p10, #0xffffffffffff// p10 = bucketsandw12, w1, w11// x12 = _cmd & mask// 64位真机#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16// 从x16(即isa)中取出cache 存入p11寄存器ldrp11, [x16, #CACHE]// p11 = mask|buckets#if CONFIG_USE_PREOPT_CACHES// 下面几个是通过位运算& mask得出索引存入p12#if __has_feature(ptrauth_calls)tbnzp11, #0, LLookupPreoptFunctionandp10, p11, #0x0000ffffffffffff// p10 = buckets#elseandp10, p11, #0x0000fffffffffffe// p10 = bucketstbnzp11, #0, LLookupPreoptFunction#endifeorp12, p1, p1, LSR #7andp12, p12, p11, LSR #48// x12 = (_cmd ^ (_cmd >> 7)) & mask#elseandp10, p11, #0x0000ffffffffffff// p10 = bucketsandp12, p1, p11, LSR #48// x12 = _cmd & mask#endif // CONFIG_USE_PREOPT_CACHES// 非64位真机#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4ldrp11, [x16, #CACHE]// p11 = mask|bucketsandp10, p11, #~0xf// p10 = bucketsandp11, p11, #0xf// p11 = maskShiftmovp12, #0xfffflsrp11, p12, p11// p11 = mask = 0xffff >> p11andp12, p1, p11// x12 = _cmd & mask#else#error Unsupported cache mask storage for ARM64.#endif// #define PTRSHIFT 3// LSL #(1 PTRSHIFT)-- 实际含义就是得到一个bucket占用的内存大小addp13, p10, p12, LSL #(1 PTRSHIFT)// p13 = buckets ((_cmd & mask) << (1 PTRSHIFT))// do {// 从x13(即p13)中取出 bucket 分别将imp和sel 存入 p17(存储imp) 和 p9(存储sel)1:ldpp17, p9, [x13], #-BUCKET_SIZE// {imp, sel} = *bucket--// 比较 sel 与 p1(传入的参数cmd)cmpp9, p1// if (sel != _cmd) {// ne == not equal,请跳转至 3fb.ne3f// scan more// } else {// 如果相等 即CacheHit 缓存命中,直接返回imp2:CacheHit Mode// hit:call or return imp// }3:cbzp9, MissLabelDynamic// if (sel == 0) goto Miss;cmpp13, p10// } while (bucket >= buckets)b.hs1b// wrap-around:// p10 = first bucket// p11 = mask (and maybe other bits on LP64)// p12 = _cmd & mask//// A full cache can happen with CACHE_ALLOW_FULL_UTILIZATION.// So stop when we circle back to the first probed bucket// rather than when hitting the first bucket again.//// Note that we might probe the initial bucket twice// when the first probed slot is the last entry.#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRSaddp13, p10, w11, UXTW #(1 PTRSHIFT)// p13 = buckets (mask << 1 PTRSHIFT)#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16addp13, p10, p11, LSR #(48 - (1 PTRSHIFT))// p13 = buckets (mask << 1 PTRSHIFT)// see comment about maskZeroBits#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4addp13, p10, p11, LSL #(1 PTRSHIFT)// p13 = buckets (mask << 1 PTRSHIFT)#else#error Unsupported cache mask storage for ARM64.#endifaddp12, p10, p12, LSL #(1 PTRSHIFT)// p12 = first probed bucket// do {4:ldpp17, p9, [x13], #-BUCKET_SIZE// {imp, sel} = *bucket--cmpp9, p1// if (sel == _cmd)b.eq2b// goto hitcmpp9, #0// } while (sel != 0 &&ccmpp13, p12, #0, ne// bucket > first_probed)b.hi4bLLookupEndFunction:LLookupRecoverFunction:bMissLabelDynamic#if CONFIG_USE_PREOPT_CACHES#if CACHE_MASK_STORAGE != CACHE_MASK_STORAGE_HIGH_16#error config unsupported#endifLLookupPreoptFunction:#if __has_feature(ptrauth_calls)andp10, p11, #0x007ffffffffffffe// p10 = bucketsautdbx10, x16// auth as early as possible#endif// x12 = (_cmd - first_shared_cache_sel)adrpx9, _MagicSelRef@PAGEldrp9, [x9, _MagicSelRef@PAGEOFF]subp12, 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_shiftlsrx17, x11, #55// w17 = (hash_shift, ...)lsrw9, w12, w17// >>= shiftlsrx17, x11, #60// w17 = mask_bitsmovx11, #0x7ffflsrx11, x11, x17// p11 = mask (0x7fff >> mask_bits)andx9, x9, x11// &= mask#else// bits 63..53 of x11 is hash_mask// bits 52..48 of x11 is hash_shiftlsrx17, x11, #48// w17 = (hash_shift, hash_mask)lsrw9, w12, w17// >>= shiftandx9, x9, x11, LSR #53// &=mask#endifldrx17, [x10, x9, LSL #3]// x17 == sel_offs | (imp_offs << 32)cmpx12, w17, uxtw.if Mode == GETIMPb.neMissLabelConstant// cache misssubx0, x16, x17, LSR #32// imp = isa - imp_offsSignAsImp x0ret.elseb.ne5f// cache misssubx17, x16, x17, LSR #32// imp = isa - imp_offs.if Mode == NORMALbrx17.elseif Mode == LOOKUPorr x16, x16, #3 // for instrumentation, note that we hit a constant cacheSignAsImp x17ret.else.abortunhandled mode Mode.endif5:ldurswx9, [x10, #-8]// offset -8 is the fallback offsetaddx16, x16, x9// compute the fallback isabLLookupStartFunction// lookup again with a new isa.endif#endif // CONFIG_USE_PREOPT_CACHES.endmacro

【第二步】 上述一系列操作如果没有取到方法缓存,那么就会进到__objc_msgSend_uncached中

// MARK: __objc_msgSend_uncachedSTATIC_ENTRY __objc_msgSend_uncachedUNWIND __objc_msgSend_uncached, FrameWithNoSaves// THIS IS NOT A CALLABLE C FUNCTION// Out-of-band p15 is the class to search// 跳转到MethodTableLookupMethodTableLookupTailCallFunctionPointer x17END_ENTRY __objc_msgSend_uncachedSTATIC_ENTRY __objc_msgLookup_uncachedUNWIND __objc_msgLookup_uncached, FrameWithNoSaves// THIS IS NOT A CALLABLE C FUNCTION// Out-of-band p15 is the class to searchMethodTableLookupretEND_ENTRY __objc_msgLookup_uncachedSTATIC_ENTRY _cache_getImpGetClassFromIsa_p16 p0, 0CacheLookup GETIMP, _cache_getImp, LGetImpMissDynamic, LGetImpMissConstantLGetImpMissDynamic:movp0, #0retLGetImpMissConstant:movp0, p2retEND_ENTRY _cache_getImp

再进一步跳转到MethodTableLookup,发现最终会调用到C语言函数lookUpImpOrForward中

// MARK: MethodTableLookup.macro MethodTableLookupSAVE_REGS MSGSEND// lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)// receiver and selector already in x0 and x1movx2, x16movx3, #3// 跳转到C语言函数 lookUpImpOrForwardbl_lookUpImpOrForward// IMP in x0movx17, x0RESTORE_REGS MSGSEND.endmacro// MARK: __objc_msgSend_uncachedSTATIC_ENTRY __objc_msgSend_uncachedUNWIND __objc_msgSend_uncached, FrameWithNoSaves// THIS IS NOT A CALLABLE C FUNCTION// Out-of-band p15 is the class to search// 跳转到MethodTableLookupMethodTableLookupTailCallFunctionPointer x17END_ENTRY __objc_msgSend_uncached

知识点: C的函数名称对应到汇编中都会在其函数名之前再加上一个_,作为函数名称

【第三步】 跳转到objc-rumtime-new.mm的lookUpImpOrForward函数来看,会到当前类的方法列表里查找,如果没有再去父类的方法缓存以及方法列表中查找,直到找到调用为止;如果都没有找到,那么就会进入到方法解析的阶段

NEVER_INLINEIMP 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 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.lock();// We don't want people to be able to craft a binary blob that looks like// a class but really isn't one and do a CFI attack.//// To make these harder we want to make sure this is a class that was// either built into the binary or legitimately registered through// objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair.checkIsKnownClass(cls);cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);// runtimeLock may have been dropped but is now locked againruntimeLock.assertLocked();curClass = cls;// The code used to lookup the class's cache again right after// we take the lock but for the vast majority of the cases// evidence shows this is a miss most of the time, hence a time loss.//// The only codepath calling into this without having performed some// kind of cache lookup is class_getInstanceMethod().// 查找类的缓存// unreasonableClassCount -- 表示类的迭代的上限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 meth = getMethodNoSuper_nolock(curClass, sel);if (meth) { // 如果存在,取出imp,存到缓存中imp = meth->imp(false);goto done;}// 当前类 = 当前类的父类,并判断父类是否为nilif (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)) {// 如果在父类中找到了forward,则停止查找,且不缓存,首先调用此类的方法解析器break;}if (fastpath(imp)) {// 如果在父类中,找到了此方法,将其存储到cache中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_CACHESwhile (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;}

下面是lookUpImpOrForward的一些详细调用解析

1.上述函数会根据传进来的类遍历查找,而且每次都要先去_cache_getImp中查找是否有方法缓存,_cache_getImp里又会调用回CacheLookup进一步查找

STATIC_ENTRY _cache_getImpGetClassFromIsa_p16 p0, 0CacheLookup GETIMP, _cache_getImp, LGetImpMissDynamic, LGetImpMissConstantLGetImpMissDynamic:movp0, #0retLGetImpMissConstant:movp0, p2retEND_ENTRY _cache_getImp

2.在getMethodNoSuper_nolock里会找到class_rw_t的methods方法列表里进行遍历查找

static method_t *getMethodNoSuper_nolock(Class cls, SEL sel) {runtimeLock.assertLocked();ASSERT(cls->isRealized());// fixme nil cls? // fixme nil sel?// 从类对象里拿到class_rw_t的methodsauto const methods = cls->data()->methods();for (auto mlists = methods.beginLists(),end = methods.endLists(); mlists != end; mlists){// <rdar://problem/46904873> getMethodNoSuper_nolock is the hottest// caller of search_method_list, inlining it turns// getMethodNoSuper_nolock into a frame-less function and eliminates// any store from this codepath.method_t *m = search_method_list_inline(*mlists, sel);if (m) return m;}return nil;}

在search_method_list_inline里会根据排序来选择是采用二分查找还是线性查找

ALWAYS_INLINE static method_t *search_method_list_inline(const method_list_t *mlist, SEL sel) {int methodListIsFixedUp = mlist->isFixedUp();int methodListHasExpectedSize = mlist->isExpectedSize();// 如果排好序的就用二分查找if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) {return findMethodInSortedMethodList(sel, mlist);} else { // 线性查找,就是一个个找// Linear search of unsorted method listif (auto *m = findMethodInUnsortedMethodList(sel, mlist))return m;}#if DEBUG// sanity-check negative resultsif (mlist->isFixedUp()) {for (auto& meth : *mlist) {if (meth.name() == sel) {_objc_fatal("linear search worked when binary search did not");}}}#endifreturn nil;}

在findMethodInSortedMethodList中进行二分查找

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 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);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.while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {probe--;}return &*probe;}if (keyValue > probeValue) {base = probe 1;count--;}}return nil;}

在findMethodInUnsortedMethodList中进行线性查找,也就是一个个往下遍历查找

template<class getNameFunc>ALWAYS_INLINE static method_t *findMethodInUnsortedMethodList(SEL sel, const method_list_t *list, const getNameFunc &getName){for (auto& meth : *list) {if (getName(meth) == sel) return &meth;}return nil;}ALWAYS_INLINE static method_t *findMethodInUnsortedMethodList(SEL key, const method_list_t *list){if (list->isSmallList()) {if (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS && objc::inSharedCache((uintptr_t)list)) {return findMethodInUnsortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSEL(); });} else {return findMethodInUnsortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSELRef(); });}} else {return findMethodInUnsortedMethodList(key, list, [](method_t &m) { return m.big().name; });}}

3.在log_and_fill_cache里将查找到的方法插入到缓存中,最后调用到objc-cache.mm中的cache_t::insert函数,该函数的详细解析可以查看文章一开始的cache_t部分内容

static voidlog_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer) {#if SUPPORT_MESSAGE_LOGGINGif (slowpath(objcMsgLogEnabled && implementer)) {bool cacheIt = logMessageSend(implementer->isMetaClass(), cls->nameForLogging(),implementer->nameForLogging(), sel);if (!cacheIt) return;}#endifcls->cache.insert(sel, imp, receiver);}总结

整个消息发送的流程可以用下图来概述

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如有侵权行为,请第一时间联系我们修改或删除,多谢。

关于我们 |  商务洽谈 |  联系我们 |  友情链接 |  版权声明 |  商务广告
Copyright © 电竞产业网 All Rights Reserved 版权所有