Category的底层架构
定义在objc-runtime-new.h中
分类相关信息合并到类或者元类源码图
源码分类合并图1里面的cats 指针数组存放的是多个分类方法的二维数组,例如[[personCategory1_(Test1)_run,personCategory1_(Test1)_eat],[personCategory2_(Test2)_run,personCategory2_(Test2)_eat]]等类似情况
源码分类合并图2可以看到,将分类的方法放到最前面,将原来的类对象的方法或者元类方法放到最后面,这个时候如果调用了同名方法,分类的必定先于本身的类的相关方法执行,但是如果两个分类中都拥有同样的方法,那么会是哪个方法先执行呢,这个和编译分类的先后顺序有关的,例如如果分类1先编译,分类2后编译,然后两个分类含有同样的方法run(),那么从合并源码图可以看出,这个赋值分类的方法是倒序遍历,所以后编译的放到最前面,所以后编译的会先执行,也就是会执行分类2的run方法;另外从源码中可以看到使用了memmove,memcpy,为什么需要使用move方法呢,直接copy过去效率不是更高么?使用move是为了确定数据的正确性,保证没有被覆盖,后者使用cp是因为数据是从别的地方拷贝过来,不存在覆盖一说,如果还是对这两个方法有点疑惑,可以参考这个链接
+load方法调用时机和调用方式
1.每个类,分类的load方法在程序运行过程中只调用一次
2.先调用类的+load方法,按照编译先后顺序调用,先编译先调用,另外调用子类的+load之前会先调用父类的+load方法
3.再调用分类的load方法,按照编译先后顺序调用(先编译,先调用)
下面先上传一张测试效果图,然后再从源码分析
load 方法调用图也许有人会疑惑,上面不是说是倒序遍历,先编译的分类,方法后调用么,这里load是特殊的,因为load调用和其他方法不一样,从源码分析:objc-os.mm->_objc_init->load_images->prepare_load_methods->schedule_class_load->add_class_to_loadable_list->add_category_loads->(*load_method)(cls,SEL_load)
load方法调用图 类方法load调用图 分类load方法调用图从上面的结果除开可以看到load方法的调用机制,还可以知道load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用
initialize方法的调用机制
+initialize 方法会在类第一次接受到消息的时候调用,测试图:
没有调用从上面可以得出结果是先调用父类的initialize,再调用子类的+initialize,并且每个类的初始化方法只会调用一次,并且分类中如果实现了initialize方法,会覆盖类本身的initialize,这也证明了initialize是根据消息发送机制调用的,找到了就不会往下传递了 ,源码寻找图:
方法寻找图 先调用父类 最终初始化调用图源码解读过程objc_msg_arm64.s->objc_msgSend objc-runtime-new.mm->class_getInstanceMethod->lookUpImpOrNil->lookUpImpOrForward->_class_initialize->callInitialize->objc_msgSend(cls,SEL_initialize)
总结补充
1.Category实现原理
Category编译之后的底层结构是struct category_t,里面存储着分类的对象方法,类方法,属性,协议信息,在程序运行的时候,runtime会将Category的数据合并到类信息中(类对象,元类对象中)
2.Category和Class Extension的却别是?
Class Extension在编译的时候,它的数据就已经包含在类信息里面了,而Category是在运行时,才会将数据合并到类信息中,所以这个时候类结构已经确定了,因此不能直接添加成员属性,但是可以通过associate的全局功能进行声明和定义
3.Category的加载处理过程是怎样的
通过runtime加载某个类的所有Category数据,然后把所有的Category的方法,属性,协议数据,合并到一个大数组中,后面参与编译的Category数据,会在数组的前面,继续将合并后的分类数据(方法,属性,协议)插入到原来的数据的前面,这个结论可以通过源码来解读,因为这些都是在运行时合并的,所以阅读源码的书序,应该从运行时的过程开始理解objc-os.mm->objc_init->map_images->map_images_nolock------->objc-runtime-new.mm-->read-images->remethodizeClass->attachCategories->attachLists->realloc,memmove,memcpy
4.initialize 和 load的区别?
+initialize是通过objc_msgSend进行调用的,load是通过地址调用的,子类没有实现+initialize会调用父类的+initialize,所以父类的initialize可能会调用多次,那是因为从源码中可以得到,如果父类没有初始化,会调用父类,然后再接着处理自己,因为自己没有没有这个重写+initialze方法,所以根据runtime往父类中查找,最后找到父类,另外initialize和load调用机制不一样,如果分类了实现+initialize,就会覆盖类本身的initialize的调用,load不会,而且load方法的调用机制和原理已经说得很清楚了
可以添加微信一起交流学习:fslskz
网友评论