Objective-C 源碼(五) Associated Objects 的實現原理

來源:互聯網
上載者:User

標籤:

    原文連結:http://blog.leichunfeng.com/blog/2015/06/26/objective-c-associated-objects-implementation-principle/  


    我們知道,在 Objective-C 中可以通過 Category 給一個現有的類添加屬性,但是卻不能添加執行個體變數,這似乎成為了 Objective-C 的一個明顯短板。然而值得慶幸的是,我們可以通過 Associated Objects 來彌補這一不足。

   

    在閱讀本文的過程中,讀者需要著重關注以下三個問題:

  1. 關聯對象被儲存在什麼地方,是不是存放在被關聯對象本身的記憶體中?

  2. 關聯對象的五種關聯策略有什麼區別,有什麼坑?

  3. 關聯對象的生命週期是怎樣的,什麼時候被釋放,什麼時候被移除?

    

    與 Associated Objects 相關的函數主要有三個,我們可以在 runtime 源碼的 runtime.h 檔案中找到它們的聲明:

OBJC_EXPORT void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
OBJC_EXPORT id objc_getAssociatedObject(id object, const void *key)
OBJC_EXPORT void objc_removeAssociatedObjects(id object)

    這三個函數的命名很容易看懂:

  • objc_setAssociatedObject 用於給對象添加關聯對象,傳入 nil 則可以移除已有的關聯對象;

  • objc_getAssociatedObject 用於擷取關聯對象;

  • objc_removeAssociatedObjects 用於移除一個對象的所有關聯對象。

    注:objc_removeAssociatedObjects 函數我們一般是用不上的,因為這個函數會移除一個對象的所有關聯對象,將該對象恢複成“原始”狀態。這樣做就很有可能把別人添加的關聯對象也一併移除,這並不是我們所希望的。所以一般的做法是通過給 objc_setAssociatedObject 函數傳入 nil 來移除某個已有的關聯對象。

    

    Key值:

    關於前兩個函數中的 key 值是我們需要重點關注的一個點,這個 key 值必須保證是一個對象層級(為什麼是對象層級?看完下面的章節你就會明白了)的唯一常量。一般來說,有以下三種推薦的 key 值:

  1. 聲明 static char kAssociatedObjectKey; ,使用 &kAssociatedObjectKey 作為 key 值;

  2. 聲明 static void *kAssociatedObjectKey = &kAssociatedObjectKey; ,使用 kAssociatedObjectKey 作為 key 值;

  3. 用 selector ,使用 getter 方法的名稱作為 key 值。

     推薦使用第三種,好用,省代碼量。

    

    關聯策略

    關聯策略                                                        等價屬性                                    說明OBJC_ASSOCIATION_ASSIGN                       @property (assign) or                 弱引用關聯對象

                                                                  @property (unsafe_unretained)

OBJC_ASSOCIATION_RETAIN_NONATOMIC    @property (strong, nonatomic)    強引用關聯對象,且為非原子操作

OBJC_ASSOCIATION_COPY_NONATOMIC       @property (copy, nonatomic)      複製關聯對象,且為非原子操作

OBJC_ASSOCIATION_RETAIN                        @property (strong, atomic)         強引用關聯對象,且為原子操作

OBJC_ASSOCIATION_COPY                           @property (copy, atomic)           複製關聯對象,且為原子操作

    原子性問題不在這裡討論,下面主要討論前三種形式:

    

    實現原理

    代碼Github原文連結:https://github.com/leichunfeng/AssociatedObjects 

    從測試代碼中可以看出:

  1. 關聯對象的釋放時機與被移除的時機並不總是一致的,比如上面的 self.associatedObject_assign 所指向的對象在 ViewController 出現後就被釋放了,但是 self.associatedObject_assign 仍然有值,還是儲存的原對象的地址。如果之後再使用 self.associatedObject_assign 就會造成 Crash ,所以我們在使用弱引用的關聯對象時要非常小心;

  2. 一個對象的所有關聯對象是在這個對象被釋放時調用的 _object_remove_assocations 函數中被移除的。

    開啟 objc-references.mm,找到 objc_setAssociatedObject 函數最終調用的函數;

    

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);}


    

    在 objc-references.mm 的 objc_getAssociatedObject 函數最終調用了的函數:

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) ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);            }        }    }    if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {        ((id(*)(id, SEL))objc_msgSend)(value, SEL_autorelease);    }    return value;}

   

    objc_removeAssociatedObjects

 這個函數負責移除一個對象的所有關聯對象,具體實現也是先根據對象的地址擷取其對應的 ObjectAssociationMap 對象,然後將所有的關連接構儲存到一個 vector 中,最終釋放 vector 中儲存的所有關聯對象。根據前面的實驗觀察到的情況,在一個對象被釋放時,也正是調用的這個函數來移除其所有的關聯對象。


    給類對象添加關聯對象

    看完原始碼後,我們知道對象地址與 AssociationsHashMap 雜湊表是一一對應的。那麼我們可能就會思考這樣一個問題,是否可以給類對象添加關聯對象呢?答案是肯定的。我們完全可以用同樣的方式給類對象添加關聯對象,只不過我們一般情況下不會這樣做,因為更多時候我們可以通過 static 變數來實作類別層級的變數。我在分類 ViewController+AssociatedObjects 中給 ViewController 類對象添加了一個關聯對象 associatedObject 。

    總結

  1. 關聯對象與被關聯對象本身的儲存並沒有直接的關係,它是儲存在單獨的雜湊表中的;

  2. 關聯對象的五種關聯策略與屬性的限定符非常類似,在絕大多數情況下,我們都會使用 OBJC_ASSOCIATION_RETAIN_NONATOMIC 的關聯策略,這可以保證我們持有關聯對象;

  3. 關聯對象的釋放時機與移除時機並不總是一致,比如實驗中用關聯策略 OBJC_ASSOCIATION_ASSIGN 進行關聯的對象,很早就已經被釋放了,但是並沒有被移除,而再使用這個關聯對象時就會造成 Crash 。




Objective-C 源碼(五) Associated Objects 的實現原理

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.