前言
Category是OC语言的基础特性之一,用于在不改变原有类结构的情况下扩展该类功能(主要是添加方法等)。
Category 原理
编译时生成Category_t的结构体,结构体中主要包括实例方法里列表、类方法列表、属性列表,在运行时把结构体中的信息合并到原类中。合并的方式为最后编译的分类信息在最前面,原类中的信息在最后面,所以如果多个分类都定义了同一个方法(名称相同),则会执行最后编译的那个分类中的方法 。往原类中添加方法的步骤为:
- 给原类的方法列表扩容
- 原方法后移
- 分类方法占用原方法的位置(按编译顺序来,最后编译的在最前面)
category-struct.png
Category中没有办法添加属性,是因为分类的结构体中没有成员变量字段的定义,而属性是需要创建成员变量和get、set方法的,虽然Category中创建属性的时候也会自动创建get、set方法, 但是不会实现它, 而且没有对应的成员变量作为值的载体。所以不能实现属性的功能。
extension 和 Category 的区别
-
OC中的扩展没有名称(匿名),而且扩展的功能是分散原类头文件臃肿和定义不想对外曝露的部分。在编译时就会合并到原类中,所以可以添加属性,成员变量等信息。 - 分类是对原类的扩展,由于不会改变原类结构和在运行时才会合并到原类信息中,所以不支持添加属性,成员变量等信息。
Load 方法
Load方法会在runtime加载类,分类的时候调用,每个类、分类在程序运行过程中只会调用一次。调用顺序为:
- 先调用父类,在调用子类
- 先调用原类,在按照编译顺序调用分类
Load方法是可以继承的。
Initialize 方法
在类第一次接收到消息的时候调用。先调用父类的该方法,再调用子类的该方法。
Initialize 是通过objc_msgSend调用的,特性如下:
- 所以如果分类实现了
Initialize方法,则只会调用分类的Initialize方法,不会再调用原类的Initialize方法。 - 如果子类没有实现
Initialize方法,会调用父类的Initialize方法。 - 如果多个子类继承自同一个父类且子类都没有实现
Initialize方法,则多个子类创建的时候会多次调用父类的Initialize方法,但是每次调用的含义不同,并不是父类初始化了三次。
Load 和 Initialize 的区别
- 调用方式
-
Load是根据函数地址直接调用 -
Initialize是通过objc_msgSend调用
-
- 调用时刻
-
Load是runtime加载类,分类时候调用一次 -
Initialize是类第一次接收到消息时候调用,每个类只会调用1次(父类的Initialize方法可能被调用多次)
-
- 调用顺序
- Load
- 先调用父类的
Load,在调用子类的Load - 再调用分类的
Load,先编译的分类优先调用
- 先调用父类的
-
Initialize- 先初始化父类
- 在初始化子类(可能最终调用的还是父类的
Initialize方法)
- Load
关联对象
常用API:
-
void objc_setAssociatedObject(id object, const void * Key, id value, objc_AssociationPolicy policy)添加关联对象 -
id objc_getAssociatedObject(id object, const void * Key)获取关联对象 -
void objc_removeAssociatedObjects(id object)移除所有关联对象
| objc_AssociationPolicy | 修饰符 |
|---|---|
| OBJC_ASSOCIATION_ASSIGN | assign |
| OBJC_ASSOCIATION_RETAIN_NONATOMIC | strong, nonatomic |
| OBJC_ASSOCIATION_COPY_NONATOMIC | copy, nonatomic |
| OBJC_ASSOCIATION_RETAIN | strong, atomic |
| OBJC_ASSOCIATION_COPY | copy, atomic |
- 在分类中使用的时候,
object一般为self -
const void *Key由于只是需要一个指针,为了节约内存,所以可以直接申明一个char类型的变量,不用赋值,也可以直接使用字符串, 这个值比较灵活 -
policy根据需要存储的值类型来做确定 - 如果是在
Category中添加属性,get方法的Key可以使用隐式函数_cmd来表示当前方法地址,set方法中使用@selector(<get方法名>)确定Key值 - 在
Category中使用关联对象,可以间接实现Category中使用成员变量的功能。 - 如果想取消某个关联对象,直接把该对象的关联value设置为null就可以了。
-
void objc_removeAssociatedObjects(id object)会把这个object下面的所有关联对象都移除掉
关联对象的原理
关联对象的Key并不是存储在对应的类中的,由AssociationsManager统一管理并存储在AssociationsHashMap中。
核心对象:
AssociationsManagerAssociationsHashMapObjectAssociationMapObjcAssociation
associatedObject.png
最后
以上就是本篇的内容,势必会有一些遗漏和错误,欢迎斧正~








网友评论