在OC底层原理14 - 类的加载之分类一文中提及,通过使用关联对象的方法实现分类中属性的getter/setter方法。本节将从底层源码的角度来探索关联对象。
关联对象的使用
-
objc_setAssociatedObject
:给指定对象关联某个值 -
objc_getAssociatedObject
:根据标识符,从对象中获取关联的值。
接下来逐一进行探索
objc_setAssociatedObject 源码探索
- 查看
objc_setAssociatedObject
实现
/*
该方法共4个参数:
object:要关联的对象
key:标识符,方便于下次查找
value:关联的值
policy:关联的策略,如nonatomic、atomic、assign等
*/
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
源码
void _object_set_associative_reference(id object, const void *key, id value, uintptr_t policy) {
//忽略非重点代码
//...
//将object包装成DisguisedPtr<objc_object>类型
DisguisedPtr<objc_object> disguised{(objc_object *)object};
//将policy - value包装成ObjcAssociation类型
ObjcAssociation association{policy, value};
// retain the new value (if any) outside the lock.
association.acquireValue();
bool isFirstAssociation = false;
{
//初始化manager变量
AssociationsManager manager;
//获取AssociationsHashMap,注意:AssociationsHashMap有且只有一个
AssociationsHashMap &associations(manager.get());
if (value) {//若有值,则进行关联对象
//此处是重点
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {
/* it's the first association we make */
isFirstAssociation = true;
}
/* establish or replace the association */
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);
}
}
}
}
}
if (isFirstAssociation)
object->setHasAssociatedObjects();
association.releaseHeldValue();
}
从以上源码中可以得知,当有value时,需要将值与对象进行关联。这里重点分析。
- 【1】获取一个
AssociationsHashMap
。
typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;
- 【2】通过
try_emplace
方法,查找AssociationsHashMap
中是否存在object
的关联对象表。
std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
BucketT *TheBucket;
//查找是否存在Key的bucket
if (LookupBucketFor(Key, TheBucket))
//存在Key的bucket,将bucket与false等包装成pair并返回
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
false); // Already in map.
// Otherwise, insert the new element.
//不存在Key的bucket,则new一个新的bucket,并将这个新的bucket和true包装成pair进行返回
TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
true);
}
此处通过lldb来查看一下,try_emplace函数
过程中TheBucket
的变化情况。
//LookupBucketFor函数前TheBucket的值,此时是垃圾数据
(lldb) p/x TheBucket
(objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > > *) $0 = 0x00007ffeefbff1e0
//第一步:调用LookupBucketFor(Key, TheBucket);
//LookupBucketFor函数后TheBucket的值
(lldb) p/x TheBucket
(objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > > *) $1 = 0x0000000000000000
//以上表明当前不存在AssociationsHashMap
//第二步:经过InsertIntoBucketImpl后,开辟AssociationsHashMap中Buckets空间,并计算Key索引,找到Buckets与Key索引对应且未被占用的Bucket
(lldb) p/x TheBucket
(objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > > *) $3 = 0x000000010082b210
(lldb) p *$3
(objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >) $4 = {
std::__1::pair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > > = {
first = (value = 1)
second = {
Buckets = 0x1e317413ba120c7e
NumEntries = 422045868
NumTombstones = 2223205251
NumBuckets = 0
}
}
}
//第三步,将DisguisedPtr<objc_object>赋值给Bucket的第一个参数
(lldb) p/x TheBucket
(objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > > *) $5 = 0x000000010082b210
(lldb) p/x *$5
(objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >) $6 = {
std::__1::pair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > > = {
first = (value = 0xfffffffeff9892d0)
second = {
Buckets = 0x1e317413ba120c7e
NumEntries = 0x1927e8ac
NumTombstones = 0x84836b83
NumBuckets = 0x00000000
}
}
}
//第四步,将ObjectAssociationMap赋值给Bucket的第二个参数,此时ObjectAssociationMap中还没与标识符及value关联
(lldb) p/x TheBucket
(objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > > *) $8 = 0x000000010082b210
(lldb) p/x *$8
(objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >) $9 = {
std::__1::pair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > > = {
first = (value = 0xfffffffeff9892d0)
second = {
Buckets = 0x0000000000000000
NumEntries = 0
NumTombstones = 0
NumBuckets = 0
}
}
}
-
LookupBucketFor
函数源码
bool LookupBucketFor(const LookupKeyT &Val, const BucketT *&FoundBucket) const {
//获取AssociationsHashMap表中Buckets的地址
const BucketT *BucketsPtr = getBuckets();
//获取当前AssociationsHashMap表中Bucket的个数
const unsigned NumBuckets = getNumBuckets();
//此处表示当前AssociationsHashMap没有Bucket
if (NumBuckets == 0) {
FoundBucket = nullptr;
return false;
}
// FoundTombstone - Keep track of whether we find a tombstone while probing.
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!");
//计算Buckets中,Val对应的索引
unsigned BucketNo = getHashValue(Val) & (NumBuckets-1);
unsigned ProbeAmt = 1;
//根据索引、偏移遍历Buckets,只有两种情况退出,一种是找到Val对应的Buckets,另一种是未找到Buckets
while (true) {
//根据索引和首地址,进行偏移找到当前ThisBucket
const BucketT *ThisBucket = BucketsPtr + BucketNo;
//比较ThisBucket的Val与传参Val是否一致,若一致则表示找到bucket
if (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) {
FoundBucket = ThisBucket;
return true;
}
// If we found an empty bucket, the key doesn't exist in the set.
// Insert it and return the default value.
//当前ThisBucket是否是一个EmptyKey,若是的话,表示Buckets已经查找完了,但是未找到Val对应的Bucket
if (LLVM_LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) {
// If we've already seen a tombstone while probing, fill it in instead
// of the empty bucket we eventually probed to.
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);
}
}
【总结】
【第一步】判断AssociationsHashMap表
中是否有Buckets
。若有则进入第二步。若无则将FoundBucket置为空并返回false
。
【第二步】根据Val计算一个hash索引,遍历Buckets查到是否存在与Val对应的Bucket。若存在
则将FoundBucket置为查找到的Bucket并返回true
。否则将FoundBucket置为空并返回false
。
-
InsertIntoBucket
源码
BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key,
ValueArgs &&... Values) {
//新建一个Bucket,插入至AssociationsHashMap表的Buckets中
TheBucket = InsertIntoBucketImpl(Key, Key, TheBucket);
//将新建Bucket的第一个参数与Key进行关联
TheBucket->getFirst() = std::forward<KeyArg>(Key);
//将新建Bucket的第二个参数与Values进行关联,此时的Value为ObjectAssociationMap
::new (&TheBucket->getSecond()) ValueT(std::forward<ValueArgs>(Values)...);
return TheBucket;
}
-
InsertIntoBucketImpl
源码
BucketT *InsertIntoBucketImpl(const KeyT &Key, const LookupKeyT &Lookup, BucketT *TheBucket) {
unsigned NewNumEntries = getNumEntries() + 1;
unsigned NumBuckets = getNumBuckets();
if (LLVM_UNLIKELY(NewNumEntries * 4 >= NumBuckets * 3)) {
//通过allocateBuckets分配空间
this->grow(NumBuckets * 2);
LookupBucketFor(Lookup, TheBucket);
NumBuckets = getNumBuckets();
} else if (LLVM_UNLIKELY(NumBuckets-(NewNumEntries+getNumTombstones()) <= NumBuckets/8)) {
//扩容
this->grow(NumBuckets);
LookupBucketFor(Lookup, TheBucket);
}
ASSERT(TheBucket);
if (KeyInfoT::isEqual(TheBucket->getFirst(), getEmptyKey())) {
// Replacing an empty bucket.
incrementNumEntries();
} else if (KeyInfoT::isEqual(TheBucket->getFirst(), getTombstoneKey())) {
// Replacing a tombstone.
incrementNumEntries();
decrementNumTombstones();
} else {
// we should be purging a zero. No accounting changes.
ASSERT(ValueInfoT::isPurgeable(TheBucket->getSecond()));
TheBucket->getSecond().~ValueT();
}
return TheBucket;
}
【总结】
【第一步】判断AssociationsHashMap表
中是否有Buckets
,若没有,则通过allocateBuckets
,分配4个Bucket的大小
的空间,并将Bucket
的第一个
参数置为EmptyKey
。
【第二步】将Key
通过hash计算
得到一个索引值BucketNo
,在Buckets
中找到BucketNo
对应的且未占用的Bucket
。并将其赋值给TheBucket
并返回。
- 【3】使用
try_emplace
函数,继续从DisguisedPtr<objc_object>
对应的Bucket中获取ObjcAssociation
,查找原理同上。
//此时查找的是DisguisedPtr<objc_object>值对应的Map
(lldb) p/x TheBucket
(objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> *) $15 = 0x0000000000000000
//调用InsertIntoBucketImpl后,开辟ObjcAssociation空间,并返回索引相应的Bucket
(lldb) p/x TheBucket
(objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> *) $16 = 0x000000010082abc8
(lldb) p/x *$16
(objc::detail::DenseMapPair<const void *, objc::ObjcAssociation>) $17 = {
std::__1::pair<const void *, objc::ObjcAssociation> = {
first = 0xffffffffffffffff
second = {
_policy = 0x0000000000000000
_value = 0x0006000000000000
}
}
}
//将第一个参数设置为Key
(lldb) p/x *$16
(objc::detail::DenseMapPair<const void *, objc::ObjcAssociation>) $18 = {
std::__1::pair<const void *, objc::ObjcAssociation> = {
first = 0x0000000100003ca4 //"cate_name"
second = {
_policy = 0x0000000000000000
_value = 0x0006000000000000
}
}
}
//将第二个参数设置为Value
(lldb) p/x *$16
(objc::detail::DenseMapPair<const void *, objc::ObjcAssociation>) $19 = {
std::__1::pair<const void *, objc::ObjcAssociation> = {
first = 0x0000000100003ca4 //对应于Key:"cate_name"
second = {
_policy = 0x0000000000000003 //对应于policy:3
_value = 0x0000000100004040 "123" //对应于value:@"123"
}
}
}
此时,已经完成object与value的关联。
当objc_setAssociatedObject
函数传入的value为空时,则表示移除关联
。
【1】在AssociationsHashMap
中找到以对象为Key
的ObjectAssociationMap
。
【2】在ObjectAssociationMap
中找到以关联标识符
为Key的ObjcAssociation
并将其移除。
【3】将以对象为Key
的ObjectAssociationMap
移除。
objc_setAssociatedObject 源码总结
-
关联对象
是由AssociationsManager
管理的AssociationsHashMap
,AssociationsHashMap
只存在一份。AssociationsHashMap
的第一个参数为包装的对象
,第二个参数为ObjectAssociationMap
。 -
ObjectAssociationMap
主要是存储关联对象的值、标识符、策略
等信息。 - 调用
objc_setAssociatedObject
的过程:- 在
AssociationsHashMap
中,查找Key为object
的Map。若未找到,则新建一个以object
为Key的Map。 - 在
object
的Map查找以关联标识符
为Key的ObjcAssociation
。若未找到,则新建一个以关联标识符
为Key的ObjcAssociation
,并将关联对象的值和策略
等信息设置至ObjcAssociation
中。
- 在
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
AssociationsHashMap &associations(manager.get());
//遍历查找AssociationsHashMap,找到以object为Key的ObjectAssociationMap
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
//从得到的ObjectAssociationMap中获取ObjcAssociation
ObjectAssociationMap &refs = i->second;
//遍历ObjcAssociation,找到以关联标识符为Key的ObjcAssociation
ObjectAssociationMap::iterator j = refs.find(key);
if (j != refs.end()) {
association = j->second;
association.retainReturnedValue();
}
}
}
return association.autoreleaseReturnedValue();
}
objc_getAssociatedObject 源码总结
从源码中看,获取关联对象的值较为简单
【1】在AssociationsHashMap
中找到以对象为Key
的ObjectAssociationMap
。
【2】在ObjectAssociationMap
中找到以关联标识符为Key
的ObjcAssociation
。
【3】从ObjcAssociation
中找到value。
关联对象注意
关联对象
不需要我们手动移除
,会在对象析构即dealloc时释放
。
dealloc的源码查找路径为:dealloc -> _objc_rootDealloc -> rootDealloc -> object_dispose(释放对象)-> objc_destructInstance -> _object_remove_assocations