應(yīng)用程序加載(五)-- 類擴(kuò)展和關(guān)聯(lián)對(duì)象

應(yīng)用程序加載(一) -- dyld流程分析
應(yīng)用程序加載(二) -- dyld&objc關(guān)聯(lián)以及類的加載初探
應(yīng)用程序加載(三)-- 類的加載
應(yīng)用程序加載(四)-- 分類的加載
應(yīng)用程序加載(五)-- 類擴(kuò)展和關(guān)聯(lián)對(duì)象


1吼拥、類擴(kuò)展extension

類擴(kuò)展需要對(duì)應(yīng)著分類(category)比較研究凿可。
分類的作用:

  1. 添加方法,可以添加實(shí)例方法和類方法
  2. 添加協(xié)議
  3. 添加屬性(@property)枯跑,只會(huì)生成gettersetter的聲明敛助,而不會(huì)生成方法的實(shí)現(xiàn)和帶下劃線的成員變量

類擴(kuò)展的作用:

  1. 可以添加方法聲明,需要在原類的.m中實(shí)現(xiàn)
  2. 可以添加屬性
  3. 可以添加成員屬性

擴(kuò)展寫法

通常在創(chuàng)建工程中時(shí)會(huì)有一個(gè)ViewController類或者創(chuàng)建自定義的UIViewController子類的時(shí)候续扔,Xcode會(huì)在類中自動(dòng)創(chuàng)建一個(gè)類擴(kuò)展焕数,如圖:

我們經(jīng)常把不想暴露的一些屬性定義在這里,需要外部調(diào)用的屬性寫在.h文件中识脆。

需要注意類擴(kuò)展有一個(gè)位置要求,必須寫在類的interfaceimplementation之間仑荐。寫一個(gè)簡(jiǎn)單的例子纵东,就在VierController.m文件中,寫一個(gè)Person類洒扎,然后為Person添加一個(gè)類擴(kuò)展衰絮。
正確的寫法:

如果將類擴(kuò)展寫interface之前或者implementation之后,編譯器會(huì)報(bào)錯(cuò)胡诗。
interface之前:

implementation之后:

擴(kuò)展和分類的區(qū)別

分類中添加屬性是沒有gettersetter實(shí)現(xiàn)的煌恢,而擴(kuò)展中是有的震庭,我們可以通過(guò)c++層看到擴(kuò)展添加屬性會(huì)有什么效果。
首先在擴(kuò)展中添加一個(gè)dzName屬性

然后進(jìn)入到ViewController.m文件所在的路徑二汛,執(zhí)行xcrun -sdk iphonesimulator clang -rewrite-objc ViewController.m命令拨拓,生成ViewController.cpp文件,打開文件苫昌,搜索dzName

通過(guò)c++的代碼可以看到祟身,生成了屬性對(duì)應(yīng)的settergetter方法物独,還有帶有下劃線的成員變量(_dzName

在擴(kuò)展中還寫了一個(gè)ext_method方法,在c++中也能夠看到

直接加入到方法列表中婉陷。這點(diǎn)可以說(shuō)明,擴(kuò)展是類的一部分秽澳,而且方法在編譯期就加入到類中担神。


2、關(guān)聯(lián)對(duì)象

上面了解了妄讯,擴(kuò)展可以給類增加屬性亥贸。但是分類添加屬性的時(shí)候只是增加了gettersetter的聲明。例如:

有如圖中的分類荣挨,里面定義了一個(gè)屬性cat_name讹俊。當(dāng)我們調(diào)用的時(shí)候:

此時(shí)編譯器是不會(huì)報(bào)錯(cuò)的,說(shuō)明cat_namesetter方法的sel是可以找到的。當(dāng)運(yùn)行起來(lái)的時(shí)候寡壮,就會(huì)報(bào)錯(cuò)况既。因?yàn)檎也坏綄?duì)應(yīng)的imp。所以說(shuō)分類中添加屬性只是添加了gettersetter方法的聲明棒仍,而沒有方法的實(shí)現(xiàn)莫其。

那么如何分類中的屬性的實(shí)現(xiàn)呢?就是通過(guò)系統(tǒng)給我們提供的關(guān)聯(lián)對(duì)象乱陡。

關(guān)聯(lián)對(duì)象用法

#import <objc/runtime.h>
@interface Person (DZ)
@property (nonatomic, copy) NSString *cate_name;
@end

@implementation Person (DZ)
- (void)setCate_name:(NSString *)cate_name{
    /**
     1: 對(duì)象
     2: 標(biāo)識(shí)符
     3: value
     4: 策略
     */
    objc_setAssociatedObject(self, "cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

- (NSString *)cate_name{
    return  objc_getAssociatedObject(self, "cate_name");
}
@end
  • 分類中定義了一個(gè)屬性cate_name
  • 實(shí)現(xiàn)了cate_namegettersetter方法憨颠,方法中調(diào)用了關(guān)聯(lián)對(duì)象的兩個(gè)方法:
    • objc_setAssociatedObject:需要四個(gè)參數(shù)积锅,分別是對(duì)象养盗、標(biāo)識(shí)符、值和關(guān)聯(lián)策略
    • objc_getAssociatedObject:相對(duì)簡(jiǎn)單蹬跃,只有兩個(gè)參數(shù)铆铆,分別是對(duì)象和標(biāo)識(shí)符
    • 注意,使用關(guān)聯(lián)對(duì)象要引入runtime的頭文件

用法很容易翁都,但是底層是如何實(shí)現(xiàn)的呢谅猾?接下來(lái)我們來(lái)探索一下底層實(shí)現(xiàn)。

關(guān)聯(lián)對(duì)象的底層實(shí)現(xiàn)

還是在objc的源碼中坐搔,可以看到相關(guān)源碼敬矩,先來(lái)看看關(guān)聯(lián)對(duì)象的set實(shí)現(xiàn):

關(guān)聯(lián)對(duì)象set
void
objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
    SetAssocHook.get()(object, key, value, policy);
}
  • 此處與以往的objc開源代碼不同,在“781”的這個(gè)版本的源碼中凳忙,增加了一層代碼封裝禽炬,通過(guò)command點(diǎn)擊SetAssocHook,能夠看到這行代碼:static ChainedHookFunction<objc_hook_setAssociatedObject> SetAssocHook{_base_objc_setAssociatedObject};
  • 底層調(diào)用的就是_base_objc_setAssociatedObject函數(shù)
static void
_base_objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
{
  _object_set_associative_reference(object, key, value, policy);
}
  • _base_objc_setAssociatedObject實(shí)現(xiàn)中柳恐,調(diào)用的是_object_set_associative_reference
void
_object_set_associative_reference(id object, const 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
    //1热幔、相關(guān)的容錯(cuò)處理
    if (!object && !value) return;

    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));
    //2断凶、包裝了一下 對(duì)象
    DisguisedPtr<objc_object> disguised{(objc_object *)object};
    //3、 包裝一下 policy - value
    ObjcAssociation association{policy, value};

    //4认烁、根據(jù)關(guān)聯(lián)策略對(duì)值處理
    // retain the new value (if any) outside the lock.
    association.acquireValue();
    
    // 5、核心代碼
    {
        AssociationsManager manager;
    
        AssociationsHashMap &associations(manager.get());

        if (value) {
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            if (refs_result.second) {
                /* it's the first association we make */
                object->setHasAssociatedObjects();
            }

            /* establish or replace the association */
            auto &refs = refs_result.first->second; // 空的桶子
            auto result = refs.try_emplace(key, std::move(association));
            if (!result.second) {
                association.swap(result.first->second);
            }
        } else {
            auto refs_it = associations.find(disguised);
            if (refs_it != associations.end()) {
                auto &refs = refs_it->second;
                auto it = refs.find(key);
                if (it != refs.end()) {
                    association.swap(it->second);
                    refs.erase(it);
                    if (refs.size() == 0) {
                        associations.erase(refs_it);

                    }
                }
            }
        }
    }

    // release the old value (outside of the lock).
    association.releaseHeldValue();
}
  1. 首先代碼進(jìn)行了相關(guān)的容錯(cuò)處理嘹承,這些不用關(guān)心如庭,簡(jiǎn)單看看就好

  2. 將傳入的參數(shù)object進(jìn)行了一次包裝。

  3. 對(duì)第三個(gè)參數(shù)value和第四個(gè)參數(shù)policy包裝一下骤竹。

  4. association.acquireValue();根據(jù)關(guān)聯(lián)策略(policy參數(shù))往毡,對(duì)值進(jìn)行處理,看看源碼實(shí)現(xiàn):

    inline void acquireValue() {
            if (_value) {
                switch (_policy & 0xFF) {
                case OBJC_ASSOCIATION_SETTER_RETAIN:
                    _value = objc_retain(_value);
                    break;
                case OBJC_ASSOCIATION_SETTER_COPY:
                    _value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy));
                    break;
                }
            }
        }
    
    • 此處根據(jù)關(guān)聯(lián)策略對(duì)value進(jìn)行retain或者copy的操作懒震。
  5. 此處進(jìn)入核心代碼部分

    {
        AssociationsManager manager;
        
        AssociationsHashMap &associations(manager.get());
    
        if (value) {
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            if (refs_result.second) {
                /* it's the first association we make */
                object->setHasAssociatedObjects();
            }
    
            /* establish or replace the association */
            auto &refs = refs_result.first->second; // 空的桶子
            auto result = refs.try_emplace(key, std::move(association));
            if (!result.second) {
                association.swap(result.first->second);
            }
        } else {
            auto refs_it = associations.find(disguised);
            if (refs_it != associations.end()) {
                auto &refs = refs_it->second;
                auto it = refs.find(key);
                if (it != refs.end()) {
                    association.swap(it->second);
                    refs.erase(it);
                    if (refs.size() == 0) {
                        associations.erase(refs_it);
    
                    }
                }
            }
        }
    }
    
    • 初始化一個(gè)AssociationsManager變量manager个扰,它不是一個(gè)單例葱色,只是用它來(lái)給下面訪問(wèn)哈希表進(jìn)行加鎖

構(gòu)造函數(shù)和析構(gòu)函數(shù)就是加鎖解鎖操作。
- 通過(guò)manager.get()函數(shù)獲取到關(guān)聯(lián)對(duì)象的哈希表的引用&associations恐锣,類型是AssociationsHashMap
- 判斷value存在時(shí):
- 從哈希表中讀取disguised對(duì)應(yīng)的結(jié)果refs_result舞痰。disguised就是傳入的object參數(shù)诀姚。
- 通過(guò)結(jié)果refs_result中的second判斷是不是第一次來(lái)。如果第一次來(lái)就會(huì)調(diào)用object->setHasAssociatedObjects();函數(shù)
- setHasAssociatedObjects就是給對(duì)象object設(shè)置狀態(tài)呀打。代表這個(gè)對(duì)象是有關(guān)聯(lián)對(duì)象存在的糯笙。如果objectnonpointer,那么這個(gè)關(guān)聯(lián)狀態(tài)就存儲(chǔ)在isa
- 然后根據(jù)參數(shù)key創(chuàng)建關(guān)聯(lián)豺憔,如果之前關(guān)聯(lián)過(guò)就進(jìn)行替換關(guān)聯(lián)。
- 此處就可以知道恭应,是兩層哈希表。第一層是以對(duì)象作為哈希函數(shù)境肾,第二層是以闖入的參數(shù)key為哈希函數(shù)胆屿。
- value不存在時(shí):傳入的參數(shù)value為nil。就進(jìn)行擦除环鲤。因?yàn)槭请p層哈希彻秆,所以得做兩層擦除。

關(guān)聯(lián)對(duì)象set流程圖:


關(guān)聯(lián)對(duì)象get

此時(shí)對(duì)關(guān)聯(lián)對(duì)象的set有所了解了酒朵,再看看get實(shí)現(xiàn)就很簡(jiǎn)單扎附,就是通過(guò)傳入的兩個(gè)參數(shù),進(jìn)行哈希的雙層查找匙铡。源碼如下:

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

id
_object_get_associative_reference(id object, const void *key)
{
    ObjcAssociation association{};

    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
            ObjectAssociationMap &refs = i->second;
            ObjectAssociationMap::iterator j = refs.find(key);
            if (j != refs.end()) {
                association = j->second;
                association.retainReturnedValue();
            }
        }
    }

    return association.autoreleaseReturnedValue();
}
關(guān)聯(lián)對(duì)象雙層哈希示意圖
  • 圖中包含示例中的關(guān)聯(lián)對(duì)象例子
關(guān)聯(lián)對(duì)象表的移除

表的創(chuàng)建我們了解后鳖眼,就得看看什么時(shí)候?qū)Ρ磉M(jìn)行移除操作的嚼摩。移除是在dealloc中處理的,看看源碼:

- (void)dealloc {
    _objc_rootDealloc(self);
}

???

void
_objc_rootDealloc(id obj)
{
    ASSERT(obj);

    obj->rootDealloc();
}

???

inline void
objc_object::rootDealloc()
{
    if (isTaggedPointer()) return;  // fixme necessary?

    if (fastpath(isa.nonpointer  &&  
                 !isa.weakly_referenced  &&  
                 !isa.has_assoc  &&  
                 !isa.has_cxx_dtor  &&  
                 !isa.has_sidetable_rc))
    {
        assert(!sidetable_present());
        free(this);
    } 
    else {
        object_dispose((id)this);
    }
}

???

id 
object_dispose(id obj)
{
    if (!obj) return nil;

    objc_destructInstance(obj);    
    free(obj);

    return nil;
}

???

void *objc_destructInstance(id obj) 
{
    if (obj) {
        // Read all of the flags at once for performance.
        bool cxx = obj->hasCxxDtor();
        bool assoc = obj->hasAssociatedObjects();

        // This order is important.
        if (cxx) object_cxxDestruct(obj);
        if (assoc) _object_remove_assocations(obj);
        obj->clearDeallocating();
    }

    return obj;
}

???

void
_object_remove_assocations(id object)
{
    ObjectAssociationMap refs{};

    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
            refs.swap(i->second);
            associations.erase(i);
        }
    }

    // release everything (outside of the lock).
    for (auto &i: refs) {
        i.second.releaseHeldValue();
    }
}
  • 通過(guò)代碼的層層調(diào)用愿卒,最后調(diào)用到_object_remove_assocations函數(shù)
  • _object_remove_assocations函數(shù)中潮秘,找到object為key的關(guān)聯(lián)對(duì)象表(第二層哈希表),然后對(duì)表進(jìn)行所有數(shù)據(jù)擦除
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末柜候,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子滋尉,更是在濱河造成了極大的恐慌飞主,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件碾篡,死亡現(xiàn)場(chǎng)離奇詭異筏餐,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)穆律,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門导俘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人辅髓,你說(shuō)我怎么就攤上這事少梁。” “怎么了凯沪?”我有些...
    開封第一講書人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵妨马,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我身笤,道長(zhǎng)葵陵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任匕垫,我火速辦了婚禮伤柄,結(jié)果婚禮上文搂,老公的妹妹穿的比我還像新娘。我一直安慰自己煤蹭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開白布常挚。 她就那樣靜靜地躺著稽物,像睡著了一般。 火紅的嫁衣襯著肌膚如雪吼过。 梳的紋絲不亂的頭發(fā)上咪奖,一...
    開封第一講書人閱讀 51,443評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音赡艰,去河邊找鬼。 笑死揖闸,一個(gè)胖子當(dāng)著我的面吹牛料身,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播贮泞,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼幔烛,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了令蛉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蝎宇,失蹤者是張志新(化名)和其女友劉穎祷安,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體汇鞭,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡虱咧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了玄坦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绘沉。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖车伞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情困曙,我是刑警寧澤谦去,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站要糊,受9級(jí)特大地震影響妆丘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜勺拣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一药有、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸羊苟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)凉倚。三九已至,卻和暖如春扮碧,著一層夾襖步出監(jiān)牢的瞬間杏糙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工宏侍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人咱旱。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓绷耍,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親毯盈。 傳聞我的和親對(duì)象是個(gè)殘疾皇子病袄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354