学计算机的那个

不是我觉到、悟到,你给不了我,给了也拿不住;只有我觉到、悟到,才有可能做到,能做到的才是我的.

0%

Association关联对象

Association 关联对象

默认情况下,由于分类底层结构的限制,不能直接给 Category 添加成员变量,但是可以通过关联对象间接实现 Category 有成员变量的效果。

使用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#import "Person.h"
@interface Person (Test)
@property (nonatomic, assign) int height;
@end

#import "Person+Test.h"
#import <objc/runtime.h>
@implementation Person (Test)
- (void)setHeight:(int)height
{
objc_setAssociatedObject(self, @selector(height), [NSNumber numberWithInt:height], OBJC_ASSOCIATION_ASSIGN);
}
- (int)height
{
return [objc_getAssociatedObject(self, @selector(height)) intValue];
}
@end

objc_AssociationPolicy 关联策略

1
2
3
4
5
6
7
8
9
10
11
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};

key的常见用法

1
2
3
4
5
6
//  使用 getter 方法的 SEL 作为 key(可读性高,有智能提示)
objc_setAssociatedObject(object, @selector(getter), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
objc_getAssociatedObject(object, @selector(getter)];

// 或使用隐式参数 _cmd
objc_getAssociatedObject(object, _cmd];

关联对象的原理

实现关联对象技术的核心对象

AssociationsManager
AssociationsHashMap
ObjectAssociationMap
ObjcAssociation

1
2
3
4
5
6
7
8
9
class AssociationsManager {
static AssociationsHashMap *_map;
};
class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap>
class ObjectAssociationMap : public std::map<void *, ObjcAssociation>
class ObjcAssociation {
uintptr_t _policy;
id _value;
};

数据结构

  • AssociationsManager

关联对象并不是存储在关联对象本身内存中,而是存储在全局统一的一个容器中;
由 AssociationsManager 管理并在它维护的一个单例 Hash 表 AssociationsHashMap 中存储;
使用 AssociationsManagerLock 自旋锁保证了线程安全。

  • AssociationsHashMap

  • 一个单例的 Hash 表,存储 disguised_ptr_t 和 ObjectAssociationMap 之间的映射。

  • disguised_ptr_t 是根据 object 生成,但不存在引用关系。

disguised_ptr_t disguised_object = DISGUISE(object);

  • ObjectAssociationMap
    存储 key 和 ObjcAssociation 之间的映射
  • ObjcAssociation
    存储着关联策略 policy 和关联对象的值 value

  • objc_setAssociatedObject

① 实例化一个 AssociationsManager 对象,它维护了一个单例 Hash 表 AssociationsHashMap 对象;
② 实例化一个 AssociationsHashMap 对象,它维护 disguised_ptr_t 和 ObjectAssociationMap 对象之间的关系;
③ 根据object生成一个 disguised_ptr_t 对象;
④ 根据 disguised_ptr_t 获取对应的 ObjectAssociationMap 对象,它存储key和 ObjcAssociation 之间的映射;
⑤ 根据policy和value创建一个 ObjcAssociation 对象,并存储在 ObjectAssociationMap 中;
⑥ 如果传进来的value为 nil,则在 ObjectAssociationMap 中删除该 ObjcAssociation 对象。

  • objc_getAssociatedObject
    ① 实例化一个 AssociationsManager 对象;
    ② 实例化一个 AssociationsHashMap 对象;
    ③ 根据object生成一个 disguised_ptr_t 对象;
    ④ 根据 disguised_ptr_t 获取对应的 ObjectAssociationMap 对象;
    ⑤ 根据 key 获取到它所对应的 ObjcAssociation 对象;
    ⑥ 返回 ObjcAssociation 中的 value。
  • objc_removeAssociatedObjects
    ① 实例化一个 AssociationsManager 对象;
    ② 实例化一个 AssociationsHashMap 对象;
    ③ 根据object生成一个 disguised_ptr_t 对象;
    ④ 根据 disguised_ptr_t 获取对应的 ObjectAssociationMap 对象;
    ⑤ 删除该 ObjectAssociationMap 对象。
  • acquireValue
    根据policy来对value进行retain或者copy操作。

相关面试题

Q:如何移除关联对象?

  • 移除一个object的某个key的关联对象:调用objc_setAssociatedObject设置关联对象value为nil。
    objc_setAssociatedObject函数会调用_object_set_associative_reference函数,并在该函数中判断传进来的value是否为nil,是的话会调用erase(j)擦除函数,将j变量擦除。j即为ObjectAssociationMap对象里的一对【key: key value: ObjcAssociation(_policy、_value)】。

  • 移除一个object的所有关联对象:调用函数objc_removeAssociatedObjects
    objc_removeAssociatedObjects函数会调用_object_remove_assocations函数,并在该函数中调用对象的erase(i)擦除函数,将i变量擦除。i即为AssociationsHashMap对象中的一对【key: object value: ObjectAssociationMap】。

Q:如果 object 被销毁,那它所对应的 ObjectAssociationMap 是否也会自动销毁?

会。

如果没有关联对象,怎么实现 Category 有成员变量的效果?

使用字典。创建一个全局的字典,将self对象在内存中的地址作为key。
缺点:
① 内存泄漏问题:全局变量会一直存储在内存中;
② 线程安全问题:可能会有多个对象同时访问字典,加锁可以解决;
③ 每添加一个成员变量就要创建一个字典,很麻烦。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#import "Person.h"
@interface Person (Test)
@property (nonatomic, assign) int height;
@end

#import "Person+Test.h"
#import <objc/runtime.h>
@implementation Person (Test)
NSMutableDictionary *heights_;
+ (void)load {
heights_ = [NSMutableDictionary dictionary];
}
- (void)setHeight:(int)height {
NSString *key = [NSString stringWithFormat:@"%@",self];
heights_[key] = @(height);
}
- (int)height {
NSString *key = [NSString stringWithFormat:@"%@",self];
return [heights_[key] intValue];
}

参考

OC 底层探索 - Association 关联对象