关联对象实现原理
Posted WeaterMr
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关联对象实现原理相关的知识,希望对你有一定的参考价值。
前言
提到关联对象不得不让我们想到两个概念:
1.分类 category
- 专门用来给类添加新的方法
- 不能给类添加成员变量和属性,添加了成员变量,也无法获取到
- 注意:可以通过
关联对象
的方式给类动态添加属性
。 - 分类中用
@property
定义变量,只会生成变量的getter
、setter
方法的声明,不能生成方法的实现和带下划线的成员变量。
2.类扩展 extension
- 可以说成是特殊的分类,也称作匿名分类
- 可以给类添加成员属性,但是是私有变量
- 可以给类添加方法,也是私有方法
他们的主要区别是分类是通过runtime运行时动态添加到类中,而类扩展是在编译期,直接生成的set,get方法,而内存的开辟是在类被实例化时候做的。
通过分类添加属性实现代码
@property (nonatomic, copy) NSString *like_name;
- (void)setLike_name:(NSString *)like_name {
objc_setAssociatedObject(self, "like_name", like_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
objc_setAssociatedObject 分析
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy){
_object_set_associative_reference(object, key, value, policy);
}
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
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};
// 在锁外保留新值(如果有的话)。
association.acquireValue();
bool isFirstAssociation = false;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
if (value) {
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {
/* 这是我们建立的第一个联想 */
isFirstAssociation = true;
}
/* 建立或替换关联 */
auto &refs = refs_result.first->second;
auto result = refs.try_emplace(key, std::move(association));
if (!result.second) {
association.swap(result.first->second);
}
} 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) {
//擦除
associations.erase(refs_it);
}
}
}
}
}
// 在锁外部调用setHasAssociatedObjects,因为这个
// 将调用对象的_noteAssociatedObjects方法,如果是
// 有一个,这可能触发+初始化,那会怎样呢
// 任意的东西,包括设置更多的关联对象
if (isFirstAssociation)
object->setHasAssociatedObjects();
// 释放旧值(锁外)
association.releaseHeldValue();
}
forbidsAssociatedObjects
// 类不允许在其实例上关联对象
#define RW_FORBIDS_ASSOCIATED_OBJECTS (1<<20)
bool forbidsAssociatedObjects() {
return (data()->flags & RW_FORBIDS_ASSOCIATED_OBJECTS);
}
AssociationsManager
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();
}
};
AssociationsHashMap 的数据结构:
AssociationsHashMap
try_emplace
// 如果键尚未在映射中,则将键值对插入映射中
// 如果键不在映射中,则就地构造值,否则
// 它没有移动
template <typename... Ts>
std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
BucketT *TheBucket;
if (LookupBucketFor(Key, TheBucket))
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
false); // 已经在表上
// 否则,插入新元素
TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
true);
}
下面我们重点看下 try_emplace 的一个流程:
/// LookupBucketFor—为Val查找适当的bucket,并将其返回
/// FoundBucket。如果bucket包含键和值,则返回
/// true,否则返回一个带有空标记或墓碑和的桶
/// returns false.
template<typename LookupKeyT>
bool LookupBucketFor(const LookupKeyT &Val,
const BucketT *&FoundBucket) const {
const BucketT *BucketsPtr = getBuckets();
const unsigned NumBuckets = getNumBuckets();
if (NumBuckets == 0) {
FoundBucket = nullptr;
return false;
}
// FoundTombstone -跟踪我们是否在探测时找到了一个墓碑
const BucketT *FoundTombstone = nullptr;
const KeyT EmptyKey = getEmptyKey();
const KeyT TombstoneKey = getTombstoneKey();
assert(!KeyInfoT::isEqual(Val, EmptyKey) &&
!KeyInfoT::isEqual(Val, TombstoneKey) &&
"Empty/Tombstone value shouldn't be inserted into map!");
//哈希函数 得到 下标
unsigned BucketNo = getHashValue(Val) & (NumBuckets-1);
unsigned ProbeAmt = 1;
while (true) {
const BucketT *ThisBucket = BucketsPtr + BucketNo;
// 发现Val的桶吗?如果是,返回它
if (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) {
FoundBucket = ThisBucket;
return true;
}
// 如果我们发现一个空的桶,则该键不存在于集合中
// 插入它并返回默认值
if (LLVM_LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) {
// 如果我们在探测的时候已经看到了墓碑,那就把它填上
// 我们最终探测到的空桶
FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket;
return false;
}
// 如果这是墓碑,记住它。如果瓦尔没有出现在地图上,我们
// 宁愿返回它而不是需要更多探测的东西
// 对于零值也是如此
if (KeyInfoT::isEqual(ThisBucket->getFirst(), TombstoneKey) &&
!FoundTombstone)
FoundTombstone = ThisBucket; // Remember the first tombstone found.
if (ValueInfoT::isPurgeable(ThisBucket->getSecond()) && !FoundTombstone)
FoundTombstone = ThisBucket;
// 否则,它是一个哈希冲突或墓碑,继续二次
// 探索
if (ProbeAmt > NumBuckets) {
FatalCorruptHashTables(BucketsPtr, NumBuckets);
}
BucketNo += ProbeAmt++;
BucketNo &= (NumBuckets-1);
}
}
// 虽然两个方法名字都一样,但是,通过参数不同,可以发现方法内部调用了上面的 LookupBucketFor
template <typename LookupKeyT>
bool LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket) {
const BucketT *ConstFoundBucket;
bool Result = const_cast<const DenseMapBase *>(this)
->LookupBucketFor(Val, ConstFoundBucket);
FoundBucket = const_cast<BucketT *>(ConstFoundBucket);
return Result;
}
获取关联对象值
objc_getAssociatedObject
id
objc_getAssociatedObject(id object, const void *key)
{
return _object_get_associative_reference(object, key);
}
_object_get_associative_reference
id
_object_get_associative_reference(id object, const void *key)
{
ObjcAssociation association{};
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
// 通过 object 找到 ObjectAssociationMap
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
ObjectAssociationMap &refs = i->second;
// 通过 key 找到 ObjcAssociation
ObjectAssociationMap::iterator j = refs.find(key);
if (j != refs.end()) {
association = j->second;
association.retainReturnedValue();
}
}
}
return association.autoreleaseReturnedValue();
}
关联对象总结
一个全局的数据集合 AssociationHashMap
,一个对象会对应一个 ObjectAssociationMap
,而该对象所关联的属性,都已键值对的形式存储在 ObjectAssociationMap
中。其实就是两层哈希map , 存取的时候两层处理(类似二维数组)
关联对象的销毁
在对象被销毁时会调用 dealloc 方法,同时 会对 关联对象进行释放。
rootDealloc->object_dispose->objc_destructInstance
objc_destructInstance
/***********************************************************************
* objc_destructInstance
* 在不释放内存的情况下销毁实例
* c++调用析构函数
* 调用ARC ivar清理
* 删除关联的引用
* 返回的obj。如果' obj '为nil,则不执行任何操作
**********************************************************************/
void *objc_destructInstance(id obj)
{
if (obj) {
// 一次读取所有标志以提高性能
bool cxx = obj->hasCxxDtor();
bool assoc = obj->hasAssociatedObjects();
// 这个顺序很重要
if (cxx) object_cxxDestruct(obj);
// 删除关联的引用
if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
obj->clearDeallocating();
}
return obj;
}
_object_remove_assocations
// 与设置/获取相关引用不同
// 这个函数是性能敏感的,因为
// 不能跟踪的原始isa对象(如OS对象)
// 是否有关联的对象
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);
//如果我们没有解除分配,那么SYSTEM_OBJECT关联将被保留
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);
}
}
// 在正常的关联之后才会被释放
SmallVector<ObjcAssociation *, 4> laterRefs;
// 释放(锁外的)所有东西
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();
}
}
设值流程
- 1: 创建一个
AssociationsManager
管理类 - 2: 获取唯一的全局静态哈希
Map
- 3: 判断是否插入的关联值是否存在:
- 3.1: 存在走第4步
- 3.2: 不存在就走 : 关联对象插入空流程
- 4: 创建一个空的
ObjectAssociationMap
去取查询的键值对 - 5: 如果发现没有这个 key 就插入一个 空的
BucketT
进去 返回 - 6: 标记对象存在关联对象
- 7: 用当前 修饰策略 和 值 组成了一个
ObjcAssociation
替换原来BucketT
中的空 - 8: 标记一下
ObjectAssociationMap
的第一次为false
如果插入空值会根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器并置空
取值流程
- 1: 创建一个
AssociationsManager
管理类 - 2: 获取唯一的全局静态哈希Map
- 3: 根据
DisguisedPtr
找到AssociationsHashMap
中的iterator
迭代查询器 - 4: 如果这个迭代查询器不是最后一个 获取 :
ObjectAssociationMap
(这里有策略和value) - 5: 找到
ObjectAssociationMap
的迭代查询器获取一个经过属性修饰符修饰的value
- 6: 返回
_value
以上是关于关联对象实现原理的主要内容,如果未能解决你的问题,请参考以下文章