在 Objective-C 中可以通过 Category 给一个现有的类添加属性,但是却不能添加实例变量,这似乎成为了 Objective-C 的一个明显短板。然而值得庆幸的是,我们可以通过 Associated Objects 来弥补这一不足。本文将结合 runtime 源码深入探究 Objective-C 中 Associated Objects 的实现原理。
读者需要着重关注以下三个问题:
- 关联对象被存储在什么地方,是不是存放在被关联对象本身的内存中?
- 关联对象的五种关联策略有什么区别,有什么坑?
- 关联对象的生命周期是怎样的,什么时候被释放,什么时候被移除?
使用场景
按照 Mattt Thompson 大神的文章 Associated Objects 中的说法,Associated Objects 主要有以下三个使用场景:
- 比如在分类中添加属性。
- 比如在分类中添加属性。
- 为 KVO 创建一个关联的观察者。
从本质上看,第 1 、2 个场景其实是一个意思,唯一的区别就在于新添加的这个属性是公有的还是私有的而已。就目前来说,我在实际工作中使用得最多的是第 2 个场景,而第 3 个场景我还没有使用过。
与 Associated Objects 相关的函数主要有三个,我们可以在 runtime 源码的 runtime.h 文件中找到它们的声明:
这三个函数的命名对程序员非常友好,可以让我们一眼就看出函数的作用:
- objc_setAssociatedObject 用于给对象添加关联对象,传入 nil 则可以移除已有的关联对象;
- objc_getAssociatedObject 用于获取关联对象;
- objc_removeAssociatedObjects 用于移除一个对象的所有关联对象。
注:
objc_removeAssociatedObjects 函数我们一般是用不上的,因为这个函数会移除一个对象的所有关联对象,将该对象恢复成“原始”状态。这样做就很有可能把别人添加的关联对象也一并移除,这并不是我们所希望的。所以一般的做法是通过给 objc_setAssociatedObject 函数传入 nil 来移除某个已有的关联对象。
key 值
关于前两个函数中的 key 值是我们需要重点关注的一个点,这个 key 值必须保证是一个对象级别(为什么是对象级别?看完下面的章节你就会明白了)的唯一常量。一般来说,有以下三种推荐的 key 值:
声明 static char kAssociatedObjectKey; ,使用 &kAssociatedObjectKey 作为 key 值;
声明 static void *kAssociatedObjectKey = &kAssociatedObjectKey; ,使用 kAssociatedObjectKey 作为 key 值
用 selector ,使用 getter 方法的名称作为 key 值。
本人更倾向于第二种.
关联策略
在给一个对象添加关联对象时有五种关联策略可供选择:
下面的1.2是对关联对象什么时候释放做了解释。
- 关联对象的释放时机与被移除的时机并不总是一致的,比如上面的 self.associatedObject_assign 所指向的对象在 ViewController 出现后就被释放了,但是 self.associatedObject_assign 仍然有值,还是保存的原对象的地址。如果之后再使用 self.associatedObject_assign 就会造成 Crash ,所以我们在使用弱引用的关联对象时要非常小心;
- 一个对象的所有关联对象是在这个对象被释放时调用的 _object_remove_assocations 函数中被移除的。
下面是一个针对alertView做的一个例子。