前言
Category
是OC
语言的基础特性之一,用于在不改变原有类结构的情况下扩展该类功能(主要是添加方法等)。
Category 原理
编译时生成Category_t
的结构体,结构体中主要包括实例方法里列表、类方法列表、属性列表,在运行时把结构体中的信息合并到原类中。合并的方式为最后编译的分类信息在最前面,原类中的信息在最后面,所以如果多个分类都定义了同一个方法(名称相同),则会执行最后编译的那个分类中的方法 。往原类中添加方法的步骤为:
- 给原类的方法列表扩容
- 原方法后移
- 分类方法占用原方法的位置(按编译顺序来,最后编译的在最前面)
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
中。
核心对象:
AssociationsManager
AssociationsHashMap
ObjectAssociationMap
ObjcAssociation
最后
以上就是本篇的内容,势必会有一些遗漏和错误,欢迎斧正~
网友评论