前言 OC的关联对象经常被用来存储Category中的属性对应的实例变量,当然它还有其他的用途。
这篇文章将讨论关联对象是如何实现的,以及关联对象的内存管理问题。
Demo 先用一段代码来引入要讲的东西:
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 #import "HCBase.h" @interface HCBase (vanney) @property (nonatomic, assign) int cui; @property (nonatomic, strong) NSString *van; @end /* ----------------- 这里是分割线 -------------- */ #import "HCBase+vanney.h" @implementation HCBase (vanney) - (void)setCui:(int)cui { objc_setAssociatedObject(self, "cui", @(cui), OBJC_ASSOCIATION_RETAIN_NONATOMIC); } - (int)cui { return [objc_getAssociatedObject(self, "cui") intValue]; } - (void)setVan:(NSString *)van { objc_setAssociatedObject(self, "van", van, OBJC_ASSOCIATION_COPY_NONATOMIC); } - (NSString *)van { return objc_getAssociatedObject(self, "van"); } @end
可以看到:
在头文件中,我们定义了两个属性 cui 和 van
在.m文件中,我们使用了关联对象来存储这两个实例变量
使用关联对象时,我们还指定了它的内存管理的方式 OBJC_ASSOCIATION_RETAIN_NONATOMIC 和 OBJC_ASSOCIATION_COPY_NONATOMIC
关联对象的实现 关联对象的实现是开源的,在runtime的objc-references.m
源文件中。在OC中,使用一个全局的表来存储所有的关联对象,如下图所示:
关于这张图的解释可以参考我的这篇文章Objective-C runtime - 分类与关联对象
上面的Demo使用了下面两个函数来存取关联对象,我们来看看他们的源码
objc_setAssociatedObject
objc_getAssociatedObject
objc_setAssociatedObject 这个方法用来设置关联对象:
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 void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) { // 关联对象使用 ObjcAssociation 这个c++对象来存储。 // 设置新对象,那么有可能有老的对象会被废弃。那么新建一个ObjcAssociation对象,之后用来存储老的对象 ObjcAssociation old_association(0, nil); // acquireValue函数: 设置新的对象的retainCount,也就是对他进行手动内存管理 id new_value = value ? acquireValue(value, policy) : nil; { AssociationsManager manager; AssociationsHashMap &associations(manager.associations()); disguised_ptr_t disguised_object = DISGUISE(object); // 如果确实是要设置新关联对象的时候 if (new_value) { // break any existing association. AssociationsHashMap::iterator i = associations.find(disguised_object); // 这个对象之前设置过关联对象,也就是说该对象的关联对象数量大于1 if (i != associations.end()) { // secondary table exists ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find(key); // 寻找全局的关联对象表,如果存在老对象的话 if (j != refs->end()) { // 存储老对象到之前新建的ObjcAssociation对象中 old_association = j->second; // 设置新的关联对象 j->second = ObjcAssociation(policy, new_value); } // 不存在老对象,直接设置新关联对象 else { (*refs)[key] = ObjcAssociation(policy, new_value); } } // 这个对象之前没有设置过任何的关联对象 else { // create the new association (first time). ObjectAssociationMap *refs = new ObjectAssociationMap; associations[disguised_object] = refs; (*refs)[key] = ObjcAssociation(policy, new_value); // 将对象的isa的has_assoc字段设置成true object->setHasAssociatedObjects(); } } // 要将关联对象设成 nil,也就是取消该关联对象。先存老对象,再在全局表中删除该关联对象 else { // setting the association to nil breaks the association. AssociationsHashMap::iterator i = associations.find(disguised_object); if (i != associations.end()) { ObjectAssociationMap *refs = i->second; ObjectAssociationMap::iterator j = refs->find(key); if (j != refs->end()) { old_association = j->second; refs->erase(j); } } } } // 释放老对象,retainCount相关,内存管理相关 if (old_association.hasValue()) ReleaseValue()(old_association); }
可以看到设置新的关联对象其实很简单:
现在全局关联对象表中寻找这个key
先存原来关联对象
再设置新的关联对象
释放原有的关联对象
objc_getAssociatedObject 获取关联对象就更简单了,直接到表中去找对应的key的对象就好了。
关联对象的内存管理 在上面讲到的源代码中,涉及到管理对象内存管理的有两个函数
acquireValue
ReleaseValue
requireValue 1 2 3 4 5 6 7 8 9 static id acquireValue(id value, uintptr_t policy) { switch (policy & 0xFF) { case OBJC_ASSOCIATION_SETTER_RETAIN: return ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain); case OBJC_ASSOCIATION_SETTER_COPY: return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy); } return value; }
可以看到,设置关联对象的内存管理方式和你手动传进来的policy有关。
将policy和0XFF相与:
结果是OBJC_ASSOCIATION_SETTER_RETAIN 的话,就调用对象的retain函数,也就是将retainCount加一
结果是OBJC_ASSOCIATION_SETTER_COPY 的话,就调用对象的copy函数,也就是将拷贝对象,并给新对象的retainCount赋1
PS :
没有找到具体的SEL_copy的实现。
至于retain的实现,其实就是将对象的isa的extra_rc字段加1 具体可以参考这篇博客OC源码 —— retain和release
各种常量 在我们自己调用存取关联对象的时候使用了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. */ };
根据名字很容易想到所对应的内存管理语义。
在objc-references.mm
中使用的是另外的常量:
1 2 3 4 5 6 7 8 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) };
可以看到这两个enum中,前三个是相对应的,他们有相同的值
ReleaseValue 1 2 3 4 5 6 7 8 9 10 11 static void releaseValue(id value, uintptr_t policy) { if (policy & OBJC_ASSOCIATION_SETTER_RETAIN) { ((id(*)(id, SEL))objc_msgSend)(value, SEL_release); } } struct ReleaseValue { void operator() (ObjcAssociation &association) { releaseValue(association.value(), association.policy()); } };
再来看看如何释放对象。可以看到,他是将关联对象的policy
和OBJC_ASSOCIATION_SETTER_RETAIN
相与,简单计算就会发现:当policy为retain和copy时,这个if判断都是正确的,也就会对相应关联对象执行release操作。和我们预想的是一致的。
参考