上周寫了篇關(guān)于iOS 分類的文章,我們可以在分類中定義屬性,但只是定義了屬性,編譯器既沒有生成相應(yīng)的setter和getter方法,也沒有生成對(duì)應(yīng)的成員屬性。如果想給對(duì)象動(dòng)態(tài)添加屬性就需要用到runtime庫中的API了
我們給一個(gè)類動(dòng)態(tài)添加屬性的做法如下:
@interface Person (Test1)
@property (nonatomic, strong) NSString *name;
@end
@implementation Person (Test1)
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self, @selector(name), name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
return objc_getAssociatedObject(self, @selector(name));
}
@end
嘗試給Person對(duì)象設(shè)置name屬性,再打印Person對(duì)象的name屬性看看是不是我們剛剛設(shè)置的值

打印結(jié)果表明,我們給Person實(shí)例對(duì)象關(guān)聯(lián)的name屬性真的設(shè)置了,并且不同對(duì)象之間關(guān)聯(lián)的屬性也互不影響。
我們都很肯定關(guān)聯(lián)的屬性值不是存放在實(shí)例對(duì)象結(jié)構(gòu)中的,因?yàn)閷?shí)例對(duì)象的結(jié)構(gòu)在編譯時(shí)就確定的,而屬性關(guān)聯(lián)卻是在運(yùn)行時(shí)動(dòng)態(tài)關(guān)聯(lián)的。objc_setAssociatedObject(...)和objc_getAssociatedObject(...)這兩個(gè)方法都需要2個(gè)參數(shù),分別是需要關(guān)聯(lián)屬性的對(duì)象self和屬性對(duì)應(yīng)的key。兩個(gè)方法都需要傳key過去,而且這兩個(gè)方法的key要一樣,因此猜測(cè)底層存儲(chǔ)的方式為map(runtime庫是C/C++)
課前小菜
在閱讀源碼之前,我們首先要理解幾個(gè)關(guān)鍵的C++類
- 1. AssociationsManager
spinlock_t AssociationsManagerLock;
class AssociationsManager {
// associative references: object pointer -> PtrPtrHashMap.
static AssociationsHashMap *_map;
public:
AssociationsManager() { AssociationsManagerLock.lock(); }
~AssociationsManager() { AssociationsManagerLock.unlock(); }
AssociationsHashMap &associations() {
if (_map == NULL)
_map = new AssociationsHashMap();
return *_map;
}
};
AssociationsHashMap *AssociationsManager::_map = NULL;
我將AssociationsManager稱之為管理對(duì)象,其內(nèi)部有一個(gè)AssociationsHashMap類型的靜態(tài)指針變量_map,這個(gè)變量在程序運(yùn)行期間只有一份。而從AssociationsManager的構(gòu)造函數(shù)和析構(gòu)函數(shù)中猜測(cè)該類存在的意義是維持一個(gè)原子鎖,確保在程序運(yùn)行期間,內(nèi)存中只存在一個(gè)AssociationsManager類型的對(duì)象。
- 2. AssociationsHashMap
typedef ObjcAllocator<std::pair<const disguised_ptr_t, ObjectAssociationMap*> > AssociationsHashMapAllocator;
class AssociationsHashMap : public unordered_map<disguised_ptr_t, ObjectAssociationMap *, DisguisedPointerHash, DisguisedPointerEqual, AssociationsHashMapAllocator> {
public:
void *operator new(size_t n) { return ::malloc(n); }
void operator delete(void *ptr) { ::free(ptr); }
};
在objc-references.mm文件中對(duì)AssociationsHashMap類的定義分成了win32架構(gòu)和其他架構(gòu),由于iOS目前僅可發(fā)布arm64架構(gòu)的,因此這里只截取了64架構(gòu)的源碼,有興趣了解win32位的實(shí)現(xiàn)的同學(xué)可自行查閱相關(guān)源碼。
AssociationsHashMap類繼承自unordered_map,unordered_map和map都是存儲(chǔ)鍵值對(duì)的容器,但map底層是紅黑樹存儲(chǔ)方式,存儲(chǔ)順序?yàn)閗ey有序序列。而unordered_map則是無序,哈希表存儲(chǔ)方式。

AssociationsHashMap中的key類型為disguised_ptr_t,value類型為ObjectAssociationMap類型的指針,key的哈希算法為DisguisedPointerHash,key判斷函數(shù)為DisguisedPointerEqual,鍵值對(duì)內(nèi)存分配器為AssociationsHashMapAllocator
3. ObjectAssociationMap
typedef ObjcAllocator<std::pair<void * const, ObjcAssociation> > ObjectAssociationMapAllocator;
class ObjectAssociationMap : public std::map<void *, ObjcAssociation, ObjectPointerLess, ObjectAssociationMapAllocator> {
public:
void *operator new(size_t n) { return ::malloc(n); }
void operator delete(void *ptr) { ::free(ptr); }
};

ObjectAssociationMap繼承自map,和AssociationsHashMap一樣都是鍵值對(duì)存儲(chǔ)。key的類型對(duì)指針,value為ObjcAssociation,key的判斷函數(shù)為ObjectPointerLess,鍵值對(duì)內(nèi)存分配器為ObjectAssociationMapAllocator。
4. ObjectAssociationMap
class ObjcAssociation {
uintptr_t _policy;
id _value;
public:
ObjcAssociation(uintptr_t policy, id value) : _policy(policy), _value(value) {}
ObjcAssociation() : _policy(0), _value(nil) {}
uintptr_t policy() const { return _policy; }
id value() const { return _value; }
bool hasValue() { return _value != nil; }
};
ObjcAssociation類中只有2個(gè)成員屬性,_policy和_value。通過名字我們可以猜到這個(gè)類主要存儲(chǔ)的是關(guān)聯(lián)的值和關(guān)聯(lián)策略。
正菜
objc_setAssociatedObject
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
ObjcAssociation old_association(0, nil);
id new_value = value ? acquireValue(value, policy) : nil;
{
AssociationsManager manager; // 獲取
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
if (new_value) {
// break any existing association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// secondary table exists
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
j->second = ObjcAssociation(policy, new_value);
} else {
(*refs)[key] = ObjcAssociation(policy, new_value);
}
} else {
// create the new association (first time).
ObjectAssociationMap *refs = new ObjectAssociationMap;
associations[disguised_object] = refs;
(*refs)[key] = ObjcAssociation(policy, new_value);
object->setHasAssociatedObjects();
}
} else {
// setting the association to nil breaks the association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
refs->erase(j);
}
}
}
}
// release the old value (outside of the lock).
if (old_association.hasValue()) ReleaseValue()(old_association);
}
- 首先根據(jù)關(guān)聯(lián)策略決定對(duì)關(guān)聯(lián)值還是引用計(jì)數(shù)器+1,亦或是拷貝關(guān)聯(lián)值
- 初始化AssociationsManager對(duì)象,若此時(shí)正有另一個(gè)對(duì)象在做關(guān)聯(lián)操作,則當(dāng)前AssociationsManager對(duì)象的初始化方法會(huì)被阻塞。在同一時(shí)間中,有且僅有一個(gè)AssociationsManager實(shí)例對(duì)象存在在內(nèi)存中,即同一時(shí)間只有一個(gè)對(duì)象在進(jìn)行關(guān)聯(lián)操作
- 獲取AssociationsManager中的靜態(tài)變量AssociationsHashMap
- 調(diào)用DISGUISE()函數(shù)獲取對(duì)象地址的反碼disguised_object
- 將對(duì)象地址的反碼作為key,在AssociationsHashMap哈希表中查找key為disguised_object的ObjectAssociationMap類型的對(duì)象
- 如果找到ObjectAssociationMap,則繼續(xù)根據(jù)我們傳入的關(guān)聯(lián)key繼續(xù)在ObjectAssociationMap表中查找對(duì)應(yīng)的ObjcAssociation對(duì)象,并替換成新的ObjcAssociation對(duì)象
- 如果在第6步中沒找到ObjcAssociation,則新建一個(gè)ObjcAssociation對(duì)象,并和關(guān)聯(lián)key綁定
- 如果在第5步中沒有找到ObjectAssociationMap ,則創(chuàng)建一個(gè)ObjectAssociationMap,把對(duì)應(yīng)的ObjcAssociation和key存儲(chǔ)起來
- 如果我們?cè)O(shè)置的value為nil,對(duì)應(yīng)的操作是從對(duì)象的關(guān)聯(lián)哈希表ObjectAssociationMap中刪除key對(duì)應(yīng)的ObjcAssociation對(duì)象
objc_getAssociatedObject
id objc_getAssociatedObject(id object, const void *key) {
return _object_get_associative_reference(object, (void *)key);
}
id _object_get_associative_reference(id object, void *key) {
id value = nil;
uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
ObjcAssociation &entry = j->second;
value = entry.value();
policy = entry.policy();
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
objc_retain(value);
}
}
}
}
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
objc_autorelease(value);
}
return value;
}
- 初始化AssociationsManager對(duì)象,若此時(shí)正有另一個(gè)對(duì)象在做關(guān)聯(lián)操作,則同樣**AssociationsManager****對(duì)象的初始化方法會(huì)被阻塞
- 獲取AssociationsManager中的靜態(tài)變量AssociationsHashMap
- 調(diào)用DISGUISE函數(shù)獲取對(duì)象地址的反碼disguised_object
- 將對(duì)象地址的反碼作為key,在AssociationsHashMap哈希表中查找key為disguised_object的ObjectAssociationMap類型的對(duì)象
- 如果在第4步中找到ObjectAssociationMap,則繼續(xù)根據(jù)我們傳入的關(guān)聯(lián)key繼續(xù)在ObjectAssociationMap表中查找對(duì)應(yīng)的ObjcAssociation對(duì)象,如果找到了,取出其中的value
- 如果在第5步中沒找到ObjcAssociation,則該key不存在關(guān)聯(lián)的值
- 如果在第4步中沒有找到ObjectAssociationMap ,則該對(duì)象沒有設(shè)置過關(guān)聯(lián)屬性
- 根據(jù)關(guān)聯(lián)策略決定是否對(duì)查詢到的value進(jìn)行retain操作或則autorelease
沙拉
文筆不是很好,??的這段話應(yīng)該很難理解了,沒關(guān)系,把關(guān)系圖畫出來就容易理解了。

AssociationsManager可重復(fù)創(chuàng)建,但是AssociationsHashMap是AssociationsManager內(nèi)的靜態(tài)變量,全局只有一個(gè),負(fù)責(zé)管理所有對(duì)象的關(guān)聯(lián)表。通過對(duì)象地址的反碼取出該對(duì)象的屬性關(guān)聯(lián)表ObjectAssociationMap。然后再通過關(guān)聯(lián)key從對(duì)象關(guān)聯(lián)表中獲取對(duì)應(yīng)的ObjectAssociation,ObjectAssociation內(nèi)存儲(chǔ)的就是真正需要的存儲(chǔ)的value值和內(nèi)存關(guān)聯(lián)策略policy了。
如果想刪除對(duì)象關(guān)聯(lián)的某一個(gè)value值,可傳nil空值即可刪除。
// setting the association to nil breaks the association.
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
if (j != refs->end()) {
old_association = j->second;
refs->erase(j);
}
}
甜點(diǎn)
在對(duì)象釋放的時(shí)候,會(huì)判斷對(duì)象是否關(guān)聯(lián)過屬性,如果關(guān)聯(lián)過屬性,則會(huì)調(diào)用_object_remove_assocations刪除其在AssociationsHashMap中對(duì)應(yīng)的值ObjectAssociationMap和ObjectAssociationMap中的每個(gè)ObjectAssociation。
// - (void)dealloc; (NSObject.mm)
// void_objc_rootDealloc(id obj); (NSObject.mm)
// inline void objc_object::rootDealloc(); (objc-object.h)
// id object_dispose(id obj); (objc-runtime-new.mm)
// void *objc_destructInstance(id obj); (objc-runtime-new.mm)
// void _object_remove_assocations(id object) (objc-references.mm)
void _object_remove_assocations(id object) {
vector< ObjcAssociation,ObjcAllocator<ObjcAssociation> > elements;
{
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
if (associations.size() == 0) return;
disguised_ptr_t disguised_object = DISGUISE(object);
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// copy all of the associations that need to be removed.
ObjectAssociationMap *refs = i->second;
for (ObjectAssociationMap::iterator j = refs->begin(), end = refs->end(); j != end; ++j) {
elements.push_back(j->second);
}
// remove the secondary table.
delete refs;
associations.erase(i);
}
}
// the calls to releaseValue() happen outside of the lock.
for_each(elements.begin(), elements.end(), ReleaseValue());
}
在_object_set_associative_reference()函數(shù)中,當(dāng)為對(duì)象創(chuàng)建ObjectAssociationMap哈希表的時(shí)候,也會(huì)調(diào)用objc_object::setHasAssociatedObjects()將對(duì)象ISA指針的第二位has_assoc設(shè)置為1。這一步只要設(shè)置了之后,直到被釋放,第二位has_assoc都為1。
Tips:在iOS arm64位系統(tǒng)中,ISA指針是經(jīng)過優(yōu)化的,采用了共同體,可存儲(chǔ)更多的信息。而ISA指針的第2位0和1分別表示該對(duì)象是否關(guān)聯(lián)過對(duì)象
union isa_t
{
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits;
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19;
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
# elif __x86_64__
...
#endif
};
驗(yàn)證一下關(guān)聯(lián)屬性前后ISA指針第二位has-assoc的變化

在沒關(guān)聯(lián)之前,ISA指針的第二位has_assoc的為0,在關(guān)聯(lián)屬性之后,ISA指針的第二位has_assoc變?yōu)榱?。

這次寫的不是很好,理解起來會(huì)有點(diǎn)困難,本人也還在繼續(xù)提升寫作能力,望諒解