这是我参与8月更文挑战的第7天,活动详情查看:8月更文挑战
Hi 👋
| 我的个人项目 | 扫雷Elic 无尽天梯 | 梦见账本 |
|---|---|---|
| 类型 | 游戏 | 财务 |
| AppStore | Elic | Umemi |
前言
前文我们深入探索了有关分类的一切,而为分类添加关联对象是我们比较常用的,但是你真的了解关联对象么?知道它是怎么设置和释放的呢?
本文基于
objc4-818.2源码
一、 关联对象
由于分类的属性不会生产相关的成员变量,所以我们需要自己实现 set/get 方法。如下:
// .h
@property (nonatomic, copy) NSString *cateName;
// .m
- (void)setCateName:(NSString *)cateName {
objc_setAssociatedObject(self, "cateName", cateName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)cateName {
return objc_getAssociatedObject(self, "cateName");
}
那么,objc_setAssociatedObject 和 objc_getAssociatedObject,究竟做了什么呢?
二、 objc_setAssociatedObject
实际调用了 _object_set_associative_reference
2.1 AssociationsHashMap
通过断点我们发现了一个 AssociationsHashMap。
可以看出它是一个 全局静态 的 Storage 类型的 HashMap。用来保存运行时所有的关联对象的关联关系。
2.2 try_emplace
如果表中没有的话,就将键值对插入表中。
Inserts key,value pair into the map if the key isn't already in the map. The value is constructed in-place if the key is not in the map, otherwise it is not moved.
一次 set 两次调用了 try_emplace
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});- 只用来检查是否存在关联对象的
keyObjectAssociationMap{}是个空的不做处理
- 只用来检查是否存在关联对象的
auto result = refs.try_emplace(key, std::move(association));- 这里才是真的设置
value
- 这里才是真的设置
触发set
通过调用 LookupBucketFor 查找表中是否已经存在了对应的关联对象。
第一次调用set方法
由于关联值不存在,所以走了插入流程
第二次调用set方法
关联值存在,更新
2.3 LookupBucketFor
和查找方法缓存的算法基本一致。
2.4 InsertIntoBucket
插入表中
template <typename KeyArg, typename... ValueArgs>
BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key,
ValueArgs &&... Values) {
TheBucket = InsertIntoBucketImpl(Key, Key, TheBucket);
// first 就是 key
TheBucket->getFirst() = std::forward<KeyArg>(Key);
// second 是Value
::new (&TheBucket->getSecond()) ValueT(std::forward<ValueArgs>(Values)...);
return TheBucket;
}
三、 关联对象释放
思考:在对象
dealloc的时候,会自动清理管理那对象么?
3.1 dealloc
我们从 dealloc 开始找:
- dealloc
- _objc_rootDealloc
- rootDealloc
- 如果有关联对象
- object_dispose
- objc_destructInstance
- _object_remove_assocations
- 来释放关联对象
- _object_remove_assocations
- objc_destructInstance
- rootDealloc
- _objc_rootDealloc
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();
}
}
四、 总结
关联对象实际是保存在一张全局的 HashMap 中的。并没有直接改变原本 Class 的数据结构