经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » iOS » 查看文章
iOS底层原理(五)Runtime(上)
来源:cnblogs  作者:FunkyRay  时间:2021/4/12 9:50:01  对本文有异议

什么是Runtime

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

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

方法类型的底层结构

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

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

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

  1. class method_array_t :
  2. public list_array_tt<method_t, method_list_t, method_list_t_authed_ptr>
  3. {
  4. typedef list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> Super;
  5. public:
  6. method_array_t() : Super() { }
  7. method_array_t(method_list_t *l) : Super(l) { }
  8. ....
  9. };

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

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

  1. static Class realizeClassWithoutSwift(Class cls, Class previously)
  2. {
  3. ....
  4. auto ro = (const class_ro_t *)cls->data();
  5. auto isMeta = ro->flags & RO_META;
  6. if (ro->flags & RO_FUTURE) {
  7. // This was a future class. rw data is already allocated.
  8. rw = cls->data();
  9. ro = cls->data()->ro();
  10. ASSERT(!isMeta);
  11. cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
  12. } else {
  13. // 分配内存空间,将ro的数据放到rw
  14. rw = objc::zalloc<class_rw_t>();
  15. rw->set_ro(ro);
  16. rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
  17. cls->setData(rw);
  18. }
  19. cls->cache.initializeToEmptyOrPreoptimizedInDisguise();
  20. ....
  21. }

method_t

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

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

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

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

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

  1. typedef struct objc_selector *SEL;

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

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

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

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

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

  1. - (int)test:(int)age height:(float)height
  2. {
  3. NSLog(@"%s", __func__);
  4. return 0;
  5. }
  6. // 该函数types为i24@0:8i16f20
  7. // i 返回值int类型
  8. // 24 几个返回值类型占据的大小总和(8 + 8 + 4 + 4)
  9. // @ id类型
  10. // 0 表示从第0位开始
  11. // : SEL类型
  12. // 8 从第8位开始
  13. // i 参数int类型
  14. // 16 从第16位开始
  15. // f 参数float类型
  16. // 20 从第20位开始

方法缓存

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

  1. struct cache_t {
  2. private:
  3. explicit_atomic<uintptr_t> _bucketsAndMaybeMask;
  4. union {
  5. struct {
  6. explicit_atomic<mask_t> _maybeMask; // 散列表的长度 - 1
  7. #if __LP64__
  8. uint16_t _flags;
  9. #endif
  10. uint16_t _occupied; // 已经缓存的方法数量
  11. };
  12. explicit_atomic<preopt_cache_t *> _originalPreoptCache;
  13. };
  14. ....
  15. mask_t mask() const;
  16. public:
  17. unsigned capacity() const;
  18. struct bucket_t *buckets() const; // 散列表
  19. Class cls() const;
  20. ....
  21. }

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

  1. struct bucket_t {
  2. private:
  3. // IMP-first is better for arm64e ptrauth and no worse for arm64.
  4. // SEL-first is better for armv7* and i386 and x86_64.
  5. #if __arm64__
  6. explicit_atomic<uintptr_t> _imp; // 函数的内存地址
  7. explicit_atomic<SEL> _sel; // sel为key
  8. #else
  9. explicit_atomic<SEL> _sel;
  10. explicit_atomic<uintptr_t> _imp;
  11. #endif
  12. ....
  13. }

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

  1. void cache_t::insert(SEL sel, IMP imp, id receiver) {
  2. ....
  3. mask_t newOccupied = occupied() + 1;
  4. unsigned oldCapacity = capacity(), capacity = oldCapacity;
  5. if (slowpath(isConstantEmptyCache())) {
  6. // Cache is read-only. Replace it.
  7. if (!capacity) capacity = INIT_CACHE_SIZE;
  8. reallocate(oldCapacity, capacity, /* freeOld */false);
  9. }
  10. else if (fastpath(newOccupied + CACHE_END_MARKER <= cache_fill_ratio(capacity))) {
  11. // Cache is less than 3/4 or 7/8 full. Use it as-is.
  12. }
  13. #if CACHE_ALLOW_FULL_UTILIZATION
  14. else if (capacity <= FULL_UTILIZATION_CACHE_SIZE && newOccupied + CACHE_END_MARKER <= capacity) {
  15. // Allow 100% cache utilization for small buckets. Use it as-is.
  16. }
  17. #endif
  18. else {
  19. // 如果空间已满,那么就进行扩容,乘以2倍
  20. capacity = capacity ? capacity * 2 : INIT_CACHE_SIZE;
  21. if (capacity > MAX_CACHE_SIZE) {
  22. capacity = MAX_CACHE_SIZE;
  23. }
  24. // 将旧的缓存释放,清空缓存,然后设置最新的mask值
  25. reallocate(oldCapacity, capacity, true);
  26. }
  27. bucket_t *b = buckets();
  28. mask_t m = capacity - 1;
  29. // 通过 sel&mask 计算出索引(哈希算法)
  30. mask_t begin = cache_hash(sel, m);
  31. mask_t i = begin;
  32. do {
  33. // 通过索引找到的该SEL为空,那么就插入bucket_t
  34. if (fastpath(b[i].sel() == 0)) {
  35. incrementOccupied();
  36. b[i].set<Atomic, Encoded>(b, sel, imp, cls());
  37. return;
  38. }
  39. // 用索引从bucket里面取sel和传进来的sel做比较,如果一样证明已经存有,直接返回
  40. if (b[i].sel() == sel) {
  41. return;
  42. }
  43. // 从散列表里查找,如果上述条件不成立(索引冲突),那么通过cache_next计算出新的索引再查找插入
  44. } while (fastpath((i = cache_next(i, m)) != begin));
  45. bad_cache(receiver, (SEL)sel);
  46. #endif // !DEBUG_TASK_THREADS
  47. }

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

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

  1. void cache_t::reallocate(mask_t oldCapacity, mask_t newCapacity, bool freeOld) {
  2. bucket_t *oldBuckets = buckets();
  3. bucket_t *newBuckets = allocateBuckets(newCapacity);
  4. ASSERT(newCapacity > 0);
  5. ASSERT((uintptr_t)(mask_t)(newCapacity-1) == newCapacity-1);
  6. setBucketsAndMask(newBuckets, newCapacity - 1);
  7. // 将旧的缓存和mask释放
  8. if (freeOld) {
  9. collect_free(oldBuckets, oldCapacity);
  10. }
  11. }

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

  1. static inline mask_t cache_hash(SEL sel, mask_t mask) {
  2. uintptr_t value = (uintptr_t)sel;
  3. #if CONFIG_USE_PREOPT_CACHES
  4. value ^= value >> 7;
  5. #endif
  6. return (mask_t)(value & mask);
  7. }

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

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

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

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

  1. #if CACHE_END_MARKER
  2. static inline mask_t cache_next(mask_t i, mask_t mask) {
  3. return (i+1) & mask;
  4. }
  5. #elif __arm64__
  6. // arm64架构下如果索引非0,就是i-1,索引为0返回mask
  7. static inline mask_t cache_next(mask_t i, mask_t mask) {
  8. return i ? i-1 : mask;
  9. }

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

方法调用的底层结构

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

  1. @interface Person : NSObject
  2. - (void)test;
  3. + (void)test;
  4. @end
  5. @implementation Person
  6. - (void)test
  7. {
  8. NSLog(@"%s", __func__);
  9. }
  10. + (void)test
  11. {
  12. NSLog(@"%s", __func__);
  13. }
  14. @end
  15. int main(int argc, const char * argv[]) {
  16. @autoreleasepool {
  17. Person *person = [[Person alloc] init];
  18. [person test];
  19. [Person test];
  20. }
  21. return 0;
  22. }
  23. // 转换成C++文件后的两个调用方法为:
  24. ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("test"));
  25. ((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("test"));

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

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

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

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

  1. objc_msgSend(person, @selector(test));
  2. // 消息接收者(receiver):person
  3. // 消息名称:test
  4. objc_msgSend([Person class], @selector(test));
  5. // 消息接收者(receiver):[Person class]
  6. // 消息名称:test

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

第二个参数都是testSEL就是消息的名称

方法调用的执行流程

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

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

消息发送

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

下面我们开始分析源码

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

  1. // MARK: _objc_msgSend的实现:汇编入口(ENTRY是入口的意思)
  2. ENTRY _objc_msgSend
  3. // 无窗口
  4. UNWIND _objc_msgSend, NoFrame
  5. // p0寄存器里存储的值(也就是参数receiver)和0做对比(cmp是比较的意思)
  6. cmp p0, #0 // nil check and tagged pointer check
  7. // 支持TAGGED_POINTERS的流程
  8. #if SUPPORT_TAGGED_POINTERS
  9. // 如果小于等于0,则跳转LNilOrTagged
  10. b.le LNilOrTagged // (MSB tagged pointer looks negative)
  11. #else
  12. // 如果等于0,则跳转LReturnZero
  13. b.eq LReturnZero
  14. #endif
  15. // 根据对象拿出isa ,即从x0寄存器指向的地址 取出 isa,存入 p13寄存器
  16. ldr p13, [x0] // p13 = isa
  17. // 在64位架构下通过 p16 = isa(p13) & ISA_MASK,拿出shiftcls信息,得到class信息
  18. GetClassFromIsa_p16 p13, 1, x0 // p16 = class
  19. LGetIsaDone:
  20. // calls imp or objc_msgSend_uncached
  21. // 如果从缓存中找不到,则跳转__objc_msgSend_uncached
  22. CacheLookup NORMAL, _objc_msgSend, __objc_msgSend_uncached
  23. #if SUPPORT_TAGGED_POINTERS
  24. LNilOrTagged:
  25. // 如果等于0,则跳转LReturnZero
  26. b.eq LReturnZero // nil check
  27. GetTaggedClass
  28. b LGetIsaDone
  29. // SUPPORT_TAGGED_POINTERS
  30. #endif
  31. LReturnZero:
  32. // 下面几个寄存器都归零
  33. // x0 is already zero
  34. mov x1, #0
  35. movi d0, #0
  36. movi d1, #0
  37. movi d2, #0
  38. movi d3, #0
  39. ret
  40. END_ENTRY _objc_msgSend
  41. ENTRY _objc_msgLookup
  42. UNWIND _objc_msgLookup, NoFrame
  43. cmp p0, #0 // nil check and tagged pointer check
  44. #if SUPPORT_TAGGED_POINTERS
  45. b.le LLookup_NilOrTagged // (MSB tagged pointer looks negative)
  46. #else
  47. b.eq LLookup_Nil
  48. #endif
  49. ldr p13, [x0] // p13 = isa
  50. GetClassFromIsa_p16 p13, 1, x0 // p16 = class
  51. LLookup_GetIsaDone:
  52. // returns imp
  53. CacheLookup LOOKUP, _objc_msgLookup, __objc_msgLookup_uncached
  54. #if SUPPORT_TAGGED_POINTERS
  55. LLookup_NilOrTagged:
  56. b.eq LLookup_Nil // nil check
  57. GetTaggedClass
  58. b LLookup_GetIsaDone
  59. // SUPPORT_TAGGED_POINTERS
  60. #endif
  61. LLookup_Nil:
  62. adr x17, __objc_msgNil
  63. SignAsImp x17
  64. ret
  65. END_ENTRY _objc_msgLookup
  66. STATIC_ENTRY __objc_msgNil
  67. // x0 is already zero
  68. mov x1, #0
  69. movi d0, #0
  70. movi d1, #0
  71. movi d2, #0
  72. movi d3, #0
  73. ret
  74. END_ENTRY __objc_msgNil
  75. ENTRY _objc_msgSendSuper
  76. UNWIND _objc_msgSendSuper, NoFrame
  77. ldp p0, p16, [x0] // p0 = real receiver, p16 = class
  78. b L_objc_msgSendSuper2_body
  79. END_ENTRY _objc_msgSendSuper

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

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

  1. // MARK: GetClassFromIsa_p16
  2. // 宏
  3. .macro GetClassFromIsa_p16 src, needs_auth, auth_address /* note: auth_address is not required if !needs_auth */
  4. #if SUPPORT_INDEXED_ISA
  5. // 将isa的值存入p16寄存器
  6. mov p16, \src // optimistically set dst = src
  7. tbz p16, #ISA_INDEX_IS_NPI_BIT, 1f // done if not non-pointer isa
  8. // isa in p16 is indexed
  9. // 将_objc_indexed_classes所在的页的基址 读入x10寄存器
  10. adrp x10, _objc_indexed_classes@PAGE
  11. // x10 = x10 + _objc_indexed_classes(page中的偏移量)(x10基址 根据 偏移量 进行 内存偏移)
  12. add x10, x10, _objc_indexed_classes@PAGEOFF
  13. // 从p16的第ISA_INDEX_SHIFT位开始,提取 ISA_INDEX_BITS 位 到 p16寄存器,剩余的高位用0补充
  14. ubfx p16, p16, #ISA_INDEX_SHIFT, #ISA_INDEX_BITS // extract index
  15. ldr p16, [x10, p16, UXTP #PTRSHIFT] // load class from array
  16. 1:
  17. #elif __LP64__
  18. .if \needs_auth == 0 // _cache_getImp takes an authed class already
  19. mov p16, \src
  20. .else
  21. // 64-bit packed isa
  22. ExtractISA p16, \src, \auth_address
  23. .endif
  24. #else
  25. // 32-bit raw isa
  26. mov p16, \src
  27. #endif
  28. .endmacro

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

  1. // MARK: CacheLookup
  2. .macro CacheLookup Mode, Function, MissLabelDynamic, MissLabelConstant
  3. //
  4. // Restart protocol:
  5. //
  6. // As soon as we're past the LLookupStart\Function label we may have
  7. // loaded an invalid cache pointer or mask.
  8. //
  9. // When task_restartable_ranges_synchronize() is called,
  10. // (or when a signal hits us) before we're past LLookupEnd\Function,
  11. // then our PC will be reset to LLookupRecover\Function which forcefully
  12. // jumps to the cache-miss codepath which have the following
  13. // requirements:
  14. //
  15. // GETIMP:
  16. // The cache-miss is just returning NULL (setting x0 to 0)
  17. //
  18. // NORMAL and LOOKUP:
  19. // - x0 contains the receiver
  20. // - x1 contains the selector
  21. // - x16 contains the isa
  22. // - other registers are set as per calling conventions
  23. //
  24. mov x15, x16 // stash the original isa
  25. LLookupStart\Function:
  26. // p1 = SEL, p16 = isa
  27. // mac os或者模拟器
  28. #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
  29. ldr p10, [x16, #CACHE] // p10 = mask|buckets
  30. lsr p11, p10, #48 // p11 = mask
  31. and p10, p10, #0xffffffffffff // p10 = buckets
  32. and w12, w1, w11 // x12 = _cmd & mask
  33. // 64位真机
  34. #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
  35. // 从x16(即isa)中取出cache 存入p11寄存器
  36. ldr p11, [x16, #CACHE] // p11 = mask|buckets
  37. #if CONFIG_USE_PREOPT_CACHES
  38. // 下面几个是通过位运算& mask得出索引存入p12
  39. #if __has_feature(ptrauth_calls)
  40. tbnz p11, #0, LLookupPreopt\Function
  41. and p10, p11, #0x0000ffffffffffff // p10 = buckets
  42. #else
  43. and p10, p11, #0x0000fffffffffffe // p10 = buckets
  44. tbnz p11, #0, LLookupPreopt\Function
  45. #endif
  46. eor p12, p1, p1, LSR #7
  47. and p12, p12, p11, LSR #48 // x12 = (_cmd ^ (_cmd >> 7)) & mask
  48. #else
  49. and p10, p11, #0x0000ffffffffffff // p10 = buckets
  50. and p12, p1, p11, LSR #48 // x12 = _cmd & mask
  51. #endif // CONFIG_USE_PREOPT_CACHES
  52. // 非64位真机
  53. #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
  54. ldr p11, [x16, #CACHE] // p11 = mask|buckets
  55. and p10, p11, #~0xf // p10 = buckets
  56. and p11, p11, #0xf // p11 = maskShift
  57. mov p12, #0xffff
  58. lsr p11, p12, p11 // p11 = mask = 0xffff >> p11
  59. and p12, p1, p11 // x12 = _cmd & mask
  60. #else
  61. #error Unsupported cache mask storage for ARM64.
  62. #endif
  63. // #define PTRSHIFT 3
  64. // LSL #(1+PTRSHIFT)-- 实际含义就是得到一个bucket占用的内存大小
  65. add p13, p10, p12, LSL #(1+PTRSHIFT)
  66. // p13 = buckets + ((_cmd & mask) << (1+PTRSHIFT))
  67. // do {
  68. // 从x13(即p13)中取出 bucket 分别将imp和sel 存入 p17(存储imp) 和 p9(存储sel)
  69. 1: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
  70. // 比较 sel 与 p1(传入的参数cmd)
  71. cmp p9, p1 // if (sel != _cmd) {
  72. // ne == not equal,请跳转至 3f
  73. b.ne 3f // scan more
  74. // } else {
  75. // 如果相等 即CacheHit 缓存命中,直接返回imp
  76. 2: CacheHit \Mode // hit: call or return imp
  77. // }
  78. 3: cbz p9, \MissLabelDynamic // if (sel == 0) goto Miss;
  79. cmp p13, p10 // } while (bucket >= buckets)
  80. b.hs 1b
  81. // wrap-around:
  82. // p10 = first bucket
  83. // p11 = mask (and maybe other bits on LP64)
  84. // p12 = _cmd & mask
  85. //
  86. // A full cache can happen with CACHE_ALLOW_FULL_UTILIZATION.
  87. // So stop when we circle back to the first probed bucket
  88. // rather than when hitting the first bucket again.
  89. //
  90. // Note that we might probe the initial bucket twice
  91. // when the first probed slot is the last entry.
  92. #if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16_BIG_ADDRS
  93. add p13, p10, w11, UXTW #(1+PTRSHIFT)
  94. // p13 = buckets + (mask << 1+PTRSHIFT)
  95. #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
  96. add p13, p10, p11, LSR #(48 - (1+PTRSHIFT))
  97. // p13 = buckets + (mask << 1+PTRSHIFT)
  98. // see comment about maskZeroBits
  99. #elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
  100. add p13, p10, p11, LSL #(1+PTRSHIFT)
  101. // p13 = buckets + (mask << 1+PTRSHIFT)
  102. #else
  103. #error Unsupported cache mask storage for ARM64.
  104. #endif
  105. add p12, p10, p12, LSL #(1+PTRSHIFT)
  106. // p12 = first probed bucket
  107. // do {
  108. 4: ldp p17, p9, [x13], #-BUCKET_SIZE // {imp, sel} = *bucket--
  109. cmp p9, p1 // if (sel == _cmd)
  110. b.eq 2b // goto hit
  111. cmp p9, #0 // } while (sel != 0 &&
  112. ccmp p13, p12, #0, ne // bucket > first_probed)
  113. b.hi 4b
  114. LLookupEnd\Function:
  115. LLookupRecover\Function:
  116. b \MissLabelDynamic
  117. #if CONFIG_USE_PREOPT_CACHES
  118. #if CACHE_MASK_STORAGE != CACHE_MASK_STORAGE_HIGH_16
  119. #error config unsupported
  120. #endif
  121. LLookupPreopt\Function:
  122. #if __has_feature(ptrauth_calls)
  123. and p10, p11, #0x007ffffffffffffe // p10 = buckets
  124. autdb x10, x16 // auth as early as possible
  125. #endif
  126. // x12 = (_cmd - first_shared_cache_sel)
  127. adrp x9, _MagicSelRef@PAGE
  128. ldr p9, [x9, _MagicSelRef@PAGEOFF]
  129. sub p12, p1, p9
  130. // w9 = ((_cmd - first_shared_cache_sel) >> hash_shift & hash_mask)
  131. #if __has_feature(ptrauth_calls)
  132. // bits 63..60 of x11 are the number of bits in hash_mask
  133. // bits 59..55 of x11 is hash_shift
  134. lsr x17, x11, #55 // w17 = (hash_shift, ...)
  135. lsr w9, w12, w17 // >>= shift
  136. lsr x17, x11, #60 // w17 = mask_bits
  137. mov x11, #0x7fff
  138. lsr x11, x11, x17 // p11 = mask (0x7fff >> mask_bits)
  139. and x9, x9, x11 // &= mask
  140. #else
  141. // bits 63..53 of x11 is hash_mask
  142. // bits 52..48 of x11 is hash_shift
  143. lsr x17, x11, #48 // w17 = (hash_shift, hash_mask)
  144. lsr w9, w12, w17 // >>= shift
  145. and x9, x9, x11, LSR #53 // &= mask
  146. #endif
  147. ldr x17, [x10, x9, LSL #3] // x17 == sel_offs | (imp_offs << 32)
  148. cmp x12, w17, uxtw
  149. .if \Mode == GETIMP
  150. b.ne \MissLabelConstant // cache miss
  151. sub x0, x16, x17, LSR #32 // imp = isa - imp_offs
  152. SignAsImp x0
  153. ret
  154. .else
  155. b.ne 5f // cache miss
  156. sub x17, x16, x17, LSR #32 // imp = isa - imp_offs
  157. .if \Mode == NORMAL
  158. br x17
  159. .elseif \Mode == LOOKUP
  160. orr x16, x16, #3 // for instrumentation, note that we hit a constant cache
  161. SignAsImp x17
  162. ret
  163. .else
  164. .abort unhandled mode \Mode
  165. .endif
  166. 5: ldursw x9, [x10, #-8] // offset -8 is the fallback offset
  167. add x16, x16, x9 // compute the fallback isa
  168. b LLookupStart\Function // lookup again with a new isa
  169. .endif
  170. #endif // CONFIG_USE_PREOPT_CACHES
  171. .endmacro

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

  1. // MARK: __objc_msgSend_uncached
  2. STATIC_ENTRY __objc_msgSend_uncached
  3. UNWIND __objc_msgSend_uncached, FrameWithNoSaves
  4. // THIS IS NOT A CALLABLE C FUNCTION
  5. // Out-of-band p15 is the class to search
  6. // 跳转到MethodTableLookup
  7. MethodTableLookup
  8. TailCallFunctionPointer x17
  9. END_ENTRY __objc_msgSend_uncached
  10. STATIC_ENTRY __objc_msgLookup_uncached
  11. UNWIND __objc_msgLookup_uncached, FrameWithNoSaves
  12. // THIS IS NOT A CALLABLE C FUNCTION
  13. // Out-of-band p15 is the class to search
  14. MethodTableLookup
  15. ret
  16. END_ENTRY __objc_msgLookup_uncached
  17. STATIC_ENTRY _cache_getImp
  18. GetClassFromIsa_p16 p0, 0
  19. CacheLookup GETIMP, _cache_getImp, LGetImpMissDynamic, LGetImpMissConstant
  20. LGetImpMissDynamic:
  21. mov p0, #0
  22. ret
  23. LGetImpMissConstant:
  24. mov p0, p2
  25. ret
  26. END_ENTRY _cache_getImp

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

  1. // MARK: MethodTableLookup
  2. .macro MethodTableLookup
  3. SAVE_REGS MSGSEND
  4. // lookUpImpOrForward(obj, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER)
  5. // receiver and selector already in x0 and x1
  6. mov x2, x16
  7. mov x3, #3
  8. // 跳转到C语言函数 lookUpImpOrForward
  9. bl _lookUpImpOrForward
  10. // IMP in x0
  11. mov x17, x0
  12. RESTORE_REGS MSGSEND
  13. .endmacro
  14. // MARK: __objc_msgSend_uncached
  15. STATIC_ENTRY __objc_msgSend_uncached
  16. UNWIND __objc_msgSend_uncached, FrameWithNoSaves
  17. // THIS IS NOT A CALLABLE C FUNCTION
  18. // Out-of-band p15 is the class to search
  19. // 跳转到MethodTableLookup
  20. MethodTableLookup
  21. TailCallFunctionPointer x17
  22. END_ENTRY __objc_msgSend_uncached

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

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

  1. NEVER_INLINE
  2. IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
  3. {
  4. // 定义的消息转发
  5. const IMP forward_imp = (IMP)_objc_msgForward_impcache;
  6. IMP imp = nil;
  7. Class curClass;
  8. runtimeLock.assertUnlocked();
  9. // 判断类是否初始化,如果没有,需要先初始化
  10. if (slowpath(!cls->isInitialized())) {
  11. behavior |= LOOKUP_NOCACHE;
  12. }
  13. // runtimeLock is held during isRealized and isInitialized checking
  14. // to prevent races against concurrent realization.
  15. // runtimeLock is held during method search to make
  16. // method-lookup + cache-fill atomic with respect to method addition.
  17. // Otherwise, a category could be added but ignored indefinitely because
  18. // the cache was re-filled with the old value after the cache flush on
  19. // behalf of the category.
  20. runtimeLock.lock();
  21. // We don't want people to be able to craft a binary blob that looks like
  22. // a class but really isn't one and do a CFI attack.
  23. //
  24. // To make these harder we want to make sure this is a class that was
  25. // either built into the binary or legitimately registered through
  26. // objc_duplicateClass, objc_initializeClassPair or objc_allocateClassPair.
  27. checkIsKnownClass(cls);
  28. cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
  29. // runtimeLock may have been dropped but is now locked again
  30. runtimeLock.assertLocked();
  31. curClass = cls;
  32. // The code used to lookup the class's cache again right after
  33. // we take the lock but for the vast majority of the cases
  34. // evidence shows this is a miss most of the time, hence a time loss.
  35. //
  36. // The only codepath calling into this without having performed some
  37. // kind of cache lookup is class_getInstanceMethod().
  38. // 查找类的缓存
  39. // unreasonableClassCount -- 表示类的迭代的上限
  40. for (unsigned attempts = unreasonableClassCount();;) {
  41. if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
  42. #if CONFIG_USE_PREOPT_CACHES
  43. // 又会在缓存里找一次,如果找的就返回
  44. imp = cache_getImp(curClass, sel);
  45. if (imp) goto done_unlock;
  46. curClass = curClass->cache.preoptFallbackClass();
  47. #endif
  48. } else {
  49. // 当前类方法列表(采用二分法查找)
  50. Method meth = getMethodNoSuper_nolock(curClass, sel);
  51. if (meth) { // 如果存在,取出imp,存到缓存中
  52. imp = meth->imp(false);
  53. goto done;
  54. }
  55. // 当前类 = 当前类的父类,并判断父类是否为nil
  56. if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
  57. // 未找到方法实现,方法解析器也不行,使用转发
  58. imp = forward_imp;
  59. break;
  60. }
  61. }
  62. // 如果父类链中存在循环,则停止
  63. if (slowpath(--attempts == 0)) {
  64. _objc_fatal("Memory corruption in class list.");
  65. }
  66. // 父类缓存
  67. imp = cache_getImp(curClass, sel);
  68. if (slowpath(imp == forward_imp)) {
  69. // 如果在父类中找到了forward,则停止查找,且不缓存,首先调用此类的方法解析器
  70. break;
  71. }
  72. if (fastpath(imp)) {
  73. // 如果在父类中,找到了此方法,将其存储到cache中
  74. goto done;
  75. }
  76. }
  77. // 没有找到方法实现,尝试一次方法解析
  78. if (slowpath(behavior & LOOKUP_RESOLVER)) {
  79. behavior ^= LOOKUP_RESOLVER;
  80. return resolveMethod_locked(inst, sel, cls, behavior);
  81. }
  82. done:
  83. if (fastpath((behavior & LOOKUP_NOCACHE) == 0)) {
  84. #if CONFIG_USE_PREOPT_CACHES
  85. while (cls->cache.isConstantOptimizedCache(/* strict */true)) {
  86. cls = cls->cache.preoptFallbackClass();
  87. }
  88. #endif
  89. // 将方法填充到缓存中
  90. log_and_fill_cache(cls, imp, sel, inst, curClass);
  91. }
  92. done_unlock:
  93. // 解锁
  94. runtimeLock.unlock();
  95. if (slowpath((behavior & LOOKUP_NIL) && imp == forward_imp)) {
  96. return nil;
  97. }
  98. return imp;
  99. }

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

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

  1. STATIC_ENTRY _cache_getImp
  2. GetClassFromIsa_p16 p0, 0
  3. CacheLookup GETIMP, _cache_getImp, LGetImpMissDynamic, LGetImpMissConstant
  4. LGetImpMissDynamic:
  5. mov p0, #0
  6. ret
  7. LGetImpMissConstant:
  8. mov p0, p2
  9. ret
  10. END_ENTRY _cache_getImp

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

  1. static method_t
  2. *getMethodNoSuper_nolock(Class cls, SEL sel) {
  3. runtimeLock.assertLocked();
  4. ASSERT(cls->isRealized());
  5. // fixme nil cls?
  6. // fixme nil sel?
  7. // 从类对象里拿到class_rw_t的methods
  8. auto const methods = cls->data()->methods();
  9. for (auto mlists = methods.beginLists(),
  10. end = methods.endLists();
  11. mlists != end;
  12. ++mlists)
  13. {
  14. // <rdar://problem/46904873> getMethodNoSuper_nolock is the hottest
  15. // caller of search_method_list, inlining it turns
  16. // getMethodNoSuper_nolock into a frame-less function and eliminates
  17. // any store from this codepath.
  18. method_t *m = search_method_list_inline(*mlists, sel);
  19. if (m) return m;
  20. }
  21. return nil;
  22. }

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

  1. ALWAYS_INLINE static
  2. method_t *search_method_list_inline(const method_list_t *mlist, SEL sel) {
  3. int methodListIsFixedUp = mlist->isFixedUp();
  4. int methodListHasExpectedSize = mlist->isExpectedSize();
  5. // 如果排好序的就用二分查找
  6. if (fastpath(methodListIsFixedUp && methodListHasExpectedSize)) {
  7. return findMethodInSortedMethodList(sel, mlist);
  8. } else { // 线性查找,就是一个个找
  9. // Linear search of unsorted method list
  10. if (auto *m = findMethodInUnsortedMethodList(sel, mlist))
  11. return m;
  12. }
  13. #if DEBUG
  14. // sanity-check negative results
  15. if (mlist->isFixedUp()) {
  16. for (auto& meth : *mlist) {
  17. if (meth.name() == sel) {
  18. _objc_fatal("linear search worked when binary search did not");
  19. }
  20. }
  21. }
  22. #endif
  23. return nil;
  24. }

findMethodInSortedMethodList中进行二分查找

  1. template<class getNameFunc>
  2. ALWAYS_INLINE static method_t *
  3. findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName) {
  4. ASSERT(list);
  5. auto first = list->begin();
  6. auto base = first;
  7. decltype(first) probe;
  8. uintptr_t keyValue = (uintptr_t)key;
  9. uint32_t count;
  10. for (count = list->count; count != 0; count >>= 1) {
  11. probe = base + (count >> 1);
  12. uintptr_t probeValue = (uintptr_t)getName(probe);
  13. if (keyValue == probeValue) {
  14. // `probe` is a match.
  15. // Rewind looking for the *first* occurrence of this value.
  16. // This is required for correct category overrides.
  17. while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
  18. probe--;
  19. }
  20. return &*probe;
  21. }
  22. if (keyValue > probeValue) {
  23. base = probe + 1;
  24. count--;
  25. }
  26. }
  27. return nil;
  28. }

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

  1. template<class getNameFunc>
  2. ALWAYS_INLINE static method_t *
  3. findMethodInUnsortedMethodList(SEL sel, const method_list_t *list, const getNameFunc &getName)
  4. {
  5. for (auto& meth : *list) {
  6. if (getName(meth) == sel) return &meth;
  7. }
  8. return nil;
  9. }
  10. ALWAYS_INLINE static method_t *
  11. findMethodInUnsortedMethodList(SEL key, const method_list_t *list)
  12. {
  13. if (list->isSmallList()) {
  14. if (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS && objc::inSharedCache((uintptr_t)list)) {
  15. return findMethodInUnsortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSEL(); });
  16. } else {
  17. return findMethodInUnsortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSELRef(); });
  18. }
  19. } else {
  20. return findMethodInUnsortedMethodList(key, list, [](method_t &m) { return m.big().name; });
  21. }
  22. }

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

  1. static void
  2. log_and_fill_cache(Class cls, IMP imp, SEL sel, id receiver, Class implementer) {
  3. #if SUPPORT_MESSAGE_LOGGING
  4. if (slowpath(objcMsgLogEnabled && implementer)) {
  5. bool cacheIt = logMessageSend(implementer->isMetaClass(),
  6. cls->nameForLogging(),
  7. implementer->nameForLogging(),
  8. sel);
  9. if (!cacheIt) return;
  10. }
  11. #endif
  12. cls->cache.insert(sel, imp, receiver);
  13. }
总结

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

原文链接:http://www.cnblogs.com/funkyRay/p/ios-di-ceng-yuan-li-wuruntime-shang.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号