通常我們會(huì)在分類(lèi)中添加方法,而無(wú)法在在分類(lèi)中添加屬性,我們?cè)诜诸?lèi)中添加@property(nonatomic, copy) NSString *name;
時(shí)編譯器并不會(huì)在編譯時(shí)幫我們自動(dòng)生成setter和getter方法凶赁,也不會(huì)生成”_屬性名“的成員變量。
但我們可以通過(guò)關(guān)聯(lián)對(duì)象技術(shù)給類(lèi)添加屬性。例如,我們要給Animal
類(lèi)添加一個(gè)name
屬性备韧,可以這么實(shí)現(xiàn):
@interface Animal (Cate)
@property(nonatomic, copy) NSString *name;
@end
@implementation Animal (Cate)
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, "name", name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
return objc_getAssociatedObject(self, "name");
}
@end
關(guān)聯(lián)對(duì)象技術(shù)我們使用了使用了兩個(gè)api -- objc_setAssociatedObject
和objc_getAssociatedObject
,它的底層是如何實(shí)現(xiàn)的呢痪枫,我們可以通過(guò)objc-7.8.1探究织堂。
objc_setAssociatedObject
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
SetAssocHook.get()(object, key, value, policy);
}
SetAssocHook
是一個(gè)封裝了一個(gè)函數(shù)指針的對(duì)象,他在源碼里是這么定義的
static ChainedHookFunction<objc_hook_setAssociatedObject> SetAssocHook{_base_objc_setAssociatedObject};
關(guān)于ChainedHookFunction
奶陈,點(diǎn)進(jìn)去查看它的實(shí)現(xiàn)
// Storage for a thread-safe chained hook function.
// get() returns the value for calling.
// set() installs a new function and returns the old one for chaining.
// More precisely, set() writes the old value to a variable supplied by
// the caller. get() and set() use appropriate barriers so that the
// old value is safely written to the variable before the new value is
// called to use it.
//
// T1: store to old variable; store-release to hook variable
// T2: load-acquire from hook variable; call it; called hook loads old variable
template <typename Fn>
class ChainedHookFunction {
std::atomic<Fn> hook{nil};
public:
ChainedHookFunction(Fn f) : hook{f} { };
Fn get() {
return hook.load(std::memory_order_acquire);
}
void set(Fn newValue, Fn *oldVariable)
{
Fn oldValue = hook.load(std::memory_order_relaxed);
do {
*oldVariable = oldValue;
} while (!hook.compare_exchange_weak(oldValue, newValue,
std::memory_order_release,
std::memory_order_relaxed));
}
};
通過(guò)它的注釋可以了解到捧挺,ChainedHookFunction
是用于線程安全鏈構(gòu)函數(shù)的存儲(chǔ),通過(guò)get()
返回調(diào)用值尿瞭,通過(guò)set()
安裝一個(gè)新的函數(shù),并返回舊函數(shù)翅睛,更確切的說(shuō)声搁,set()
將舊值寫(xiě)入調(diào)用方提供的變量,get()
和 set()
使用適當(dāng)?shù)臇艡谑沟迷谛轮嫡{(diào)用前安全地寫(xiě)入變量捕发。
所以疏旨,SetAssocHook.get()
返回的是傳入的函數(shù)指針_base_objc_setAssociatedObject
,objc_setAssociatedObject
底層調(diào)用的其實(shí)是_base_objc_setAssociatedObject
扎酷,我們也可以通過(guò)符號(hào)斷點(diǎn)驗(yàn)證調(diào)用的是否正確檐涝,這里就不做演示。
進(jìn)入_base_objc_setAssociatedObject
的實(shí)現(xiàn)查看法挨,它的底層調(diào)用了_object_set_associative_reference
谁榜。
static void
_base_objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
_object_set_associative_reference(object, key, value, policy);
}
_object_set_associative_reference
中便有關(guān)聯(lián)對(duì)象實(shí)現(xiàn)的具體代碼
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
// This code used to work when nil was passed for object and key. Some code
// probably relies on that to not crash. Check and handle it explicitly.
// rdar://problem/44094390
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};
// retain the new value (if any) outside the lock.
association.acquireValue();
{
AssociationsManager manager;
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 */
object->setHasAssociatedObjects();
}
/* 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);
}
}
}
}
}
// release the old value (outside of the lock).
association.releaseHeldValue();
}
通過(guò)閱讀源碼,關(guān)聯(lián)對(duì)象設(shè)值流程為:
- 將被關(guān)聯(lián)對(duì)象封裝成
DisguisedPtr
類(lèi)型凡纳,將策略和關(guān)聯(lián)值封裝成ObjcAssociation
類(lèi)型窃植,并根據(jù)策略處理關(guān)聯(lián)值。 - 創(chuàng)建一個(gè)
AssociationsManager
管理類(lèi) - 獲取唯一的全局靜態(tài)哈希Map
- 判斷是否插入的關(guān)聯(lián)值是否存在荐糜,如果存在走第4步巷怜,如果不存在則執(zhí)行關(guān)聯(lián)對(duì)象插入空流程
- 創(chuàng)建一個(gè)空的
ObjectAssociationMap
去取查詢(xún)的鍵值對(duì) - 如果發(fā)現(xiàn)沒(méi)有這個(gè)key就插入一個(gè)空的
BucketT
進(jìn)去返回 - 標(biāo)記對(duì)象存在關(guān)聯(lián)對(duì)象
- 用當(dāng)前修飾策略和值組成了一個(gè)
ObjcAssociation
替換原來(lái)BucketT
中的空 - 標(biāo)記一下
ObjectAssociation
的第一次為false
關(guān)聯(lián)對(duì)象插入空流程為:
- 根據(jù)
DisguisedPtr
找到AssociationsHashMap
中的迭代查詢(xún)器、 - 清理迭代器
- 插入空值(相當(dāng)于清除)
處理傳入變量
DisguisedPtr<objc_object> disguised{(objc_object *)object};
ObjcAssociation association{policy, value};
// retain the new value (if any) outside the lock.
association.acquireValue();
這部分代碼比較簡(jiǎn)單暴氏,可以進(jìn)入association.acquireValue
看看關(guān)聯(lián)值是如何處理的延塑。
inline void acquireValue() {
if (_value) {
switch (_policy & 0xFF) {
case OBJC_ASSOCIATION_SETTER_RETAIN:
_value = objc_retain(_value);
break;
case OBJC_ASSOCIATION_SETTER_COPY:
_value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy));
break;
}
}
}
這份代碼中,如果_policy
是OBJC_ASSOCIATION_SETTER_RETAIN
答渔,則對(duì)關(guān)聯(lián)值進(jìn)行retain
操作关带,如果是OBJC_ASSOCIATION_SETTER_COPY
,則對(duì)關(guān)聯(lián)值進(jìn)行copy
操作研儒,其他的則不做任何處理豫缨。
創(chuàng)建哈希表管理類(lèi)獲取全局哈希表
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();
}
};
在AssociationsManager
的構(gòu)造方法里独令,通過(guò)AssociationsManagerLock.lock
加了一把鎖,在當(dāng)前AssociationsManager
釋放之前好芭,后續(xù)創(chuàng)建的AssociationsManager
都無(wú)法對(duì)其管理的資源進(jìn)行操作燃箭,從而保證了線程安全,在通過(guò)get()
拿到全局唯一的哈希表(因?yàn)?code>_mapStorage是static
修飾的)
關(guān)聯(lián)非空值流程
當(dāng)要關(guān)聯(lián)的值非空時(shí)舍败,我們需要將這個(gè)值與當(dāng)前對(duì)象關(guān)聯(lián)起來(lái)招狸,這一部分代碼為
auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
if (refs_result.second) {
/* it's the first association we make */
object->setHasAssociatedObjects();
}
/* 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);
}
通過(guò)源碼,一步步分析它的原理
分析 try_emplace 實(shí)現(xiàn)流程
try_emplace
是DenseMap
類(lèi)的一個(gè)方法邻薯,這個(gè)方法在這個(gè)流程中被調(diào)用兩次裙戏,第一次調(diào)用的是全局關(guān)聯(lián)對(duì)象哈希表的try_emplace
,傳了封裝了當(dāng)前對(duì)象的disguised
作為key
和一個(gè)空的ObjectAssociationMap
作為第二個(gè)元素厕诡,第二次累榜,調(diào)用的第一次try_emplace
得到的map,傳遞了關(guān)聯(lián)對(duì)象的key值作為key
灵嫌,傳遞了封裝value和策略的association
作為第二個(gè)參數(shù)壹罚。
try_emplace
內(nèi)部到底做了什么事情呢,我們可以去源碼一探究竟寿羞。
// 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.
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); // Already in map.
// Otherwise, insert the new element.
TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
return std::make_pair(
makeIterator(TheBucket, getBucketsEnd(), true),
true);
}
try_emplace
具體流程分析如下:
- 創(chuàng)建一個(gè)空的
BucketT
猖凛,調(diào)用LookupBucketFor
查找Key
對(duì)應(yīng)的BucketT
,如果能夠找到绪穆,將找到的BucketT
賦值給剛剛創(chuàng)建的那個(gè)空的BucketT
辨泳,并將BucketT
封裝成DenseMapIterator
作為類(lèi)對(duì)的第一個(gè)元素,將false
作為第二個(gè)元素玖院,并將該類(lèi)對(duì)返回菠红,此時(shí)的BucketT
存放的是上次存放的key
和value
- 如果沒(méi)有找到,那么將傳入的Key和Value插入創(chuàng)建新的
BucketT
难菌,同樣創(chuàng)建一個(gè)DenseMapIterator
和Bool
組成的類(lèi)對(duì)途乃,只不過(guò)此時(shí)傳遞布爾值為true
,此時(shí)的BucketT
已經(jīng)存放了傳入的key
和value
LookupBucketFor
這個(gè)方法是在當(dāng)前DenseMap
中通過(guò)key
查找對(duì)應(yīng)的bucket
扔傅,如果找到匹配的耍共,并且bucket
包含key和value,則返回true
猎塞,并將找到bucket
通過(guò)FoundBucket
返回试读,否則,返回false
并通過(guò)FoundBucket
返回一個(gè)空的bucket
荠耽。
另外貼上代碼
/// LookupBucketFor - Lookup the appropriate bucket for Val, returning it in
/// FoundBucket. If the bucket contains the key and a value, this returns
/// true, otherwise it returns a bucket with an empty marker or tombstone and
/// 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 - 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!");
unsigned BucketNo = getHashValue(Val) & (NumBuckets-1);
unsigned ProbeAmt = 1;
while (true) {
const BucketT *ThisBucket = BucketsPtr + BucketNo;
// Found Val's bucket? If so, return it.
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.
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 this is a tombstone, remember it. If Val ends up not in the map, we
// prefer to return it than something that would require more probing.
// Ditto for zero values.
if (KeyInfoT::isEqual(ThisBucket->getFirst(), TombstoneKey) &&
!FoundTombstone)
FoundTombstone = ThisBucket; // Remember the first tombstone found.
if (ValueInfoT::isPurgeable(ThisBucket->getSecond()) && !FoundTombstone)
FoundTombstone = ThisBucket;
// Otherwise, it's a hash collision or a tombstone, continue quadratic
// probing.
if (ProbeAmt > NumBuckets) {
FatalCorruptHashTables(BucketsPtr, NumBuckets);
}
BucketNo += ProbeAmt++;
BucketNo &= (NumBuckets-1);
}
}
非空值流程流程分析
通過(guò)閱讀源碼钩骇,可以總結(jié)出他的流程為:
- 嘗試通過(guò)關(guān)聯(lián)對(duì)象全局hashMap的
try_emplace
方法找到當(dāng)前對(duì)象對(duì)應(yīng)的bucket
,此時(shí)的bucket
的key
為當(dāng)前對(duì)象,value為一張ObjectAssociationMap
倘屹,也是一張hashMap - 如果查找返回的
refs_result
類(lèi)對(duì)第二個(gè)元素為true
银亲,也就是說(shuō)當(dāng)前對(duì)象第一次有關(guān)聯(lián)對(duì)象,將當(dāng)前對(duì)象標(biāo)記為有關(guān)聯(lián)對(duì)象(修改isa) - 通過(guò)
refs_result.first->second
拿到當(dāng)前對(duì)象對(duì)應(yīng)ObjectAssociationMap
纽匙,調(diào)用這張ObjectAssociationMap
的try_emplace
找到關(guān)聯(lián)對(duì)象標(biāo)識(shí)符對(duì)應(yīng)的value务蝠,也就是value
和policy
組裝成的ObjcAssociation
對(duì)象 - 如果第二個(gè)
try_emplace
方法返回的result
的第二個(gè)元素為true
說(shuō)明這是該對(duì)象第一次插入該標(biāo)識(shí)符的值,此時(shí)的value
和policy
已經(jīng)在try_emplace
插入到ObjectAssociationMap
烛缔,不需要進(jìn)一步處理 - 如果
result.second
為false
馏段,說(shuō)明原先的對(duì)象的該標(biāo)識(shí)符對(duì)應(yīng)的關(guān)聯(lián)對(duì)象有值,調(diào)用association.swap(result.first->second)
交換修改關(guān)聯(lián)對(duì)象(result.first->second
存放的是value
和policy
組裝成的ObjcAssociation
對(duì)象)
關(guān)聯(lián)空值流程
關(guān)聯(lián)空值其實(shí)就是刪除關(guān)聯(lián)對(duì)象践瓷,這部分代碼為:
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);
}
}
}
其實(shí)通過(guò)非空關(guān)鍵對(duì)象流程的探究對(duì)關(guān)聯(lián)對(duì)象原理了解院喜,這部分代碼就顯得簡(jiǎn)單很多。
- 以用當(dāng)前對(duì)象封裝的
DisguisedPtr
對(duì)象為key
在全局關(guān)聯(lián)對(duì)象表associations
中查找得到refs_it
- 如果
refs_it
不等于associations
的最后一個(gè)元素晕翠,通過(guò)refs_it->second
拿到當(dāng)前對(duì)象對(duì)象的ObjectAssociationMap
對(duì)象refs
喷舀,也就是存放標(biāo)識(shí)符和ObjcAssociation
對(duì)象it
(value和policy)的那張哈希表 - 通過(guò)標(biāo)識(shí)符找到
ObjcAssociation
- 如果
it
不是refs
最后一個(gè)元素,交換原有標(biāo)識(shí)符對(duì)應(yīng)的關(guān)聯(lián)對(duì)象淋肾,因?yàn)閭魅氲臑榭赵越粨Q后標(biāo)識(shí)符對(duì)應(yīng)的對(duì)象為空 - 調(diào)用
refs.erase(it)
,刪除該標(biāo)識(shí)符對(duì)應(yīng)的bucket
- 如果此時(shí)對(duì)象對(duì)應(yīng)的
ObjectAssociationMap
大小為0巫员,則刪除該對(duì)象對(duì)應(yīng)的關(guān)聯(lián)表
objc_getAssociatedObject
objc_getAssociatedObject
底層調(diào)用的是_object_get_associative_reference
id
objc_getAssociatedObject(id object, const void *key)
{
return _object_get_associative_reference(object, key);
}
_object_get_associative_reference
進(jìn)入_object_get_associative_reference
id
_object_get_associative_reference(id object, const void *key)
{
ObjcAssociation association{};
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.get());
AssociationsHashMap::iterator i = associations.find((objc_object *)object);
if (i != associations.end()) {
ObjectAssociationMap &refs = i->second;
ObjectAssociationMap::iterator j = refs.find(key);
if (j != refs.end()) {
association = j->second;
association.retainReturnedValue();
}
}
}
return association.autoreleaseReturnedValue();
}
這個(gè)部分代碼比較簡(jiǎn)單,大致可以分為以下幾個(gè)步驟:
- 創(chuàng)建
AssociationsManager
對(duì)象甲棍,通過(guò)AssociationsManager
對(duì)象拿到全局唯一的關(guān)聯(lián)對(duì)象管理表 - 以對(duì)象為
key
在關(guān)聯(lián)對(duì)象表中查找對(duì)象的AssociationsHashMap::iterator
- 如果找到了简识,取到
iterator
的第二個(gè)元素,也就是ObjectAssociationMap
感猛,用標(biāo)識(shí)符為key在這個(gè)ObjectAssociationMap
查找 - 如果找到了七扰,取找到的
refs
的第二個(gè)元素,也就是set時(shí)存放的value
(第一個(gè)為policy
)陪白,返回 - 以上如果都沒(méi)有找到颈走,返回nil
總結(jié)
關(guān)聯(lián)對(duì)象其實(shí)使用了兩張hashMap,可以用一張圖解釋他的原理咱士。