深入了解category
extension看起来像是一个匿名的category,但是extension和有名字的category几乎完全是两个东西
extension在编译期决议,它就是类的一部分,在编译期和头文件里面的@interface以及实现文件里面的@implement一起形成一个完整的类,它伴随的产生而产生,消失而消失。extension一般用来隐藏类的私有消息,你必须有一个类的源码才能为一个类添加extension,所以你无法为系统的类添加extension。
category在运行期决议的
extension可以添加实例变量,而category是无法使用实例变量的。因为在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部布局。
Objective-C 对象都是 C 语言结构体实现的。在runtime层,category结构体
image.png
1、类的名字
这里看到对name的修饰是用的const,所以扩充类的名字不可以重复,不然在编译阶段的时候会编译错误。
2、类
image.png
这里指出扩展类并不是被扩充类的重新映射
3、
struct method_list_t *instanceMethods; //类方法列表
struct method_list_t *classMethods; //实例方法列表
struct protocol_list_t *protocols; //category中实现的协议的列表
struct property_list_t *instanceProperties; //实例属性列表
struct property_list_t *_classProperties; //类属性列表
4、
从注释和里面的一些逻辑来简单推测,这个修饰的对象其实只是作为一个标识符的存在,标记对应的方法列表在方法map上的位置。
5、
从源码数据结构的定义来看缺少了实例变量相关的描述,这也解释了为啥category无法添加实例变量。但是OC作为一个动态语言,所有的方法名对象声明都是在编译期组织排列好,在运行的过程中再去寻找转发所对应的实现。如果想要实现在category中添加实例变量,少年,runtime了解一下?
回到category
如果category中有与被扩展类相同的方法,调用的时候则会调用到category里面的这个方法。实际上category的方法没有“完全替换”原来的方法,如果category和原有类都有method A ,那么category附加完成之后,类的方法列表里就会有两个method A。但是在运行调用过程中,category的method A会排在方法列表更前,runtime在找到第一个符合的方法的时候就把它给return回去了,这就造成了“覆盖”的一种感觉。
category的方法被放到了新方法列表的前面,而原来类的方法被放到了新方法列表的后面。那如果说是两个category拥有相同的方法呢?这样又是谁的会先被调用?同样的,这取决于编译排列的先后。
修改Compile Sources中的category文件顺序就可以了,但是作为被扩充的类,也就是你所扩充的类无论怎么去调整顺序,它一定是会在category之前加载,也就是+load()方法一定是第一个走到的,然后才依次去加载扩充类,然后最后加载的扩充里面的方法会是最先被找到的。