Onewang

iOS developer logging

生命不息,奋斗不止


主页

深入理解OC方法缓存

objc_msgSend的消息分发的步骤:

  • 判断receiver是否为nil,也就是objc_msgSend的第一个参数self,也就是要调用的哪个方法所属对象
  • 从缓存里寻找,找到了则分发,否则
  • 利用objc_class.mm中_class_lookupMethodAndLoadCache3方法去寻找selector
  • 如果支持GC,忽略掉非GC环境的方法(retain等)
  • 从本class的method list寻找selector,如果找到,填充到缓存中,并返回selector,否则
  • 寻找父类的method list,并依次往上寻找,直到找到selector,填充到缓存中,并返回selector,否则
  • 调用_class_resolveMethod,如果可以动态resolve为一个selector,不缓存,方法返回,否则
  • 转发这个selector,否则
  • 报错,抛出异常
      struct objc_cache {
          unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
          unsigned int occupied                                    OBJC2_UNAVAILABLE;
          Method _Nullable buckets[1]                              OBJC2_UNAVAILABLE;
      };
    

    objc_cache的定义包含三个变量:

  • mask:可以认为是当前能达到的最大index(从0开始),所以缓存的size(total)是mask+1
  • occupied:被占用的槽位,因为缓存是以散列表的形式存在的,所以会有空槽,而occupied表示当前被占用的数目
  • buckets:用数据表示的方法数组; Method的定义:包含方法名,方法类型以及方法实现;

缓存和散列 缓存的存储使用了散列表;为什么要用散列表呢?因为散列表检索起来比较快;

  • 方法缓存存在什么地方? obj-class.png 我们通过runtime的头文件可以看到类的定义里就有cache字段,没错,类的所有缓存都存在metaclass上,所以每个类都只有一份方法缓存,而不是每一个类的object都保存一份;

  • 父类方法的缓存只存在父类吗?还是子类也会缓存父类的方法? 在前面对objc_msgSend的追溯中我们可以看到,即便是从父类取到的方法,也会存在类本身的方法缓存里;也就是子类也会缓存父类的方法;而当用一个父类对象去调用哪个方法的时候,也会在父类的metaclass里缓存一份;

  • 类的方法缓存大小有没有限制? 当_class_slow_grow是非0值的时候,只有当方法缓存第奇数次满(使用的槽位超过3/4)的时候,方法缓存的大小才会增长(会清空缓存,否则hash值就不对了);当第偶数次满的时候,方法缓存会被清空并重新利用;如果_class_slow_grow值为0,那么每一次方法缓存满的时候,其大小都会增长; 答案是没有限制,虽然这个值被设置为1,方法缓存的大小增速会慢一点,但确实是没有上限的;

  • 为什么类的方法列表不直接做成散列表呢?做成list,还要单独缓存,多费事?
  • 散列表是没有顺序的,Objective-C的方法列表是一个list,是有顺序的;Objective-C在查找方法的时候会顺着list一次查找,并且category的方法在原始方法list的前面,需要先被找到,如果直接用hash存方法,方法的顺序就没法保证了;
  • List的方法还保存了除了selector和imp之外其他很多属性
  • 散列表是有空槽的,会浪费空间
最近的文章

数组和指针的区别

数组名和指针的区别int arr[] = {1,2,3,4,5};定义一个数组,数组名为 arr,分别打印arr 本身的地址,arr 首元素的地址,以及arr本身;发现三者的值是相同的;变量的三要素:变量名称;变量,即名为arr变量自己的地址,该地址存储了 arr 变量;arr 的值,为所指对象的值;1.数组名取地址得到的是数组名所指元素的地址;对指针取地址得到的是指针变量自身的地址;2.数组名是常量指针,指针时变量指针;总结:1.数组名代表了一个指向数组首元素的常量指针,一经定义,不可更...…

继续阅读
更早的文章

代理设计模式

NSProxy什么是NSProxy:NSProxy没有父类,是顶级类(根类),跟NSObject同等地位。NSProxy和NSObject都实现了“NSObject Protocol”。NSProxy设计时就是以“抽象类”设计的,专门为转发消息而生。实现要求:继承NSProxy的子类要实现自己的初始化方法,如“init”、“initWith”。重写- forwardInvocation:和- methodSignatureForSelector:方法,完成消息转发如此看来NSProxy确实...…

继续阅读