0%

OC关联对象实现原理

objc_AssociationPolicy

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
/**
* Policies related to associative references.
* These are options to objc_setAssociatedObject()
*/
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. */0x0301
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */0x0303
};

//对应的内部枚举
enum {
OBJC_ASSOCIATION_SETTER_ASSIGN = 0,
OBJC_ASSOCIATION_SETTER_RETAIN = 1,
OBJC_ASSOCIATION_SETTER_COPY = 3, // NOTE: both bits are set, so we can simply test 1 bit in releaseValue below.
OBJC_ASSOCIATION_GETTER_READ = (0 << 8),
OBJC_ASSOCIATION_GETTER_RETAIN = (1 << 8),
OBJC_ASSOCIATION_GETTER_AUTORELEASE = (2 << 8)
};

注意:上面的01401是八进制写法,对应的十六进制是0x0301。我说怎么后面的switch看不懂呢。

AssociationPolicy中NONATOMIC 和 ATOMIC 的区别

对于setter方法,NONATOMICATOMIC 没有什么区别

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
// This code used to work when nil was passed for object and key. Some code
// probably relies on that to not crash. Check and handle it explicitly.
// rdar://problem/44094390
if (!object && !value) return;

if (object->getIsa()->forbidsAssociatedObjects())
_objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));

DisguisedPtr<objc_object> disguised{(objc_object *)object};
ObjcAssociation association{policy, value};

// retain the new value (if any) outside the lock.
association.acquireValue();

bool isFirstAssociation = false;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());

if (value) {
//try_emplace会先查找,没找到才创建新的ObjectAssociationMap,创建时会根据需要扩容AssociationsHashMap。
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) { //指定的disguised,以前没有值,这里是新创建的。则做好标记isFirstAssociation。
/* it's the first association we make */
isFirstAssociation = true;
}

/* establish or replace the association */
auto &refs = refs_result.first->second; //refs是ObjectAssociationMap哈希表
auto result = refs.try_emplace(key, std::move(association)); //try_emplace作用同上。
if (!result.second) { //指定的key有旧值,进行swap,这样association保存的就是旧值,后面就执行释放旧值的操作。
association.swap(result.first->second); //result.first->second是ObjcAssociation对象
}
} else {
auto refs_it = associations.find(disguised);
if (refs_it != associations.end()) {
auto &refs = refs_it->second;
auto it = refs.find(key);
if (it != refs.end()) {
association.swap(it->second);
refs.erase(it);
if (refs.size() == 0) { //ObjectAssociationMap哈希表全空时,则将其从AssociationsHashMap中清除。
associations.erase(refs_it); //清除AssociationsHashMap的某个桶。erase里还会对AssociationsHashMap进行减容或扩容。

}
}
}
}
}

// Call setHasAssociatedObjects outside the lock, since this
// will call the object's _noteAssociatedObjects method if it
// has one, and this may trigger +initialize which might do
// arbitrary stuff, including setting more associated objects.
if (isFirstAssociation)
object->setHasAssociatedObjects();

// release the old value (outside of the lock).
association.releaseHeldValue();
}

inline void acquireValue() {
if (_value) {
switch (_policy & 0xFF) {
case OBJC_ASSOCIATION_SETTER_RETAIN:
_value = objc_retain(_value);
break;
case OBJC_ASSOCIATION_SETTER_COPY:
_value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy));
break;
}
}
}

inline void releaseHeldValue() {
if (_value && (_policy & OBJC_ASSOCIATION_SETTER_RETAIN)) {
objc_release(_value); //_policy是带retain或copy的这里都会进行release。
}
}

inline void swap(ObjcAssociation &other) {
std::swap(_policy, other._policy);
std::swap(_value, other._value);
}

大致步骤:

  1. retain新值,if needed。使用OBJC_ASSOCIATION_ASSIGN策略是不会retain的。
  2. AssociationsManagerLock加锁
  3. 将新值保存到哈希表中,将旧值保存到association里,用于后续释放。
  4. AssociationsManagerLock解锁
  5. 标记object有关联对象
  6. release旧值,if needed。

NONATOMICATOMIC 的区别只体现在 objc_getAssociatedObject 的实现上:

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
id
objc_getAssociatedObject(id object, const void *key)
{
return _object_get_associative_reference(object, key);
}

id
_object_get_associative_reference(id object, const void *key)
{
ObjcAssociation association{};

{
AssociationsManager manager; //临时变量manager,里面有全局锁,相当于对代码块进行了加锁解锁,常见写法。
AssociationsHashMap &associations(manager.get());
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
ObjectAssociationMap &refs = i->second;
ObjectAssociationMap::iterator j = refs.find(key);
if (j != refs.end()) {
association = j->second;
association.retainReturnedValue();
}
}
}

return association.autoreleaseReturnedValue();
}

inline void retainReturnedValue() {
if (_value && (_policy & OBJC_ASSOCIATION_GETTER_RETAIN)) {
objc_retain(_value);
}
}

inline id autoreleaseReturnedValue() {
if (slowpath(_value && (_policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE))) {
return objc_autorelease(_value);
}
return _value;
}

对于ATOMIC调用getter方法时,获取到的是retain后注册到自动释放池里的对象:

  1. AssociationsManagerLock加锁
  2. 获取关联对象
  3. retain关联对象
  4. AssociationsManagerLock解锁
  5. 将关联对象注册到自动释放池,并返回给调用者

NONATOMIC 只是简单的返回对象的地址:

  1. AssociationsManagerLock加锁
  2. 获取关联对象
  3. AssociationsManagerLock解锁
  4. 将关联对象返回给调用者。

因此使用 NONATOMIC 选项,可能会出现这种情况:A线程调用getter方法得到对象的地址,后面B线程调用setter方法,而setter方法会释放旧值,于是A线程获得的对象被销毁了,A线程在使用时存在野指针风险。而使用 ATOMIC 选项,因为getter方法里会retain并注册到自动释放池所以A线程获得对象在使用期间不会被销毁。实验时应该采用MRC环境。

正是由于上述细微的不同,所以对于关联对象该用 ATOMIC 选项时,还得用 ATOMIC 选项。

关联对象的存储

通过objc_setAssociatedObject完成设值。

1
2
3
4
5
#import <objc/runtime.h>

- (void)setNonatomicBook:(NSString *)nonatomicBook {
objc_setAssociatedObject(self, @selector(nonatomicBook), nonatomicBook, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

ps:关联的值只能为对象,不能为基础类型。

那么该方法究竟把传入的对象保存到哪里去了呢?数据结构出场。

AssociationsManager

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
spinlock_t AssociationsManagerLock;

namespace objc {

class ObjcAssociation {
uintptr_t _policy;
id _value; //只能是对象
public:
ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
ObjcAssociation() : _policy(0), _value(nil) {}
ObjcAssociation(const ObjcAssociation &other) = default;
ObjcAssociation &operator=(const ObjcAssociation &other) = default;
ObjcAssociation(ObjcAssociation &&other) : ObjcAssociation() {
swap(other);
}

inline void swap(ObjcAssociation &other) {
std::swap(_policy, other._policy);
std::swap(_value, other._value);
}

inline uintptr_t policy() const { return _policy; }
inline id value() const { return _value; }

inline void acquireValue() {
if (_value) {
switch (_policy & 0xFF) {
case OBJC_ASSOCIATION_SETTER_RETAIN:
_value = objc_retain(_value);
break;
case OBJC_ASSOCIATION_SETTER_COPY:
_value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy));
break;
}
}
}

...
};

typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;

// class AssociationsManager manages a lock / hash table singleton pair.
// Allocating an instance acquires the lock

class AssociationsManager {
using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
static Storage _mapStorage;

public:
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }

AssociationsHashMap &get() {
return _mapStorage.get();
}

static void init() {
_mapStorage.init();
}
};

AssociationsManager::Storage AssociationsManager::_mapStorage;

} // namespace objc

// We cannot use a C++ static initializer to initialize certain globals because
// libc calls us before our C++ initializers run. We also don't want a global
// pointer to some globals because of the extra indirection.
//
// ExplicitInit / LazyInit wrap doing it the hard way.
template <typename Type>
class ExplicitInit {
alignas(Type) uint8_t _storage[sizeof(Type)];

public:
template <typename... Ts>
void init(Ts &&... Args) { //上面的init方法
new (_storage) Type(std::forward<Ts>(Args)...); //堆上的容器。
}

Type &get() {//上面的get方法
return *reinterpret_cast<Type *>(_storage);
}
};

字段:

AssociationsManagerLock:一把全局自旋锁。

_mapStorage:一个局部静态变量哈希表AssociationsHashMap,键为对象地址,值为一个哈希表ObjectAssociationMap。哈希表ObjectAssociationMap的键为关联对象的key(必须为一个常量字符串,如果是alloc动态生成的即使字符相等也会有问题),值为ObjcAssociation。ObjcAssociation里记录了policy和value(传入的关联对象)。

因此整个数据结构就很清楚了,就是哈希表套哈希表。这种数据结构在源码里用的很普遍。

设值过程如下:

  1. 加锁。
  2. 根据对象的地址从哈希表AssociationsHashMap中找到哈希表ObjectAssociationMap。
  3. 再根据关联对象的key从哈希表ObjectAssociationMap中找到ObjcAssociation。
  4. 新值替换旧值。
  5. 解锁。结束。

AssociationsHashMap/ObjectAssociationMap扩容减容时机

这里全是C++代码,看不懂。

哈希表在插入元素时会根据需要进行扩容,在删除元素时会进行减容或扩容(删除后会检查是否满足扩容条件)。AssociationsHashMap初始化后有一个默认容量(不会很大),后续根据需要扩容和减容。

关联对象的获取

获取过程类似。

添加weak关联对象

从objc_AssociationPolicy枚举中,可以看到是没有weak选项的,只有一个assign,但assign在关联对象销毁后,指针并不会被置为nil,因此有野指针访问风险。至于系统为啥没有帮我们实现weak选项,可能的原因是因为类别添加的属性其实是没有实例变量对应的,所以没办法把指针变量地址注册到关联对象的weak表中去,不过具体原因不得而知,我也想象不到。

但我们自己可以实现这个weak选项。

weak主要的作用就是对象销毁后,所有指向它的指针都将被置为nil,从而避免了野指针访问。因此我们只需要在关联对象销毁时,再次调用objc_setAssociatedObject置为nil就可以了。

一种简洁的实现:利用block能够捕获自动变量的特点以及现成的weak特性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@interface XQDetailViewController (Book)

@property (nonatomic, weak) Book *myBook;

@end

- (Book *)myBook {
id (^block)(void) = objc_getAssociatedObject (self, @selector(myBook));
id obj = (block ? block () : nil);
return obj;
}

- (void)setMyBook:(Book *)myBook {
id __weak weakObject = myBook;
id (^block)(void) = ^{ return weakObject; };
objc_setAssociatedObject (self, @selector (myBook), block, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

第二种办法:利用中间层。

第三种办法:给关联对象添加一个stub存根对象,存根对象是随同关联对象销毁的,存根对象销毁的dealloc方法里面调用block将宿主对象的手动设置为nil。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (Person *)person {
id obj = objc_getAssociatedObject(self, @selector(person));
return obj;
}

- (void)setPerson:(Person *)person {
__weak XQDetailViewController *obj = self;
[person jj_deallocBlock:^{
/// person对象销毁后手动置为nil.
objc_setAssociatedObject(obj, @selector (person), nil, OBJC_ASSOCIATION_ASSIGN);
}];
/// 这里不能再是retain了,只能是assign。然后关联对象销毁的时候再手动置为nil.
objc_setAssociatedObject(self, @selector (person), person, OBJC_ASSOCIATION_ASSIGN);
}

有没有通用的方法?从系统层面解决?

参考

如何使用 Runtime 给现有的类添加 weak 属性

Weak Associated Object

objc_setAssociatedObject retain atomic or nonatomic

iOS开发遇坑总结

C++ 声明未知大小的全局数组 只能使用

其他

Q:1.全局哈希表或数组它的容量后面还能扩容吗?

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <stdlib.h>
#define MAX 10
char a[MAX]; //全局区
int main()
{
int i;
char b[MAX]; //栈区
char *c=(char *)malloc(MAX * sizeof(char)); //堆区
}

数组的大小其实是固定的。扩容并不是在原来的内存基础上增加内存,而是重新申请一片内存,然后把之前的数据拷贝过来,效果上看起来像是原来的数组变大了。而为了表述方便就把这个过程称为扩容。能够扩容说明这段内存是由程序员管理的,而只有堆区的内存是由程序员负责申请和释放。而全局区的内存是由系统分配管理的,所以无法在全局区内存进行数组扩容。上面的全局关联哈希表实际上类似于一个全局指针变量,指向一个堆上的哈希表。因此可以扩容减容。weak中的全局SideTablesMap哈希表也是一样的。目前很少看到使用直接在全局区创建的数组,一般都是使用指针。

十进制(decimal):非0开头,所以其他进制的写法要前补0用于区分;

二进制(binary): 0b 或 0B开头;

八进制(Octal缩写OCT或O) :常常以数字0开始表明该数字是八进制;

十六进制((简写为hex或下标16)):0x或0X开头;

负数:前面加 -

左移:

1
2
3
NSInteger a = 0 << 8; //           0
NSInteger b = 1 << 8; // 01 0000 0000
NSInteger c = 2 << 8; // 10 0000 0000
觉得文章有帮助可以打赏一下哦!