從源碼理解關聯屬性

博客連接 從源碼理解關聯屬性數組

在類中,咱們使用@property (nonatomic, copy) NSString *name生成一個屬性。它幹了三件事情:安全

  1. 聲明一個_name的變量;
  2. 聲明setName:getName方法;
  3. setter和getter方法的默認實現;

可是在分類中寫上述這樣一個屬性的,它只有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);
複製代碼

這三個方法的做用分別是:函數

  • 以鍵值對形式添加關聯對象
  • 根據key獲取關聯對象
  • 移除全部關聯對象

關聯對象的核心對象

在分析關聯對象的API實現以前,先看一下關聯對象的核心對象。ui

AssociationsManager

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用於取得一個全局的AssociationsHashMapspa

另外AssociationsManager經過一個自旋鎖spinlock_t AssociationsManagerLock來確保對AssociationsHashMap的操做是線程安全的。

AssociationsHashMap

HashMap至關於OC中的NSDictionaryAssociationsHashMap的定義以下:

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_tObjectAssociationMap的映射。 咱們能夠理解爲AssociationsHashMap以key-value的形式存着若干個ObjectAssociationMap

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保存了從keyObjcAssociation的映射,咱們能夠理解爲ObjectAssociationMap以key-value的形式存着若干個ObjcAssociation對象。這個數據結構保存了當前對象對應的全部關聯對象:

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方法時傳入的policyvalue

objc_setAssociatedObject

經過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_getAssociatedObject

在理解了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

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對象;
  • ObjectAssociationMapkey爲健存儲關聯對象的數據結構(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;
}
複製代碼
相關文章
相關標籤/搜索