iOS底層總結(jié)- 關(guān)聯(lián)對象實(shí)現(xiàn)原理

前言

Category能否添加成員變量?如果可以,如何給Category添加成員變量?
答:不能直接添加成員變量,但是可以通過runtime的方式間接實(shí)現(xiàn)添加成員變量的效果。

Category動態(tài)關(guān)聯(lián)對象

方法一:我們可以通過使用靜態(tài)全局變量給分類添加屬性


static NSString *_name;
-(void)setName:(NSString *)name
{
    _name = name;
}
-(NSString *)name
{
    return _name;
}

但是這樣_name靜態(tài)全局變量與類并沒有關(guān)聯(lián),無論對象創(chuàng)建與銷毀,只要程序在運(yùn)行_name變量就存在,并不是真正意義上的屬性。

方法二:使用runtime動態(tài)添加屬性


-(void)setName:(NSString *)name
{
    objc_setAssociatedObject(self, @"name",name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSString *)name
{
    return objc_getAssociatedObject(self, @"name");    
}

關(guān)聯(lián)對象的實(shí)現(xiàn)原理

實(shí)現(xiàn)關(guān)聯(lián)對象技術(shù)的核心對象有

AssociationsManager
AssociationsHashMap
ObjectAssociationMap
ObjcAssociation

其中Map同我們平時使用的字典類似。通過key-value一一對應(yīng)存值。

本文基于objc-723版本,在Apple GithubApple OpenSource上有源碼,來到runtime源碼,首先找到objc_setAssociatedObject函數(shù),看一下其實(shí)現(xiàn)

objc_setAssociatedObject函數(shù)

void 
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy) {
    objc_setAssociatedObject_non_gc(object, key, value, policy);
}

void objc_setAssociatedObject_non_gc(id object, const void *key, id value, objc_AssociationPolicy policy) {
    _object_set_associative_reference(object, (void *)key, value, policy);
}

我們看到其實(shí)內(nèi)部調(diào)用的是_object_set_associative_reference函數(shù),我們來到_object_set_associative_reference函數(shù)中

_object_set_associative_reference函數(shù)

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

_object_set_associative_reference函數(shù)內(nèi)部我們可以全部找到我們上面說過的實(shí)現(xiàn)關(guān)聯(lián)對象技術(shù)的核心對象。接下來我們來一個一個看其內(nèi)部實(shí)現(xiàn)原理探尋他們之間的關(guān)系。

AssociationsManager

class AssociationsManager {
    static spinlock_t _lock;
    static AssociationsHashMap *_map;               // associative references:  object pointer -> PtrPtrHashMap.
public:
    AssociationsManager()   { _lock.lock(); }
    ~AssociationsManager()  { _lock.unlock(); }
    
    AssociationsHashMap &associations() {
        if (_map == NULL)
            _map = new AssociationsHashMap();
        return *_map;
    }
};

通過AssociationsManager內(nèi)部源碼發(fā)現(xiàn),AssociationsManager內(nèi)部有一個AssociationsHashMap對象,還有個自旋鎖 spinlock_t。

AssociationsHashMap

image.png

通過AssociationsHashMap內(nèi)部源碼我們發(fā)現(xiàn)AssociationsHashMap繼承自unordered_map首先來看一下unordered_map內(nèi)的源碼

image.png

unordered_map內(nèi)部分源碼

從unordered_map源碼中我們可以看出_Key_Tp也就是前兩個參數(shù)對應(yīng)著map中的Key和Value,那么對照上面AssociationsHashMap內(nèi)源碼發(fā)現(xiàn)_Key中傳入的是disguised_ptr_t,_Tp中傳入的值則為ObjectAssociationMap。

緊接著我們來到ObjectAssociationMap中,上圖中ObjectAssociationMap已經(jīng)標(biāo)記出,我們發(fā)現(xiàn)ObjectAssociationMap中同樣以key、Value的方式存儲著ObjcAssociation。

接著我們來到ObjcAssociation中


image.png

ObjcAssociation

我們發(fā)現(xiàn)ObjcAssociation存儲著_policy、_value,而這兩個值我們可以發(fā)現(xiàn)正是我們調(diào)用objc_setAssociatedObject函數(shù)傳入的值,也就是說我們在調(diào)用objc_setAssociatedObject函數(shù)中傳入的value和policy這兩個值最終是存儲在ObjcAssociation中的。

現(xiàn)在我們已經(jīng)對AssociationsManager、 AssociationsHashMap、 ObjectAssociationMap、ObjcAssociation四個對象之間的關(guān)系有了簡單的認(rèn)識,那么接下來我們來細(xì)讀源碼,看一下objc_setAssociatedObject函數(shù)中傳入的四個參數(shù)分別放在哪個對象中充當(dāng)什么作用。

細(xì)讀上述源碼我們可以發(fā)現(xiàn),首先根據(jù)我們傳入的value經(jīng)過acquireValue函數(shù)處理獲取new_value。acquireValue函數(shù)內(nèi)部其實(shí)是通過對策略的判斷返回不同的值

static id acquireValue(id value, uintptr_t policy) {
    switch (policy & 0xFF) {
    case OBJC_ASSOCIATION_SETTER_RETAIN:
        return ((id(*)(id, SEL))objc_msgSend)(value, SEL_retain);
    case OBJC_ASSOCIATION_SETTER_COPY:
        return ((id(*)(id, SEL))objc_msgSend)(value, SEL_copy);
    }
    return value;
}

之后創(chuàng)建AssociationsManager manager;以及拿到manager內(nèi)部的AssociationsHashMap即associations。
之后我們看到了我們傳入的第一個參數(shù)object
object經(jīng)過 DISGUISE 函數(shù)被轉(zhuǎn)化為了disguised_ptr_t類型的disguised_object。

    typedef uintptr_t disguised_ptr_t;
    inline disguised_ptr_t DISGUISE(id value) { return ~uintptr_t(value); }
    inline id UNDISGUISE(disguised_ptr_t dptr) { return id(~dptr); }

DISGUISE函數(shù)
DISGUISE函數(shù)其實(shí)僅僅對object做了位運(yùn)算

  1. 我們看到被處理成new_value的value,同policy被存入了ObjcAssociation中。
  2. 而ObjcAssociation對應(yīng)我們傳入的key被存入了ObjectAssociationMap中。
  3. disguised_object和ObjectAssociationMap則以key-value的形式對應(yīng)存儲在associations中也就是AssociationsHashMap中。
image.png

value為nil

從上述代碼中可以看出,如果我們value設(shè)置為nil的話那么會執(zhí)行下面的代碼,就會將關(guān)聯(lián)對象從ObjectAssociationMap中移除。

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

最后我們通過一張圖可以很清晰的理清楚其中的關(guān)系


image.png

關(guān)聯(lián)對象底層對象關(guān)系

通過上圖我們可以總結(jié)為:

一個實(shí)例對象就對應(yīng)一個ObjectAssociationMap,而ObjectAssociationMap中存儲著多個此實(shí)例對象的關(guān)聯(lián)對象的key以及ObjcAssociation,為ObjcAssociation中存儲著關(guān)聯(lián)對象的value和policy策略。

由此我們可以知道關(guān)聯(lián)對象并不是放在了原來的對象里面,而是自己維護(hù)了一個全局的map用來存放每一個對象及其對應(yīng)關(guān)聯(lián)屬性表格。

objc_getAssociatedObject函數(shù)
objc_getAssociatedObject內(nèi)部調(diào)用的是_object_get_associative_reference

id  objc_getAssociatedObject(id object, const void *key) {
    return objc_getAssociatedObject_non_gc(object, key);
}

id objc_getAssociatedObject_non_gc(id object, const void *key) {
    return _object_get_associative_reference(object, (void *)key);
}

_object_get_associative_reference函數(shù)

從_object_get_associative_reference函數(shù)內(nèi)部可以看出,向set方法中那樣,反向?qū)alue一層一層取出最后return出去。

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);
         > 1.  //Hash表中查找disguised_object
        AssociationsHashMap::iterator i = associations.find(disguised_object);
        if (i != associations.end()) {
        > 2.  //找到的話根據(jù)key取出對應(yīng)的ObjcAssociation
            ObjectAssociationMap *refs = i->second;
            ObjectAssociationMap::iterator j = refs->find(key);
            if (j != refs->end()) {
                ObjcAssociation &entry = j->second;
                value = entry.value();
                policy = entry.policy();
               > 3.  //根據(jù)policy對取出的value做相應(yīng)的操作 
                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函數(shù)

objc_removeAssociatedObjects用來刪除所有的關(guān)聯(lián)對象,objc_removeAssociatedObjects函數(shù)內(nèi)部調(diào)用的是_object_remove_assocations函數(shù)

void objc_removeAssociatedObjects(id object) 
{
#if SUPPORT_GC
    if (UseGC) {
        auto_zone_erase_associative_refs(gc_zone, object);
    } else 
#endif
    {
        if (object && object->hasAssociatedObjects()) {
            _object_remove_assocations(object);
        }
    }
}

objc_removeAssociatedObjects函數(shù)

image.png

總結(jié):

關(guān)聯(lián)對象并不是存儲在被關(guān)聯(lián)對象本身內(nèi)存中,而是存儲在全局的統(tǒng)一的一個AssociationsManager中,如果設(shè)置關(guān)聯(lián)對象為nil,就相當(dāng)于是移除關(guān)聯(lián)對象。

我們現(xiàn)在來看objc_AssociationPolicy policy 參數(shù): 屬性以什么形式保存的策略。


typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
    OBJC_ASSOCIATION_ASSIGN = 0,  // 指定一個弱引用相關(guān)聯(lián)的對象
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, // 指定相關(guān)對象的強(qiáng)引用,非原子性
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,  // 指定相關(guān)的對象被復(fù)制,非原子性
    OBJC_ASSOCIATION_RETAIN = 01401,  // 指定相關(guān)對象的強(qiáng)引用,原子性
    OBJC_ASSOCIATION_COPY = 01403     // 指定相關(guān)的對象被復(fù)制,原子性   
};

我們會發(fā)現(xiàn)其中只有RETAIN和COPY而為什么沒有weak呢?

總過上面對源碼的分析我們知道,object經(jīng)過DISGUISE函數(shù)被轉(zhuǎn)化為了disguised_ptr_t類型的disguised_object。

disguised_ptr_t disguised_object = DISGUISE(object);

weak修飾的屬性,當(dāng)沒有擁有對象之后就會被銷毀,并且指針置位nil,那么在對象銷毀之后,雖然在map中既然存在值object對應(yīng)的AssociationsHashMap,但是因?yàn)閛bject地址已經(jīng)被置位nil,會造成壞地址訪問而無法根據(jù)object對象的地址轉(zhuǎn)化為disguised_object了。

使用 OBJC_ASSOCIATION_ASSIGN 策略其實(shí)等于 assign/unsafe_unretained,本質(zhì)上是保存了對象的地址而不是真正的弱引用,在一些情定的情況下 屬性對象釋放時再調(diào)用方法會出現(xiàn)野指針異常

更安全的 association weak 屬性

DZNEmptyDataSet 中可以學(xué)習(xí)使用 strong + WeakContainer 的方式,實(shí)現(xiàn)對屬性對象的 weak 引用。
思路是:

  • 聲明一個 WeakContainer 類對真實(shí)的屬性對象進(jìn)行 weak 屬性引用
  • 通過 OBJC_ASSOCIATION_RETAIN_NONATOMIC 策略對 WeakContainer 進(jìn)行 retain association
  • 這樣在 get 關(guān)聯(lián)屬性對象時由于 WeakContainer 對真是屬性對象的 weak 引用,會返回 nil 而不是野指針

實(shí)現(xiàn) WeakContainer 如下:

@interface XWeakObjectContainer : NSObject

@property (nonatomic, readonly, weak) id weakObject;

- (instancetype)initWithWeakObject:(id)object;

@end

@implementation XWeakObjectContainer

- (instancetype)initWithWeakObject:(id)object {
    self = [super init];
    if (self) {
        _weakObject = object;
    }
    
    return self;
}

@end

從而變相地實(shí)現(xiàn) weak association 如下:

@implementation NSObject (XAssociate)

- (void)setXProperty:(id)xProperty {
    XWeakObjectContainer *container = [[XWeakObjectContainer alloc] initWithWeakObject:xProperty];
    objc_setAssociatedObject(self, @selector(xProperty), container, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (id)xProperty {
    XWeakObjectContainer *container = objc_getAssociatedObject(self, _cmd);
    return container.weakObject;
}

@end

雖然 retain 了一個 WeakContainer,但是 WeakContainer 最終會隨著屬性的持有對象一起銷毀,不存在泄露。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容