iOS探索 runtime面試題分析(六、關聯對象分析)

六、關聯對象分析

實則是為了解決分類創建屬性的問題

1.分類直接添加屬性的后果

  • 編譯會出現警告:沒有setter方法和getter方法
  • 運行會報錯:-[FXPerson setName:]: unrecognized selector sent to instance 0x100f61180'

2.為什么不能直接添加屬性

Categoryruntime中是用一個結構體表示的:

struct category_t {
    const char *name;
    classref_t cls;
    struct method_list_t *instanceMethods;
    struct method_list_t *classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;
    ...
};

里面雖然可以添加屬性變量,但是這些properties并不會自動生成Ivar,也就是不會有 @synthesize的作用,dyld加載期間,這些分類會被加載并patch到相應的類中。這是一個動態過程,Ivar不能動態添加

3.解決方案

手動實現setter、getter方法,關聯對象

- (void)setName:(NSString *)name {
    /**
    參數一:id object : 給哪個對象添加屬性,這里要給自己添加屬性,用self。
    參數二:void * == id key : 屬性名,根據key獲取關聯對象的屬性的值,在objc_getAssociatedObject中通過次key獲得屬性的值并返回。
    參數三:id value : 關聯的值,也就是set方法傳入的值給屬性去保存。
    參數四:objc_AssociationPolicy policy : 策略,屬性以什么形式保存。
    */
    objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSString *)name {
    /**
    參數一:id object : 獲取哪個對象里面的關聯的屬性。
    參數二:void * == id key : 什么屬性,與objc_setAssociatedObject中的key相對應,即通過key值取出value。
    */
    return objc_getAssociatedObject(self, @"name");
}

4.關聯對象原理

  1. setter方法——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) {
    // This code used to work when nil was passed for object and key. Some code
    // probably relies on that to not crash. Check and handle it explicitly.
    // rdar://problem/44094390
    if (!object && !value) return;

    assert(object);

    if (object->getIsa()->forbidsAssociatedObjects())
        _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));

    // retain the new value (if any) outside the lock.
    // 在鎖之外保留新值(如果有)。
    ObjcAssociation old_association(0, nil);
    // acquireValue會對retain和copy進行操作,
    id new_value = value ? acquireValue(value, policy) : nil;
    {
        // 關聯對象的管理類
        AssociationsManager manager;
        // 獲取關聯的 HashMap -> 存儲當前關聯對象
        AssociationsHashMap &associations(manager.associations());
        // 對當前的對象的地址做按位去反操作 - 就是 HashMap 的key (哈希函數)
        disguised_ptr_t disguised_object = DISGUISE(object);
        if (new_value) {
            // break any existing association.
            // 獲取 AssociationsHashMap 的迭代器 - (對象的) 進行遍歷
            AssociationsHashMap::iterator i = associations.find(disguised_object);
            if (i != associations.end()) {
                // secondary table exists
                ObjectAssociationMap *refs = i->second;
                // 根據key去獲取關聯屬性的迭代器
                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).
                // 如果AssociationsHashMap從沒有對象的關聯信息表,
                // 那么就創建一個map并通過傳入的key把value存進去
                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.
            // 如果傳入的value是nil,并且之前使用相同的key存儲過關聯對象,
            // 那么就把這個關聯的value移除(這也是為什么傳入nil對象能夠把對象的關聯value移除)
            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).
    // 最后把之前使用傳入的這個key存儲的關聯的value釋放(OBJC_ASSOCIATION_SETTER_RETAIN策略存儲的)
    if (old_association.hasValue()) ReleaseValue()(old_association);
}

  • ObjcAssociation old_association(0, nil)處理傳進來的值得到new_value
  • 獲取到管理所有關聯對象的hashmap總表的管理者AssociationsManager,然后拿到hashmap總表AssociationsHashMap
  • DISGUISE(object)對關聯對象的地址進行取反操作得到哈希表對應的下標index
  • 如果new_value為空(即對屬性賦值為nil)就直接找到相應的表進行刪除
  • 如果new_value不為空,就拿到總表的迭代器通過拿到的下標index進行遍歷查找;如果找到管理對象的關聯屬性哈希map表,然后再通過key去遍歷取值
    • 如果取到了,就先把新值設置到key上,再將舊值釋放掉
    • 如果沒取到,就直接將新值設置在key上

還是不明白就LLDB斷點調試唄

[圖片上傳中...(image-339b2c-1587710318659-18)]

<figcaption></figcaption>

  1. getter方法——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());
        // 生成偽裝地址。處理參數 object 地址
        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();
                // OBJC_ASSOCIATION_GETTER_RETAIN - 就會持有一下
                if (policy & OBJC_ASSOCIATION_GETTER_RETAIN) {
                    objc_retain(value);
                }
            }
        }
    }
    if (value && (policy & OBJC_ASSOCIATION_GETTER_AUTORELEASE)) {
        objc_autorelease(value);
    }
    return value;
}

objc_getAssociatedObjectobjc_setAssociatedObject的逆過程

2020面試刷題與技術儲備專區

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容