IMG_5502.JPG

补充

cache_t

  • cache_t中的t一般代表结构体
  • cache_t结构体中,_originaProptCache与内部结构体互斥,它们公用了内存

image.png

LLDB调试

在得到cache_t结构体后,调用buckets()得到bucket_t结构体指针,这时可以直接操作$18[1]进行取值,那bucket是一个数组吗?

  1. (lldb) p $17.buckets()
  2. (bucket_t *) $18 = 0x0000000108e06500
  3. (lldb) p $18[1]
  4. (bucket_t) $19 = {
  5. _sel = {
  6. std::__1::atomic<objc_selector *> = "" {
  7. Value = ""
  8. }
  9. }
  10. _imp = {
  11. std::__1::atomic<unsigned long> = {
  12. Value = 49024
  13. }
  14. }
  15. }
  16. (lldb) p $19.sel()
  17. (SEL) $20 = "saySomething"

这里其实是取内存平移,$18[1]即为在原有地址基础上平移一个单位,等于$18+1,我们执行$18+1后同样可以取出saySomethingsel

  1. (lldb) p $18+1
  2. (bucket_t *) $21 = 0x0000000108e06510
  3. (lldb) p $21->sel()
  4. (SEL) $22 = "saySomething"

_bucketsAndMaybeMask

_bucketsAndMaybeMask作为cache_t的第一个成员,猜想其应该是一个指针,接着打印其值,发现Value不为空,以16进制打印输出,得到0x0000000108e06500,回顾上面的$18也是0x0000000108e06500,所以_bucketsAndMaybeMask就是bucket_t * 指针

  1. (lldb) p $17._bucketsAndMaybeMask
  2. (explicit_atomic<unsigned long>) $23 = {
  3. std::__1::atomic<unsigned long> = {
  4. Value = 4443890944
  5. }
  6. }
  7. (lldb) p/x 4443890944
  8. (long) $24 = 0x0000000108e06500

有了这个首地址,通过&操作可以得到其它bucket,在源码中也可以看到

  • _bucketsAndMaybeMask强转得到addr,addr&bucketsMask得到buckets的地址
  • _bucketsAndMaybeMask同时包含的buckets中的第一个桶地址
    1. struct bucket_t *cache_t::buckets() const
    2. {
    3. uintptr_t addr = _bucketsAndMaybeMask.load(memory_order_relaxed); // 得到addr首地址
    4. return (bucket_t *)(addr & bucketsMask); // &操作,bucketsMask为非0即111111....
    5. }
    在存储sel、imp的过程中,先找到cache_t,然后通过其存储的bucket_t *首地址内存+1查找可以insert的地址
    未命名文件-2.jpg

    insert - cache闭环流程

    **void insert(SEL sel, IMP imp, id receiver)** 在什么时候调用,断点查看**log_and_fill_cache**,再查找是谁调用了**log_and_fill_cache** -> loolupImpOrFoward
    image.png
    在objc_cache.mm文件头步有以下流程,自上而下调用,readers->objc_msgSend->cache_getImp->cache_t::insert->cache_t::destroy
    未命名文件 (4).jpg

    运行时runtime

    运行时就是代码跑起来了.被装载到内存中去了 .(你的代码保存在磁盘上没装入内存之前是个死家伙.只有跑到内 存中才变成活的).而运行时类型检查就与前面讲的编译时类型检查(或者静态类型检查)不一样.不是简单的扫描代码.而是在内存中做些操作,做些判断
    runtime调起的三种方法
    1. OC语法 -> [person saySomethind]
    2. NSObject 接口 -> isKindOfClass
    3. objc 底层接口 -> class_getInstanceSize
    建立一个person,实现两个方法,通过clang编译后.cpp文件,可以看到对应OC语法被翻译为中间层语言

    结论

  1. 上层语言经编译后都会得到一个解释
  2. 调用方法 = 消息发送 : objc_msgSend(消息接收者,消息主体(sel + 参数))
  1. LGPerson *person = [LGPerson alloc];
  2. [person sayNB];
  3. [person sayHello];
  4. LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc"));
  5. ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayNB"));
  6. ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("sayHello"));

直接模拟objc_msgSend可以调用,运行得到sayHello
image.png
objc_sendSuper底层也是结构体,包含两个参数receiver、super_class

  • 当receiver传person,super_class传LGTeacher.class时,objc_super == [person sayNB];
  • super_class传入NSObject,则先从NSObject查找

    1. struct objc_super {
    2. /// Specifies an instance of a class.
    3. __unsafe_unretained _Nonnull id receiver;//接收者,是实例
    4. __unsafe_unretained _Nonnull Class super_class;//父类
    5. /* super_class is the first class to search */
    6. };

    因此可以直接调用objc_msgSendSuper,同样打印sayHello
    image.png
    总结:

  • 消息发送,是一个非常有魅力的过程

  • Runtime是C、C++、汇编写的一套底层API

    objc_msgSend

objc_msgSend底层用汇编写的,因为方法调用频率高,汇编保证快速安全。

  1. entry _objc_msgSend
  2. p0(p0~p7接收参数)是传入的参数person,比较判断是否存在,不存在直接return
  3. 接下来将x0的的值赋给p13,即将isa赋值给p13
  4. 通过isa & mask得到类地址,赋值给p16,p16 = class
  5. 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

  1. `GetClassFromIsa_p16` 定义如下,其中的`ExtractISA`即进行&操作
  2. ```objectivec
  3. .macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */
  4. #elif __LP64__
  5. .if \needs_auth == 0 // _cache_getImp takes an authed class already
  6. mov p16, \src
  7. .else
  8. // 64-bit packed isa
  9. ExtractISA p16, \src, \auth_address
  10. .endif
  1. .macro ExtractISA
  2. and $0, $1, #ISA_MASK // & mask操作
  3. .endmacro

补充

  • buckets中存储的imp是一个uintptr_t(无符号长整型)类型数据,取出后会进行异或操作
    • return (IMP)(imp ^ (uintptr_t)cls);
    • set存储时已经进行了异或操作 c = a ^ b,即encoding操作
    • get取出时同样进行异或操作 a = c ^ c, 进行的decoding操作
    • cls 即算法中的盐slot