Q:Category的实现原理,以及Category为什么只能加方法不能加属性?
实际上Category 是一个 category_t 的结构体,里面维护着类的信息和category的名称,以及类方法列表,实例方法列表,协议的列表和属性的列表
分类的结构体:
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods; // 对象方法
struct method_list_t *classMethods; // 类方法
struct protocol_list_t *protocols; // 协议
struct property_list_t *instanceProperties; // 属性
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
从category结构体的定义也可以看出category可以添加实例方法,类方法;可以遵守协议,添加属性;但无法添加实例变量。
注意,在category中可以有属性(property),但是该属性只是生成了getter和setter方法的声明,并没有产生对应的实现,更不会添加对应的实例变量。所以,尽管添加了属性,也无法使用点语法调用getter和setter方法;(可以使用Runtime去实现Category为已有的类添加新的属性并生成getter和setter方法).
Q:Category 能否添加成员变量?如果可以,如何给 Category 添加成员变量?
由于分类底层结构的限制,不能直接给 Category 添加成员变量,但是可以通过关联对象间接实现 Category 有成员变量的效果。
「简单说下关联对象方法添加实例变量原理:」
其实关联对象并没有添加到实例变量到被关联对象内存中,
关联对象有一个全局内容管理器(「AssociationsManager」),关联对象通过上面提到的三个API,操作实例变量 存取,移除。从而完成了对分类的添加实例变量。
传送门:OC - Association 关联对象
Q:为什么分类中属性不会自动生成 setter、getter 方法的实现,不会生成成员变量,也不能添加成员变量?
因为类的内存布局在编译的时候会确定,但是分类是在运行时才加载,在运行时Runtime会将分类的数据,合并到宿主类中。
Q:为什么优先调用最后编译的分类的方法?
attachCategories()方法中,从所有未完成整合的分类取出分类的过程是倒序遍历,最先访问最后编译的分类。然后获取该分类中的方法等列表,添加到二维数组中,所以最后编译的分类中的数据最先加到分类二维数组中,最后插入到宿主类的方法列表前面。而消息传递过程中优先查找宿主类中靠前的元素,找到同名方法就进行调用,所以优先调用最后编译的分类的方法。
Q:Category 中有 +load 方法吗?+load 方法是什么时候调用的?+load 方法能继承吗?
- 分类中有+load方法;
- +load方法在Runtime加载类、分类的时候调用;
- +load方法可以继承,但是一般情况下不会手动去调用+load方法,都是让系统自动调用。
load、initialize的区别,以及它们在category重写的时候的调用的次序。
答:区别在于调用方式和调用时刻
调用方式:load是根据函数地址直接调用,initialize是通过objc_msgSend调用
调用时刻:load是runtime加载类、分类的时候调用(只会调用1次),initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)
调用顺序:先调用类的load方法,先编译那个类,就先调用load。在调用load之前会先调用父类的load方法。分类中load方法不会覆盖本类的load方法,先编译的分类优先调用load方法。initialize先初始化父类,之后再初始化子类。如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次),如果分类实现了+initialize,就覆盖类本身的+initialize调用。
Category中+load 和+initialize调用的顺序是什么样的? Category的+load 和+initialize 方法的区别什么?
「调用顺序:」
类要优先于分类调用+load方法。先编译的分类的+load方法会被优先调。父类+load优先于子类。由于分类是objc_msgSend机制,+initialize在所有分类要优先于类调用。 在类中 +initialize父类优先于子类 。如果子类没有实现+initialize,会调用父类的+initialize(所以同一个父类的+initialize可能会被调用多次)。
「+load 和+initialize 区别在于调用方式和调用时刻不同」
调用方式不同:
+load是根据函数地址直接调用,initialize是通过objc_msgSend调用调用时刻不同:
+load方法会在runtime加载类、分类时调用(而且程序运行过程中只调用一次)。
+initialize是类第一次接收到消息的时候调用(即是类调用alloc时),每一个类只会initialize一次(上面提到子类没有实现+initialize,会调用父类的+initialize。这样父类的initialize方法可能会被调用多次)