博客連接 從源碼理解關聯屬性數組
在類中,咱們使用@property (nonatomic, copy) NSString *name
生成一個屬性。它幹了三件事情:安全
_name
的變量;setName:
和getName
方法;可是在分類中寫上述這樣一個屬性的,它只有setter和getter方法的聲明,並不會生成成員變量和實現setter和getter方法,所以若是想要在分類中實現屬性的話得使用關聯對象的方式。bash
首先咱們要明白爲何要使用關聯對象? 在分類中@property
並不會自動生成實例變量以及存取方法,另外在分類是不能聲明成員變量的。從源碼的角度去看,Category在編譯時期生成的結構體中根本沒有存放成員變量的數組。基於上面的緣由,若是咱們要實現類中屬性那樣的效果,就要使用關聯對象。數據結構
關聯對象的應用以下:app
// .h
@interface FatherA : NSObject
@property (nonatomic, copy) NSString *name;
@end
// .m
@implementation FatherA
- (void)setName:(NSString *)name {
objc_setAssociatedObject(self,
@selector(name),
name,
OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (NSString *)name {
return objc_getAssociatedObject(self, _cmd);
}
@end
複製代碼
這裏使用objc4-750.1
的源代碼,你能夠經過這裏下載。咱們經常使用的關於關聯對象的API主要有如下幾個:ide
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy);
id objc_getAssociatedObject(id object, const void *key);
void objc_removeAssociatedObjects(id object);
複製代碼
這三個方法的做用分別是:函數
在分析關聯對象的API實現以前,先看一下關聯對象的核心對象。ui
AssociationsManager
的定義以下:atom
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
會維護一個AssociationsHashMap
,在初始化的時候,調用AssociationsManagerLock.lock()
,在析構時會調用AssociationsManagerLock.unlock()
,而associations
用於取得一個全局的AssociationsHashMap
。spa
另外AssociationsManager
經過一個自旋鎖spinlock_t AssociationsManagerLock
來確保對AssociationsHashMap
的操做是線程安全的。
HashMap
至關於OC中的NSDictionary
。AssociationsHashMap
的定義以下:
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); }
};
複製代碼
AssociationsHashMap
繼承自unordered_map
,使用的是C++語法。它的做用就是保存從對象的disguised_ptr_t
到ObjectAssociationMap
的映射。 咱們能夠理解爲AssociationsHashMap以key-value的形式存着若干個ObjectAssociationMap。
ObjectAssociationMap
的定義以下:
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
保存了從key
到ObjcAssociation
的映射,咱們能夠理解爲ObjectAssociationMap以key-value的形式存着若干個ObjcAssociation對象。這個數據結構保存了當前對象對應的全部關聯對象:
ObjcAssociation
的定義以下:
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
對象保存了對象對應的關聯對象,其中的_policy
和_value
字段存的即是咱們使用objc_setAssociatedObject
方法時傳入的policy
和value
。
經過objc_setAssociatedObject
函數,咱們添加一個關聯對象,其實現以下:
void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
_object_set_associative_reference(object, (void *)key, value, policy);
}
複製代碼
接着看一下_object_set_associative_reference
的實現:
void _object_set_associative_reference(id object, void *key, id value, uintptr_t policy) {
// retain the new value (if any) outside the lock.
// 建立一個ObjcAssociation局部變量,持有原有的關聯對象和最後的釋放
ObjcAssociation old_association(0, nil);
// 調用acquireValue對new_value進行retain或者copy
id new_value = value ? acquireValue(value, policy) : nil;
{
// 初始化一個AssociationsManager,並獲取AssociationsHashMap
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
// disguised_ptr_t是AssociationsHashMap中的key,經過傳入的object獲得
disguised_ptr_t disguised_object = DISGUISE(object);
// new_value有值表明設置或者更新關聯對象的值,不然表示刪除一個關聯對象
if (new_value) {
// break any existing association.
// 查找ObjectAssociationMap
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
// secondary table exists
// ObjectAssociationMap存在
// 判斷key是否存在,key存在更新原有的關聯對象,key不存在,則新增,而且新增的位置須要結合end()
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不存在
// 初始化一個ObjectAssociationMap,再實例化ObjcAssociation對象添加到Map中,並調用 setHasAssociatedObjects函數
// setHasAssociatedObjects標明當前類具備關聯類
// 它會將isa結構體中的has_assoc標記爲true
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.
// 查找ObjectAssociationMap
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
// ObjectAssociationMap存在
// 判斷key是否存在,key存在則調用erase函數來刪除ObjectAssociationMap中key對應的節點
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).
// 原來的關聯對象有值,調用ReleaseValue函數釋放關聯對象的值
if (old_association.hasValue()) ReleaseValue()(old_association);
}
複製代碼
經過上面的源碼以及註釋能夠知道objc_setAssociatedObject
的流程,接着咱們用一張圖片來講明關聯對象的原理:
在理解了objc_setAssociatedObject
的實現以後,objc_getAssociatedObject
就變得容易理解了,其實現以下:
id objc_getAssociatedObject(id object, const void *key) {
return _object_get_associative_reference(object, (void *)key);
}
複製代碼
接着看一下_object_get_associative_reference
的實現:
id _object_get_associative_reference(id object, void *key) {
id value = nil;
uintptr_t policy = OBJC_ASSOCIATION_ASSIGN;
{
// 初始化一個AssociationsManager,並獲取AssociationsHashMap
AssociationsManager manager;
AssociationsHashMap &associations(manager.associations());
// 經過object得到disguised_ptr_t,用做在AssociationsHashMap的key
disguised_ptr_t disguised_object = DISGUISE(object);
// 查找ObjectAssociationMap的位置
AssociationsHashMap::iterator i = associations.find(disguised_object);
if (i != associations.end()) {
ObjectAssociationMap *refs = i->second;
ObjectAssociationMap::iterator j = refs->find(key);
// 查找ObjcAssociation對象
if (j != refs->end()) {
ObjcAssociation &entry = j->second;
value = entry.value();
policy = entry.policy();
// 說明是強類型,retain操做
if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
objc_retain(value);
}
}
}
}
if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
// autorelease
objc_autorelease(value);
}
return value;
}
複製代碼
objc_removeAssociatedObjects
用來刪除全部的關聯對象,其實現:
void objc_removeAssociatedObjects(id object)
{
if (object && object->hasAssociatedObjects()) {
_object_remove_assocations(object);
}
}
複製代碼
經過hasAssociatedObjects
函數判斷是否含有關聯對象,若是有則調用_object_remove_assocations
。
在objc_setAssociatedObject
的實現中有提到在添加關聯對象的時候若是ObjectAssociationMap
不存在,則會初始化一個ObjectAssociationMap
,再實例化ObjcAssociation
對象添加到Map中,並調用 setHasAssociatedObjects
函數。setHasAssociatedObjects
函數用來將isa結構體中的has_assoc
標記爲true
,而hasAssociatedObjects
函數則用來獲取該該標誌位的結果。
接着看一下_object_remove_assocations
,其實現以下:
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_remove_assocations
會將對象包含的全部關聯對象加入到一個vector
中,刪除AssociationsHashMap
中對應的節點,而後對全部的 ObjcAssociation
對象調用 ReleaseValue()
,釋放再也不被須要的值。
關聯對象的實現
ObjcAssociation
對象;ObjectAssociationMap
以key
爲健存儲關聯對象的數據結構(ObjcAssociation
對象);ObjectAssociationMap
,對象中的has_assoc
用來肯定是否含有關聯對象,而對象與ObjectAssociationMap
之間的映射關係則存儲在AssociationsHashMap
中;AssociationsHashMap
是全局惟一的,有AssociationsManager
管理。分類中可否實現屬性
若是將屬性當作是實例變量,那答案是不能,若是將屬性當作是存取方法以及存儲值的集合,那麼分類是能夠實現屬性的,我的更傾向於前者。
weak類型的關聯對象
關聯對象裏是沒有weak類型的策略,而在開發過程當中,真的幾乎沒有說要用弱類型的關聯對象,除非是爲了用而用。我是這麼理解的,既然叫作關聯對象,那確定須要和自身生命週期有聯繫才談得上關聯,使用weak則表明對象和自身生命週期是沒有聯繫,自身的釋放不會影響關聯對象。綜上我認爲weak類型的關聯對象是沒有意義的。
可是若是非要實現一個weak類型的關聯對象也不是不能夠,拿個中間對象包裝一下便可。代碼以下:
#pragma mark - Weak Associated Object
@interface _NNWeakAssociatedWrapper : NSObject
@property (nonatomic, weak) id associatedObject;
@end
@implementation _NNWeakAssociatedWrapper
@end
void nn_objc_setWeakAssociatedObject(id object, const void * key, id value) {
_NNWeakAssociatedWrapper *wrapper = objc_getAssociatedObject(object, key);
if (!wrapper) {
wrapper = [_NNWeakAssociatedWrapper new];
objc_setAssociatedObject(object, key, wrapper, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
wrapper.associatedObject = value;
}
id nn_objc_getWeakAssociatedObject(id object, const void * key) {
id wrapper = objc_getAssociatedObject(object, key);
id objc = wrapper && [wrapper isKindOfClass:_NNWeakAssociatedWrapper.class] ?
[(_NNWeakAssociatedWrapper *)wrapper associatedObject] :
nil;
return objc;
}
複製代碼