思考:
- Category 能否添加成员变量?如果可以,如何给 Category 添加成员变量?
- 类对象可以关联对象吗?
- 关联对象存储在什么位置?
- 对象及其关联对象之间有强引用关系吗?
- 对象释放后,关联对象会被释放吗?
Category 的成员变量
_category_t 结构体
|
|
从 _category_t 的结构体可以看出,Category 中并没有存放成员变量的容器,所以 Category 本身是不支持添加成员变量的。
在 Persion.h 中定义属性
|
|
在 Persion.h 中定义了属性后,系统会在 Persion.m 里自动做三件事:
在 Persion+Test.h 中定义属性
|
|
在 Persion+Test.h 中定义了属性后,系统会在 Persion+Test.h 里自动是实现两件事:
在分类里定义的属性,并没有对应的成员变量和 set/get 方法的实现。如果尝试手动添加,系统会报错。
定义全局变量 int weight_
|
|
通过定义全局变量 _weight 的方式可以实现成员变量的效果,但是 _weight 是全局变量,所有的实例对象 person 会共用同一个 _weight。这样会导致所有的实例对象 person 都可以修改 _weight,无法保证单个实例对象 person 与 _weight 一一对应关系。
定义全局变量 NSMutableDictionary *weights_
|
|
打印结果:
- 注意:
10和30是存储在 persion 对象的内部,20和40是存放在全局的字典 weights_ 对象里面。
优点:通过定义全局变量字典,以 persion 对象的地址作 key,属性 weight 作 value,保证了 persion 对象与 weight 的一一对相应关系。
缺点:这种方式可能会有线程问题,在 - (void)setWeight:(int)weight
方法里需要加锁。另外每添加一个属性就要定义一个新的全局变量字典。
小结
因为分类底层结构的限制,不能直接添加成员变量到分类中,但是可以通过关联对象来间接实现。
基本用法
方法列表
参数:
id _Nonnull object:需要添加关联对象的对象(通常写法传 self);
const void * _Nonnull key:指针类型,保存关联对象的key;
id _Nullable value:关联对象;
objc_AssociationPolicy policy:关联策略;
添加关联对象:
获取关联对象:
移除所有的关联对象:
关联策略 objc_AssociationPolicy
|
|
关联策略 objc_AssociationPolicy 类似于定义属性时设置的参数,确定要关联的对象的内存内存管理方式。
关联策略中没有 week,因为关联对象的实现并没有弱引用效果:
👆通过 OBJC_ASSOCIATION_ASSIGN 策略关联了对象 persion3,此时在 ObjcAssociation 对象里的 value 记住的是关联对象 persion3 的地址值,当关联对象 persion3 离开大括号销毁后,value 依然保存着关联对象的地址值。如果这个时候再通过 value 去访问关联对象就会出现坏内存访问的错误。
key的常见用法
先添加打印:
用法一:static const void *Key = &Key;
const void *Key;
|
|
打印结果:
因为这种方式定义的 NameKey/WeightKey 没有赋值,都是 NULL。这样 name 和 weight 在赋值或取值的时候用的 key 都是 NULL,所以打印结果 name == weight。
const void *Key = &Key;
修改 NameKey/WeightKey 的定义:
打印结果:
这样定义的 NameKey/WeightKey 内部存储的分别是 NameKey/WeightKey 的地址值,这样保证了 name 和 weight 在赋值或取值的时候用的 key 是不同的值。但是这种方式定义的 NameKey/WeightKey 是全局变量,在其它文件可以通过 exten 访问到:
打印结果:
static const void *Key = &Key;
使用 static,限定全局变量的作用域,修改 NameKey/WeightKey 的定义:
打印结果:
这个时候再使用 extern 的方式查找全局变量 NameKey/WeightKey 就会报错:
小结
- static:保证了全局变量的作用域仅限于当前文件。
Key = &Key
:Key 内部存储的是 Key 的地址值。
用法二:static const char Key;
static const int Key;
const void * _Nonnull key
要求的参数是指针(地址值),所以可以定义一个 int Key,传入 &Key。
static const char Key;
int 类型在内存中占用4个字节,既然只是要求指针,也可以定义一个 char Key,传入 &Key,这样就只占用1个字节了👇。
打印结果:
用法三:使用属性名作为 key
使用属性名作为 key 可以很好的增加代码的可读性:
打印结果:
@”name” 与 const void * _Nonnull key。
定义一个字符串:
str 里装的就是 @”name” 的地址值。因为 str == @”name”,所以字符串 @”name” 就是其地址值,可以直接让字符串 @”name” 作 key。
字符串 @”name” 是放在内存常量区的,所以不同位置的字符串 @”name” 都是同一个字符串,这样也能保证了存储和读取时传入的 key 是同一个地址值👇 :
打印结果:
用法四:使用 get 方法的 @selecor 作为 key
@selecor
|
|
打印结果:
@selector()
方法是一个指向结构体的指针,即返回某个结构体的指针,方法名相同的 @selector()
地址值相等:
打印结果:
使用 @selector()
作为 key,即增加可读性,又在编写时会有代码提示,如果写错了还会出现方法不存在的警告。
_cmd
在 get 方法里可以使用 _cmd 替换 @selecor(方法名):
打印结果:
隐式参数:OC 方法在被调用的时候,有两个隐式的参数传入,self 和 _cmd。_cmd 就是当前方法的 SEL,即 @selecor(name):
因为 set 方法中的 _cmd == @selecor(setName:)
,get 方法中的 _cmd == @selecor(name)
,所以为了保证存储和读取时用到的 key 是同一个,set 方法和 get 方法不能同时使用 _cmd。
类对象添加关联对象
类对象只有一个,不同的实例对象在修改和读取被新关联的对象时,都是在操作类对象关联的这个对象,这样又会出现实例对象和关联对象不是一一对应的关系。
打印结果:
关联对象的原理
实现关联对象技术的核心对象有:
- AssociationsManager:关联对象管理类,内部管理着所有的 AssociationsHashMap;
- AssociationsHashMap:存储着所有添加了关联对象的对象,及其添加的关联对象的信息;
- ObjectAssociationMap:存储着某一个关联对象的 key、value 和 policy 信息;
- ObjcAssociation:存储着某一个关联对象的 value 和 policy 信息;
打开 runtime 源码 objc4-781,找到 _base_objc_setAssociatedObject 方法的实现👇:
_base_objc_setAssociatedObject
|
|
找到 objc-references.mm 文件里的 _object_set_associative_reference,
Jump To Definition -> _object_set_associative_reference:
AssociationsManager
|
|
AssociationsHashMap
可以看到 AssociationsManager 中有一个 AssociationsHashMap 对象,并且 AssociationsHashMap 对象是一个通过 DenseMap 定义的,DenseMap 是以 DisguisedPtr
|
|
ObjectAssociationMap
|
|
👆从 ObjectAssociationMap 的定义可以看出,ObjectAssociationMap 是一个通过 DenseMap 定义的,以指针(地址值)为 key,ObjcAssociation 为 value 的字典。ObjcAssociation:
ObjcAssociation
|
|
可以看出 ObjcAssociation 有两个变量 _policy 和 _value。
objc_getAssociatedObject
|
|
找到 objc-references.mm 文件里的 _object_get_associative_reference,
Jump To Definition -> _object_get_associative_reference:
总结
Category 能否添加成员变量?如果可以,如何给 Category 添加成员变量?
因为分类底层结构的限制,不能直接添加成员变量到分类中,但是可以通过关联对象来间接实现。
给 Category 添加成员变量的方式有很多,不过主要是通过下面两个方法添加成员变量:1234//添加关联对象objc_setAssociatedObject();//获取关联对象objc_getAssociatedObject();类对象可以关联对象吗?
关联对象方法可以给任何对象关联新的对象,类对象自然也是可以关联对象的。但是类对象只有一个,不同的实例对象在修改和读取被新关联的对象时,都是在操作类对象关联的这个对象,这样又会出现实例对象和关联对象不是一一对应的关系。关联对象存储在什么位置?
关联对象并不是存储在被关联对象本身内存中,关联对象存储在全局的统一的一个 AssociationsManager 中。对象及其关联对象之间有强引用关系吗?
不存在。在 set 方法中并没有直接使用传入的 object 对象,而是通过的 disguised 方法将传入的 object 对象生成了 DisguisedPtr 对象,并作为 key。对象释放后,关联对象会被释放吗?
会释放。参考内存管理-release。设置关联对象为nil,就相当于是移除关联对象。