一、前言
Associated Objects(关联对象)是什么?什么时候用?为什么要用?怎么用?
最开始用到关联对象是源于一个需求(废话,肯定是源于需求)。
大家都知道,Button的点击事件,一定是将本身传入参数:1
2
3
4
5
6
7
8
9
10- (void)setupFoundationUI {
UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
btn.frame = (CGRect){0, 0, 30, 30};
[self.view addSubview:btn];
[btn addTarget:self action:@selector(btnDidClick:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)btnDidClick:(UIButton *)sender {
NSLog(@"Btn did click ...");
}
如果想要传入一个特定的参数呢?
- 当时我想传的参数是整型,于是我想到了tag(那时的我还不知道关联对象)
- tag其实是用来标记不同的Button对象,但此时,我也很无奈。。。就先借用一下吧,哈哈
也就是这样👇👇👇1
2
3
4
5
6
7
8
9
10
11- (void)setupFoundationUI {
UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
btn.frame = (CGRect){0, 0, 30, 30};
btn.tag = 110;
[self.view addSubview:btn];
[btn addTarget:self action:@selector(btnDidClick:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)btnDidClick:(UIButton *)sender {
NSLog(@"Btn did click ...tag = %zd", sender.tag);
}
后来又有需求,要传的参数是字符串,甚至是对象。。。
就在此时我注意到了关联对象(Associated Objects)
二、关联对象的介绍
1.关联对象解决的问题
我们知道,在 Objective-C 中可以通过 Category (类别、分类,反正你们懂得)给一个现有的类添加属性,但是却不能添加实例变量,这似乎成为了 Objective-C 的一个明显短板,关联对象就可以解决这个问题。
2.如何用关联对象
首先要引入 runtime
1
#import <objc/runtime.h>
API主要就是(来自系统文件runtime.h的介绍)👇👇👇
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
37
38
39
40
41
42
43
44
45
46
47/**
* Sets an associated value for a given object using a given key and association policy.
*
* @param object The source object for the association.
* @param key The key for the association.
* @param value The value to associate with the key key for object. Pass nil to clear an existing association.
* @param policy The policy for the association. For possible values, see “Associative Object Behaviors.”
*
* @see objc_setAssociatedObject
* @see objc_removeAssociatedObjects
*/
OBJC_EXPORT void
objc_setAssociatedObject(id _Nonnull object, const void * _Nonnull key,
id _Nullable value, objc_AssociationPolicy policy)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
/**
* Returns the value associated with a given object for a given key.
*
* @param object The source object for the association.
* @param key The key for the association.
*
* @return The value associated with the key \e key for \e object.
*
* @see objc_setAssociatedObject
*/
OBJC_EXPORT id _Nullable
objc_getAssociatedObject(id _Nonnull object, const void * _Nonnull key)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
/**
* Removes all associations for a given object.
*
* @param object An object that maintains associated objects.
*
* @note The main purpose of this function is to make it easy to return an object
* to a "pristine state”. You should not use this function for general removal of
* associations from objects, since it also removes associations that other clients
* may have added to the object. Typically you should use \c objc_setAssociatedObject
* with a nil value to clear an association.
*
* @see objc_setAssociatedObject
* @see objc_getAssociatedObject
*/
OBJC_EXPORT void
objc_removeAssociatedObjects(id _Nonnull object)
OBJC_AVAILABLE(10.6, 3.1, 9.0, 1.0, 2.0);
官方的解释已经很清晰了,就不过多解读了(绑定、获取、移除),值得注意的一点是:objc_removeAssociatedObjects
是移除一个对象的所有关联对象,将该对象恢复成“原始”状态,这样的操作风险太大,所以一般的做法是通过给 objc_setAssociatedObject
函数传入 nil 来移除某个已有的关联对象。如下这样👇👇👇1
objc_setAssociatedObject(self, &key, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
关于key的问题
- 声明 static char kAssociatedObjectKey; 使用 &kAssociatedObjectKey 作为 key 值;
- 声明 static void *kAssociatedObjectKey = &kAssociatedObjectKey; 使用 kAssociatedObjectKey 作为 key 值;
- 用 selector ,使用 getter 方法的名称作为 key 值。
关于policy(关联策略)的问题
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)
复制关联对象,且为原子操作
具体内容可以参考官方文档,这里就不copy了
三、用关联对象解决上述问题
传整型数据
1
2
3
4
5
6
7
8
9
10
11
12
13NSString *const kButtonKey = @"kButtonKey";
- (void)setupFoundationUI {
UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
objc_setAssociatedObject(btn, &kButtonKey, @110, OBJC_ASSOCIATION_ASSIGN);
[self.view addSubview:btn];
[btn addTarget:self action:@selector(btnDidClick:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)btnDidClick:(UIButton *)sender {
NSInteger value = [objc_getAssociatedObject(sender, &kButtonKey) integerValue];
NSLog(@"btn did click ...value = %zd", value);
}传对象数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16NSString *const kButtonKey = @"kButtonKey";
- (void)setupFoundationUI {
UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
btn.frame = (CGRect){0, 0, 30, 30};
Person *person = [[Person alloc] init];
person.name = @"LiMing";
objc_setAssociatedObject(btn, &kButtonKey, person, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[self.view addSubview:btn];
[btn addTarget:self action:@selector(btnDidClick:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)btnDidClick:(UIButton *)sender {
Person *person = objc_getAssociatedObject(sender, &kButtonKey);
NSLog(@"person`s name = %@", person.name);
}
四、关联对象用于Category
以实现UIBarButtonItem的扩展为例子,为其增加红点的功能,其中大量的使用了关联对象
需求:
1.显示小红点
2.显示数字红点
3.即有小红点又有数字红点时,优先显示数字红点
4.可自定义红点颜色(默认是红色[UIColor redColor])
5.数字红点数目大于99时,显示99+
具体代码如下:👇👇👇1
2
3
4
5
6
7
8
9
@interface UIBarButtonItem (Badge)
@property (assign, nonatomic) UIColor *badgeColor;
- (void)configBadgeWithBigNum:(NSInteger)bigNum small:(BOOL)isOn;
@end
1 |
|
五、写在最后
- 关联对象与被关联对象本身的存储并没有直接的关系,它是存储在单独的哈希表中的;
- 关联对象的五种关联策略与属性的限定符非常类似,在绝大多数情况下,我们都会使用
OBJC_ASSOCIATION_RETAIN_NONATOMIC
的关联策略,这可以保证我们持有关联对象; - 关联对象的释放时机与移除时机并不总是一致,比如用关联策略
OBJC_ASSOCIATION_ASSIGN
进行关联的对象,很早就已经被释放了,但是并没有被移除,而再使用这个关联对象时就会造成 Crash 。