iOS之Category
Category的底层结构
编译后的分类信息都保存在category_t
结构体中(定义在objc-runtime-new.h
中):
Category的加载处理过程
Category
中的方法是在程序运行时通过runtime
动态地将分类中的方法合并到类对象、元类对象中。
- 通过Runtime加载某个类的所有
Category
数据(方法、属性、协议); - 把所有
Category
的方法、属性、协议数据,合并到一个大数组中,参与编译的Category
数据,会放在数组的前面 - 将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面
Category的实现原理
- Category编译之后的底层结构是
struct category_t
; - 里面存储着分类的对象方法、类方法、属性、协议信息。
- 在程序运行时,
runtime
会将Category
的数据合并到类信息中(类对象、元类对象中)
Category和Extension的区别
备注:OC的Extension通常是放在.m
中:可以声明私有的成员变量、属性、方法等
决议时间不同:
- Class Extension在
编译
的时候,它的数据就已经包含在类信息中; - Category是在
运行时
才将数据合并到类信息中
- Class Extension在
目标和能力不同:
- Extension:主要用于扩展类的私有成员、属性、方法
- Category:
- 主要用来拆分功能模块;
- 可以增加
instanceMethods
、classMethods
、protocols
; - Category中的
properties
只会增加该属性对应的setter、getter的方法声明,不会增加对应的成员和方法实现。
Category和load()方法
Category中可以有load方法
load
方法调用+load
方法会在runtime
加载类、分类时调用- 每个类、分类的
+load
方法在程序运行中只会被调用一次
load
方法调用顺序:- 先调用类的
load
方法- 按照编译顺序先后调用(先编译的先调用)
- 先调用父类的
load
方法,再调用子类的load
方法
- 再调用分类的load方法:按照编译顺序先后调用
- 先调用类的
load方法的独特性
- 类和分类中的
+load
方法是直接根据+load
函数的指针地址直接调用的;类的其他类方法是使用消息发送机制调用的; load
方法可以继承,但是一般不会主动调用+load
方法,而是让系统自动调用。- 手动调用
+load
方法时跟系统调用机制不同,手动调用不是通过函数指针地址来直接调用load
方法,而是通过objc_msgSend
和isa
来调用。
- 类和分类中的
load
和initialize
+initialize
方法会在类第一次接收到消息时调用这句话可以理解为如果多次调用
[ZZPerson alloc]
,ZZPerson
的+initialize
方法只会被调用一次。调用顺序:
先调用父类的
initialize
,然后调用子类的initialize
(先初始化父类,再初始化子类,每个类只会初始化一次);如果子类没有实现
+initialize
,会调用父类的+initialize
;当父类有多个子类,这些子类都没有实现
+initialize
方法时,最终会通过isa
指针和superclass
指针找到父类的+initialize
方法,所以父类的+initialize
可能会被调用多次,多次被调用不代表父类被初始化多次如果分类实现了
+initialize
,会覆盖类本身的+initialize
调用。
+initialize
和+load
方法的区别:- 调用方式不同:
+initialize
是通过objc_msgSend
进行调用的;+load
是通过函数指针直接调用的; - 调用时机不同:
+load
是在runtime
加载类、分类的时候调用,并且只会调用一次;+initialize
是在类第一次接收到消息时调用,当前类没有实现+initialize
方法时,父类的+initialize
可能被调用多次。
- 调用方式不同:
Category添加成员的问题
Category为什么不能直接添加成员变量?
已注册类的实例对象大小和内存布局都已经确定,如果添加实例变量就会破坏类的内部布局,这对编译型语言来说是灾难性的。比如:一个类只有注册过才能用来创建对象,假设一个已经注册过的类创建了对象A,然后我们又给这个类增加了一个实例变量,并用这个类又创建了对象B,那么A和B的存储结构都不一样,那么A和B还能算是同一类对象吗?所以从逻辑上讲,也不能允许添加实例变量。
Category为什么能添加方法呢?
因为方法列表存放在类对象中,为类对象增加一个方法,所有该类的实例对象都会拥有这个方法,因此实例对象之间没有造成差异,还是同一个类型。
Category添加属性的本质
在Category中添加属性后,相当于只是声明了属性变量的
setter
、getter
,没有实现setter
、getter
,也没有属性对应的成员变量。添加成员变量的方案:
- 使用全局的字典变量:使用字典是为了一个类有多个实例对象时防止混淆不同对象的成员变量值。
- 使用关联对象
使用全局字典示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36// 全局的字典
@implementation ZZPerson (Test)
NSMutableDictionary *names_;
NSMutableDictionary *weights_;
+ (void)load
{
weights_ = [NSMutableDictionary dictionary];
names_ = [NSMutableDictionary dictionary];
}
- (void)setName:(NSString *)name
{
// NSString *key = [NSString stringWithFormat:@"%p", self];
names_[ZZKey] = name;
}
- (NSString *)name
{
// NSString *key = [NSString stringWithFormat:@"%p", self];
return names_[ZZKey];
}
- (void)setWeight:(int)weight
{
// NSString *key = [NSString stringWithFormat:@"%p", self];
weights_[ZZKey] = @(weight);
}
- (int)weight
{
// NSString *key = [NSString stringWithFormat:@"%p", self];
return [weights_[ZZKey] intValue];
}
Category和关联对象
Category中通常使用关联对象来添加属性/成员。
关联对象API:
- 添加关联对象
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociatedPolicy policy)
- 获取关联对象
id objc_getAssociatedObject(id object, const void *key)
- 移除所有关联对象
void objc_removeAssociatedObject(id object)
- 添加关联对象
关联策略和对应的修饰符:
objc_AssociatedPolicy 对应的修饰符 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 关联对象时key的常见用法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 静态指针
static void *MyKey = &MyKey;
objc_setAssoiciatedObject(obj, MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, MyKey)
// 静态变量的地址值
static char MyKey;
objc_setAssoiciatedObject(obj, &MyKey, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, &MyKey)
// 使用属性名作为key
objc_setAssoiciatedObject(obj, @"property", value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, @"property")
// 使用get方法的@selector作为key
objc_setAssoiciatedObject(obj, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC)
objc_getAssociatedObject(obj, @selector(getter))关联对象实现原理
关联对象并不是存储在被关联对象自身的内存中
关联对象存储在全局的一个AssociatesManager中,是一个哈希表结构