补充
cache_t
cache_t中的t一般代表结构体- 在
cache_t结构体中,_originaProptCache与内部结构体互斥,它们公用了内存
LLDB调试
在得到cache_t结构体后,调用buckets()得到bucket_t结构体指针,这时可以直接操作$18[1]进行取值,那bucket是一个数组吗?
(lldb) p $17.buckets()(bucket_t *) $18 = 0x0000000108e06500(lldb) p $18[1](bucket_t) $19 = {_sel = {std::__1::atomic<objc_selector *> = "" {Value = ""}}_imp = {std::__1::atomic<unsigned long> = {Value = 49024}}}(lldb) p $19.sel()(SEL) $20 = "saySomething"
这里其实是取内存平移,$18[1]即为在原有地址基础上平移一个单位,等于$18+1,我们执行$18+1后同样可以取出saySomething的sel
(lldb) p $18+1(bucket_t *) $21 = 0x0000000108e06510(lldb) p $21->sel()(SEL) $22 = "saySomething"
_bucketsAndMaybeMask
_bucketsAndMaybeMask作为cache_t的第一个成员,猜想其应该是一个指针,接着打印其值,发现Value不为空,以16进制打印输出,得到0x0000000108e06500,回顾上面的$18也是0x0000000108e06500,所以_bucketsAndMaybeMask就是bucket_t * 指针
(lldb) p $17._bucketsAndMaybeMask(explicit_atomic<unsigned long>) $23 = {std::__1::atomic<unsigned long> = {Value = 4443890944}}(lldb) p/x 4443890944(long) $24 = 0x0000000108e06500
有了这个首地址,通过&操作可以得到其它bucket,在源码中也可以看到
- _bucketsAndMaybeMask强转得到addr,addr&bucketsMask得到buckets的地址
- _bucketsAndMaybeMask同时包含的buckets中的第一个桶地址
在存储struct bucket_t *cache_t::buckets() const{uintptr_t addr = _bucketsAndMaybeMask.load(memory_order_relaxed); // 得到addr首地址return (bucket_t *)(addr & bucketsMask); // &操作,bucketsMask为非0即111111....}
sel、imp的过程中,先找到cache_t,然后通过其存储的bucket_t *首地址内存+1查找可以insert的地址
insert - cache闭环流程
**void insert(SEL sel, IMP imp, id receiver)**在什么时候调用,断点查看**log_and_fill_cache**,再查找是谁调用了**log_and_fill_cache**-> loolupImpOrFoward
在objc_cache.mm文件头步有以下流程,自上而下调用,readers->objc_msgSend->cache_getImp->cache_t::insert->cache_t::destroy
运行时runtime
运行时就是代码跑起来了.被装载到内存中去了 .(你的代码保存在磁盘上没装入内存之前是个死家伙.只有跑到内 存中才变成活的).而运行时类型检查就与前面讲的编译时类型检查(或者静态类型检查)不一样.不是简单的扫描代码.而是在内存中做些操作,做些判断runtime调起的三种方法
建立一个OC语法 -> [person saySomethind]NSObject 接口 -> isKindOfClassobjc 底层接口 -> class_getInstanceSize
person,实现两个方法,通过clang编译后.cpp文件,可以看到对应OC语法被翻译为中间层语言结论
- 上层语言经编译后都会得到一个解释
- 调用方法 = 消息发送 :
objc_msgSend(消息接收者,消息主体(sel + 参数))
LGPerson *person = [LGPerson alloc];[person sayNB];[person sayHello];LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), 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"));
直接模拟objc_msgSend可以调用,运行得到sayHello
objc_sendSuper底层也是结构体,包含两个参数receiver、super_class
- 当receiver传person,super_class传LGTeacher.class时,objc_super == [person sayNB];
super_class传入NSObject,则先从NSObject查找
struct objc_super {/// Specifies an instance of a class.__unsafe_unretained _Nonnull id receiver;//接收者,是实例__unsafe_unretained _Nonnull Class super_class;//父类/* super_class is the first class to search */};
因此可以直接调用
objc_msgSendSuper,同样打印sayHello
总结:消息发送,是一个非常有魅力的过程
- Runtime是C、C++、汇编写的一套底层API
objc_msgSend
objc_msgSend底层用汇编写的,因为方法调用频率高,汇编保证快速安全。
- entry _objc_msgSend
- p0(p0~p7接收参数)是传入的参数person,比较判断是否存在,不存在直接return
- 接下来将x0的的值赋给p13,即将isa赋值给p13
- 通过isa & mask得到类地址,赋值给p16,p16 = class
cacheLookUp查找该class缓存中是否有imp,即cache_getImp -> cache_insert ```objectivec ENTRY _objc_msgSend UNWIND _objc_msgSend, NoFrame
cmp p0, #0 // nil check and tagged pointer check p0为寄存器,即消息接收者person
if SUPPORT_TAGGED_POINTERS // 判断是否为taggedPoint类型
b.le LNilOrTagged // (MSB tagged pointer looks negative)
else
b.eq LReturnZero
endif
ldr p13, [x0] // p13 = isa x0为person,将其地址(isa)赋值给p13 GetClassFromIsa_p16 p13, 1, x0 // p16 = class 通过isa 与 mask 操作 得到类赋值给p16 LGetIsaDone: // calls imp or objc_msgSend_uncached // cache_t的insert 被objc_msgSend调用,在探索找到Class CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
`GetClassFromIsa_p16` 定义如下,其中的`ExtractISA`即进行&操作```objectivec.macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */#elif __LP64__.if \needs_auth == 0 // _cache_getImp takes an authed class alreadymov p16, \src.else// 64-bit packed isaExtractISA p16, \src, \auth_address.endif
.macro ExtractISAand $0, $1, #ISA_MASK // & mask操作.endmacro
补充
- buckets中存储的imp是一个uintptr_t(无符号长整型)类型数据,取出后会进行异或操作
- return (IMP)(imp ^ (uintptr_t)cls);
- set存储时已经进行了异或操作 c = a ^ b,即encoding操作
- get取出时同样进行异或操作 a = c ^ c, 进行的decoding操作
- cls 即算法中的盐slot
