类的加载(下)-分类的加载

895 阅读6分钟

上文已经大致说了类的加载juejin.cn/post/692338…,本文就细致的说一下分类的方法、属性、协议如何加载到本类的

  • 准备工作

    创建几个分类

    添加一个和本类同名的方法 打断点的方式

  • 探索分类的本质

    1. 第一步先clong一下看一下编译后的分类是什么(xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 分类名称.m)
      xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc HQPerson+TestA.m发现底层分类的结构是category_t 继续搜索_OBJC_$_CATEGORY_INSTANCE_METHODS_HQPerson_$_TestA发现并没有set、get方法
    2. 总结
      1. 分类的底层实现其实就是一个category_t的结构体
      2. category_t结构体重有实例方法列表、类方法列表、协议列表、属性列表
      3. 分类中的方法列表中并没有属性的set、get方法的实现
  • 分类的加载

    从上文中juejin.cn/post/692338…知道分类的加载在attachCategories方法中attachCategories方法的执行流程如下realizeClassWithoutSwift -> methodizeClass -> attachToClass -> load_categories_nolock -> extAlloc ->attachCategories同时也知道attachCategories也是通过attachLists将分类的方法加入到本类的,所以后实现的分类的方法肯定排在类方法列表的前面,分类的加载在底层其实就是将分类中的方法、协议、属性添加到本类中,但是分类的加载时机又是怎样的呢

  • 分类加载时机

    首先第一点,分类肯定是依附于类的,如果类都不存子啊分类肯定也不会存在,类的加载分懒加载类和非懒加载类,分类的加载可以分为以下四种情况

    1. 本类是懒加载类,分类是懒加载类
      1. 第一步:先在_read_images中打断点,不出意外的发现断点不会走这里,上文中有分析如果是懒加载类的话只有第一次发送消息的时候才会加载类,现在分类也是懒加载类那肯定分类的加载也不会走_read_images
      2. 第二步:上文知道如果不是在_read_images中加载类的话会走realizeClassMaybeSwiftMaybeRelock方法,继续在realizeClassMaybeSwiftMaybeRelock打断点运行,发现果不其然来到了realizeClassMaybeSwiftMaybeRelock方法中,然后再进入到realizeClassWithoutSwift->realizeClassWithoutSwift->methodizeClass方法进行ro、rw赋值、方法排序、分类的加载等
      3. 第三步:分类的加载在methodizeClass方法中methodizeClass方法中会调用attachToClass方法加载分类attachToClass方法中又会调用attachCategories方法来开辟rwe,并对rwe进行复制,所以断点打到attachCategories方法中,但是会发现并没有走到attachCategories方法中去
      4. 第四步:没有走到attachCategories方法中去那么就猜测在编译时就加载到了类的 ro 里面,重新运行到realizeClassWithoutSwift方法中,通过打印来验证这一设想
        通过打印发现此时的ro已经含有了分类的方法了。(协议、属性如想要验证如上调试即可)
      5. 总结:本类是懒加载类,分类是懒加载类的情况下,本类依然实在第一次发送消息的时候加载,分类的加载提前到来编译时
    2. 本类是懒加载类,分类是非懒加载类
      1. 第一步:分类中添加load方法,本类中不添加load方法
      2. 第二步:运行程序发现断点会来到_read_images方法中,juejin.cn/post/692338…文章中说了懒加载类会在第一次消息转发的时候实现,这里我们发现不一定所有的懒加载类都在第一次消息转发的时候实现的,如果分类中有非懒加载分类的话,也会在read_iamge方法中加载分类的,故而找到一个程序的优化点分类的load方法要谨慎实现
      3. 第三步:在attachCategories打断点运行项目,发现断点并没有来到这里,那么可以猜测这种情况分类也是在编译时就已经加载到ro中
      4. 第四步:在realizeClassWithoutSwift方法中打断点,打印ro验证步骤三的猜想通过打印结果发现的确分类的信息是在编译时就已经加载到ro中去了
      5. 总结:本类是懒加载类,分类是非懒加载类的情况,本类会提前到read_image方法中去加载,分类同样还是在编译的时期就已经添加到ro中去了
    3. 本类是非懒加载类,分类是懒加载类
      1. 第一步:本类加载+(void)load方法(这里只将一个分类添加了load方法另外一个分类没有添加),断点调试毋庸置疑会走到_read_images方法中去,同样的在attachCategories中添加断点,发现断点并没有执行,
      2. 第二步:既然在运行时没有进入到attachCategories方法,那么同样的可以猜测是在编译时就已经添加到ro里面去了,所以继续在realizeClassWithoutSwift断点,打印ro
        发现分类的信息已经存在ro中了,至此也验证了猜想
      3. 第三步:将所有的分类都添加上load方法,继续上述的步骤发现只要有一个分类添加了load方法就会造成上述结果
      4. 总结:本类是懒加载类,分类是非懒加载类的情况下,本来还是会在read_images方法中加载,分类在编译时就已经加载完成
    4. 本类是非懒加载类,分类是非懒加载类
      1. 第一步:本类分类都添加上load方法
      2. 第二步:同样的运行程序发现断点会走到_read_images方法中(说明都是非懒加载类的情况下本类实在_read_images方法中加载的)
      3. 第三步:同样的在methodizeClass方法中打印ro,发现并没有分类的信息
      4. 第四步:继续过掉断点只在attachCategories方法中留一个断点,然后bt查看调用堆栈发现attachCategories是在load_image方法中执行的并非是在read_image就开始实现分类
      5. 第五步:通过源码验证设想,我们知道加载分类实在attachCategories方法中,全局搜索attachCategories方法发现调用的地方只有两个第一个方法是attachToClass,这个是非懒加载的时候会走的方法,另外一个调用的地方是load_categories_nolock,说明编译时会调用此方法实现分类,继续全局搜索load_categories_nolock发现又有两个地方加载,_read_images,和loadAllCategories方法_read_images是第一次加载类的地方通过断点调试发现不会走到这个地方,所以只可能是loadAllCategories方法,继续全局搜索loadAllCategories方法发现会在load_images方法中调用顿时恍然大悟
      6. 总结:本类是非懒加载类,分类是非懒加载,本类的加载还是在_read_images方法中,分类的加载推迟到了load_image方法中
  • 分类加载时机总结

    1. 本类是懒加载类,分类是懒加载类的情况下,本类依然实在第一次发送消息的时候加载,分类的加载提前到来编译时
    2. 本类是懒加载类,分类是非懒加载类的情况,本类会提前到read_image方法中去加载,分类同样还是在编译的时期就已经添加到ro中去了
    3. 本类是懒加载类,分类是非懒加载类的情况下,本来还是会在read_images方法中加载,分类在编译时就已经加载完成
    4. 本类是非懒加载类,分类是非懒加载,本类的加载还是在_read_images方法中,分类的加载推迟到了load_image方法中