补充
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 接口 -> isKindOfClass
objc 底层接口 -> 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 already
mov p16, \src
.else
// 64-bit packed isa
ExtractISA p16, \src, \auth_address
.endif
.macro ExtractISA
and $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