我们知道,在 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 文件中找到它们的声明:
1 2 3 |
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_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
值。
我个人最喜欢的(没有之一)是第 3
种方式,因为它省掉了一个变量名,非常优雅地解决了计算科学中的两大世界难题之一(命名)。
关联策略
在给一个对象添加关联对象时有五种关联策略可供选择:
关联策略 | 等价属性 | 说明 |
---|---|---|
OBJC_ASSOCIATION_ASSIGN | @property (assign) or @property (unsafe_unretained) | 弱引用关联对象 |
OBJC_ASSOCIATION_RETAIN_NONATOMIC | @property (strong, nonatomic) | 强引用关联对象,且为非原子操作 |
OBJC_ASSOCIATION_COPY_NONATOMIC | @property (copy, nonatomic) | 复制关联对象,且为非原子操作 |
OBJC_ASSOCIATION_RETAIN | @property (strong, atomic) | 强引用关联对象,且为原子操作 |
OBJC_ASSOCIATION_COPY | @property (copy, atomic) | 复制关联对象,且为原子操作 |
其中,第 2
种与第 4
种、第 3
种与第 5
种关联策略的唯一差别就在于操作是否具有原子性。由于操作的原子性不在本文的讨论范围内,所以下面的实验和讨论就以前三种以例进行展开。
实现原理
在探究 Associated Objects 的实现原理前,我们还是先来动手做一个小实验,研究一下关联对象什么时候会被释放。本实验主要涉及 ViewController
类和它的分类 ViewController+AssociatedObjects
。注:本实验的完整代码可以在这里 AssociatedObjects 找到,其中关键代码如下:
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 |
@interface ViewController (AssociatedObjects) @property (assign, nonatomic) NSString *associatedObject_assign; @property (strong, nonatomic) NSString *associatedObject_retain; @property (copy, nonatomic) NSString *associatedObject_copy; @end @implementation ViewController (AssociatedObjects) - (NSString *)associatedObject_assign { return objc_getAssociatedObject(self, _cmd); } - (void)setAssociatedObject_assign:(NSString *)associatedObject_assign { objc_setAssociatedObject(self, @selector(associatedObject_assign), associatedObject_assign, OBJC_ASSOCIATION_ASSIGN); } - (NSString *)associatedObject_retain { return objc_getAssociatedObject(self, _cmd); } - (void)setAssociatedObject_retain:(NSString *)associatedObject_retain { objc_setAssociatedObject(self, @selector(associatedObject_retain), associatedObject_retain, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (NSString *)associatedObject_copy { return objc_getAssociatedObject(self, _cmd); } - (void)setAssociatedObject_copy:(NSString *)associatedObject_copy { objc_setAssociatedObject(self, @selector(associatedObject_copy), associatedObject_copy, OBJC_ASSOCIATION_COPY_NONATOMIC); } @end |
在 ViewController+AssociatedObjects.h
中声明了三个属性,限定符分别为 assign, nonatomic
、strong, nonatomic
和 copy, nonatomic
,而在 ViewController+AssociatedObjects.m
中相应的分别用 OBJC_ASSOCIATION_ASSIGN
、OBJC_ASSOCIATION_RETAIN_NONATOMIC
、OBJC_ASSOCIATION_COPY_NONATOMIC
三种关联策略为这三个属性添加“实例变量”。
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 |
__weak NSString *string_weak_assign = nil; __weak NSString *string_weak_retain = -t">nil; __weak NSString *string_weak_retain = opensource.apple.com/tarballs/objc4/">runtime 源码深入探究 Objective-C 中 Associated Objects 的实现原理。
在阅读本文的过程中,读者需要着重关注以下三个问题:
这是我写这篇文章的初衷,也是本文的价值所在。 使用场景按照 Mattt Thompson 大神的文章 Associated Objects 中的说法,Associated Objects 主要有以下三个使用场景:
从本质上看,第 相关函数与 Associated Objects 相关的函数主要有三个,我们可以在 runtime 源码的 runtime.h 文件中找到它们的声明:
这三个函数的命名对程序员非常友好,可以让我们一眼就看出函数的作用:
注: key 值关于前两个函数中的
我个人最喜欢的(没有之一)是第 关联策略在给一个对象添加关联对象时有五种关联策略可供选择:
其中,第 实现原理在探究 Associated Objects 的实现原理前,我们还是先来动手做一个小实验,研究一下关联对象什么时候会被释放。本实验主要涉及
在
|