Associated Objects 提供了为既有类动态添加关联对象的能力,也是对 category 只能拓展方法的一个很好的补充。它通过 Key-Value 的形式将对象与类绑定,并提供类似属性的关联策略,最终保存在全局的 Hash map 中。从源码分析 Associated Objects 的实现及其能与不能,并通过一种 walk around 的方式实现 weak Associated Objects 。
关联对象的核心对象
实现
runtime 提供的我们熟悉的API如下:
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
objc_setAssociatedObject()
方法内部调用了_object_set_associative_reference()
,将参数透传。_object_set_associative_reference
的实现在 objc-references.mm 文件中。该文件包含了 ObjC 关联对象的相关实现,ObjcAssociation
类是对关联对象的封装,包括关联策略(policy)以及 value 、初始化方法以及一些内部方法,这里包括根据不同关联策略对 value 的处理。AssociationsManager
主要用来管理 Association Map 及相关的锁(spinlock_t)操作。
- 1.创建一个
asociationsManager
管理类 - 2.获取
唯一
的全局静态哈希Map:AssociationsHashMap
- 3.判断是否插入的
关联值value
是否存在- 3.1 存在就走第4步
- 3.2 不存在就走
关联对象-插入空流程
- 4.通过
ry_emplac
方法,并创建一个空的ObjectAssociationMap
去取查询的键值对: - 5.如果发现
没有
这个key
就插入一个 空的 BucketT
进去并返回true - 6.通过
setHasAssociatedObjects
方法标记对象存在关联对象
即置isa指针
的has_asso
属性为true
- 7.用当前
policy 和 value
组成了一个ObjcAssociation
替换原来BucketT 中的空
- 8.标记一下
ObjectAssociationMa
的第一次
为false
try_emplace
实现源码
所以到目前为止,关联属性涉及的map结构为:
总结:
AssociationsManager
可以有多个,通过AssociationsManagerLock
锁可以得到一个AssociationsHashMap类型的map
map
中有很多的关联对象map
,类型是ObjectAssociationMap
,其中key为DisguisedPtr<objc_object>
,例如Person会对应一个ObjectAssociationMap,Teacher也会对应一个ObjectAssociationMap
ObjectAssociationMap
哈希表中有很多key-value
键值对,其中key的类型为const void *
,value的类型为ObjcAssociation
- 其中
ObjcAssociation
是用于包装policy和value
的一个类
objc_getAssociatedObject
源码实现
_object_get_associative_reference
方法源码
- 1:创建一个
AssociationsManager
管理类 - 2:获取唯一的全局静态哈希
Map:AssociationsHashMap
- 3:通过
find
方法根据DisguisedPtr
找到AssociationsHashMap
中的iterator
迭代查询器 - 4:如果这个迭代查询器不是最后一个 获取 :
ObjectAssociationMap
(policy
和value
) - 5:通过
find
方法找到ObjectAssociationMap
的迭代查询器获取一个经过属性修饰符修饰的value
- 6:返回
value
_object_remove_assocations
移除方法 _object_remove_assocations(id object, bool deallocating)
中可以看到有一个 deallocating
参数,这是因为 runtime 在销毁对象的 objc_destructInstance
方法中会主动调用移除该对象所有关联对象的方法,如果关联对象为系统对象 SYSTEM_OBJECT
则仅会在 deallocating 时销毁,主动调用时系统对象会被保留:
void
_object_remove_assocations(id object, bool deallocating)
{
ObjectAssociationMap refs{};
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
refs.swap(i->second);
// If we are not deallocating, then SYSTEM_OBJECT associations are preserved.
bool didReInsert = false;
if (!deallocating) {
for (auto &ref: refs) {
if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
i->second.insert(ref);
didReInsert = true;
}
}
}
if (!didReInsert)
associations.erase(i);
}
}
// Associations to be released after the normal ones.
SmallVector<ObjcAssociation *, 4> laterRefs;
// release everything (outside of the lock).
for (auto &i: refs) {
if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
// If we are not deallocating, then RELEASE_LATER associations don't get released.
if (deallocating)
laterRefs.append(&i.second);
} else {
i.second.releaseHeldValue();
}
}
for (auto *later: laterRefs) {
later->releaseHeldValue();
}
}
总结
关联对象的底层调用流程为: 总的来说,关联对象主要就是两层哈希map的处理,即存取时都是两层处理,类似于二维数组
Weak Associated Object
其实思路比较简单,通过增加一个中间层的方式来实现。上面的源码看到,当拥有 Associated Object 的对象被析构,policy 为 RETAIN 的 Associated Object 会被发送 objc_release
消息。这样这个 Associated Object 的 weak reference 便可正常的置为 nil,具体如下 :
一个 weak Associated Object 代理类,拥有一个 weak 修饰的属性
@interface WeakAssociatedObjectProxy : NSObject
@property (nonatomic, weak) id object;
@end
想要类的 category 中进行对象关联:
@implementation NSObject (Weak)
-(NSString *) myObject {
WeakAssociatedObjectProxy * proxy = objc_getAssociatedObject (self, @selector (myObject));
return proxy.object;
}
-(void) setMyObject:(NSString *) myObject {
WeakAssociatedObjectProxy *proxy = [[WeakAssociatedObjectProxy alloc]init];
proxy.object = myObject;
objc_setAssociatedObject (self, @selector (myObject), proxy, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
用 RETAIN 关联的 proxy 对象在 self 被析构时会被发送 release 消息,进而会将其 weak 属性设为 nil 。
另一种方式可以通过 block 来作为中间层,通过 COPY 的 policy 让 block 持有传入的 myObject ,这种方式更加简洁:
@implementation NSObject (Weak)
-(NSString *) myObject {
id (^block)(void) = objc_getAssociatedObject (self, @selector (myObject));
return (block ? block () : nil);
}
-(void) setMyObject:(NSString *) myObject {
id __weak weakObject = myObject;
id (^block)(void) = ^{ return weakObject; };
objc_setAssociatedObject (self, @selector (myObject), block, OBJC_ASSOCIATION_COPY);
}
@end