類擴展&分類關聯(lián)對象底層探究

一、分類及擴展

分類又名 Category祖凫、類別
  • 分類中原則上只能添加方法型豁。不能增加成員變量。
  • 分類中可以訪問原來類中的成員變量互纯,但是只能訪問@protect和@public形式的變量
  • 分類中可以用@property 聲明變量瑟幕,只會生成變量的setter、getter方法的聲明,不能生成方法實現(xiàn) 和 帶下劃線的成員變量
  • 可以通過runtime 給分類添加屬性只盹,即屬性關聯(lián)辣往,重寫setter、getter方法
  • 如果分類中有和原有類同名的方法殖卑,會優(yōu)先調(diào)用分類中的方法站削,就是說會忽略原有類的方法。
  • 如果多個分類中都有同名方法 那么他的調(diào)用順序和編譯順序有關系
    可以在 bulid Phases -> Compile Sources中查看順序 由下而上調(diào)用
  • 如果繼承關系 子類 和父類 和父類分類擁有同名方法 子類調(diào)用 父類和父類分類(不管父類有無實現(xiàn))均不會響應孵稽。 如子類并未實現(xiàn) 父類 和父類分類 有同名方法 子類發(fā)起調(diào)用 則會 調(diào)用父類分類方法 父類分類方法(不管父類有無實現(xiàn))父類分類調(diào)用順序依舊和編譯順序有關
類擴展又名 Extension
  • 類擴展不僅可以增加方法许起, 可以增加成員變量及屬性,只是全部為私有的菩鲜。
  • 可以說成特殊的分類园细,也可稱之為 匿名分類。
類擴展探究

類擴展創(chuàng)建方式

  • 直接在類中寫接校,向下面我們熟悉的味道,永遠在實現(xiàn)之前聲明之后


    截屏2020-10-27 下午2.44.19.png
  • 手動創(chuàng)建一個.h文件單拎出來猛频。通過 command+N 新建 -> Objective-C File -> 選擇Extension
    截屏2020-10-27 下午2.47.05.png
類擴展底層原理探索

寫一個類擴展并通過clang查看其源碼

clang -rewrite-objc main.mm -o main.cpp
截屏2020-10-27 下午3.06.08.png

截屏2020-10-27 下午3.10.14.png
截屏2020-10-27 下午3.15.26.png
  • 查看 LGTeacher 類拓展的方法,在編譯過程中蛛勉,方法就直接添加到了 methodlist中伦乔,作為類的一部分,即編譯時期直接添加到本類里面

  • 運行objc源碼程序 在readClass寫上針對性LGTeacher的代碼


    截屏2020-10-27 下午3.30.56.png

總結(jié)

  • 由此可以看出 類擴展 在編譯期 就會作為類的一部分董习,和類一起編譯進來
  • 類擴展只是聲明烈和,且需要依賴當前的主類。

分類關聯(lián)對象底層探究

截屏2020-10-27 下午3.51.40.png
  • objc_setAssociatedObject源碼實現(xiàn)


    截屏2020-10-27 下午4.04.33.png
  • _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
    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));
/// 將object 偽裝成 DisguisedPtr 類型數(shù)據(jù)結(jié)構(gòu)
    DisguisedPtr<objc_object> disguised{(objc_object *)object};
/// 將策略 和 value 存入 ObjcAssociation 數(shù)據(jù)結(jié)構(gòu)中
    ObjcAssociation association{policy, value};

    // retain the new value (if any) outside the lock.
    association.acquireValue();
/// 局部作用域
    {
///初始化一個關聯(lián) 管理對象  在構(gòu)造時加鎖皿淋,析構(gòu)時開鎖
        AssociationsManager manager;
 
///   AssociationsHashMap 類型 就是一個嵌套的DenseMap 
///DenseMap<DisguisedPtr<objc_object>, DenseMap<const void *, ObjcAssociation>>
///   manager.get() :
/// 獲取的是一個 static的_mapStorage變量 associations數(shù)據(jù)為manager.get()
///獲取而來 所以associations是全局靜態(tài)變量
        AssociationsHashMap &associations(manager.get());

        if (value) {
//    try_emplace:         在這disguised作為key查找招刹,如果已經(jīng)在associations表中,就把查
/// 找到的桶作為DenseMapIterator的位置指針進行初始化窝趣,然后用pair包裝后返回疯暑;key
///沒在associations表中就把disguised作為key,ObjectAssociationMap{}作為value存入
///桶中哑舒,然后把該桶作為DenseMapIterator的位置指針進行初始化妇拯,然后用pair包裝后
///返回。返回值類型std::pair<DenseMapIterator, bool>
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            if (refs_result.second) {
                //能進來這里洗鸵,說明disguised為key的桶是新插入進來的越锈,所以根據(jù)條件設置isa_t中的has_assoc位為true
                object->setHasAssociatedObjects();
            }

            //找到associations中的disguised對應的ObjectAssociationMap表
            auto &refs = refs_result.first->second;
            //用key在ObjectAssociationMap表中查找,如果表中不存在該key那么就把key和association對應插入到ObjectAssociationMap中
            auto result = refs.try_emplace(key, std::move(association));
            //result.second為false, 說明ObjectAssociationMap表中原來已有該key膘滨,不會移動甘凭,所以這里進行了swap的操作來交換association的值。
            if (!result.second) {
                association.swap(result.first->second);
            }
        } else {   //value為nil, 取消關聯(lián)火邓。
        //先從associations表中找到disguised對應的ObjectAssociationMap表丹弱,用pair包裝后返回德撬。
            auto refs_it = associations.find(disguised);
            //如果從associations表中找到了disguised對應的ObjectAssociationMap表
            if (refs_it != associations.end()) {
                //從pair中拿到ObjectAssociationMap表
                auto &refs = refs_it->second;
                //從ObjectAssociationMap表中查找key對應的association,然后把它作為DenseMapIterator的位置指針初始化后返回
                auto it = refs.find(key);
                //如果找到了就進去
                if (it != refs.end()) {
                    //這里交換值是為了把要擦除的association記錄下來躲胳,因為下面還要進行releaseHeldValue
                    association.swap(it->second);
                    //從ObjectAssociationMap表中擦除association以及其他相應的操作
                    refs.erase(it);
                    if (refs.size() == 0) {
                        //說明沒有關聯(lián)的值了蜓洪,從associations表中擦除ObjectAssociationMap表
                        associations.erase(refs_it);
                    }
                }
            }
        }
    }

    // release the old value (outside of the lock).//釋放
    association.releaseHeldValue();
}
 
  • AssociationsManager源碼結(jié)構(gòu)
// class AssociationsManager manages a lock / hash table singleton pair.
// Allocating an instance acquires the lock

class AssociationsManager {
    using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
    static Storage _mapStorage;

public:
    AssociationsManager()   { AssociationsManagerLock.lock(); }///初始化 加鎖
    ~AssociationsManager()  { AssociationsManagerLock.unlock(); }///析構(gòu) 解鎖

    AssociationsHashMap &get() { 
        return _mapStorage.get();
    }

    static void init() {
        _mapStorage.init();
    }
}

從中可以看出get() 方法獲取的為全局靜態(tài)變量 唯一的

  • AssociationsHashMap &associations() 源碼結(jié)構(gòu)
class ObjcAssociation {
    uintptr_t _policy;
    id _value;
.....省略
}

typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;
  
截屏2020-10-27 下午10.25.39.png
  • try_emplace
2251862-56319ac00d5108f6.jpg
  • iterator
    截屏2020-10-27 下午11.14.44.png

    截屏2020-10-27 下午11.16.39.png

    從這里可以看出 返回值 pair<iterator,bool>類型 可以拆解為
    pair<KeyT,ValueT,ValueInfoT,KeyInfoT,BucketT,false ,Bool>
    pair<KeyT,ValueT,DenseMapValueInfo<ValueT>,DenseMapInfo<KeyT>,detail::DenseMapPair<KeyT, ValueT>,false ,Bool>
截屏2020-10-27 下午10.59.33.png

優(yōu)化打印值

///1.KeyT
//pair<objc::DenseMapIterator<DisguisedPtr<objc_object>,

///2.ValueT
//objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>,    objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >,

///3.DenseMapValueInfo<ValueT>    ->      ValueInfoT
//objc::DenseMapValueInfo<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >,

///4.DenseMapInfo<KeyT>      ->     KeyInfoT
//objc::DenseMapInfo<DisguisedPtr<objc_object> >,

///5.detail::DenseMapPair<KeyT, ValueT>  ->      BucketT
//objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >,

///6.false
//false>,

///7.bool
//bool>

到這里可以得出下面的一張關系圖


關聯(lián)對象結(jié)構(gòu) 2.jpg

objc_getAssociatedObject源碼分析

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)
{   
    //先初始化一個用來接收值的association
    ObjcAssociation association{};
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        //用object作為key從associations表中找到對應的ObjectAssociationMap表
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
            ObjectAssociationMap &refs = i->second;
            //用key在ObjectAssociationMap表中搜索對應的ObjcAssociation
            ObjectAssociationMap::iterator j = refs.find(key);
            if (j != refs.end()) {
                //找到后賦值給association,然后retain
                association = j->second;
                association.retainReturnedValue();
            }
        }
    }

    return association.autoreleaseReturnedValue();
}

關聯(lián)對象釋放
通過_objc_rootDealloc->rootDealloc->object_dispose->objc_destructInstance

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);
        //如果有關聯(lián)對象坯苹,移除
        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();
    }
}

從中可以看出關聯(lián)對象不用手動移除隆檀,在對象釋放時會自動移除。

總結(jié)

  • 類擴展是在編譯期就已經(jīng)是類的一部分了北滥。一般用于對外隱藏屬性和方法刚操,但并不是真正的私有。
  • 關聯(lián)對象通過manager維護了以偽裝后的objc_object指針為key的AssociationsHashMap表和以const void *類型的指針為key的ObjectAssociationMap表再芋。ObjectAssociation就存在ObjectAssociationMap表中菊霜。
  • 關聯(lián)對象在對象釋放時會自動移除。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末济赎,一起剝皮案震驚了整個濱河市鉴逞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌司训,老刑警劉巖构捡,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異壳猜,居然都是意外死亡勾徽,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門统扳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來喘帚,“玉大人,你說我怎么就攤上這事咒钟〈涤桑” “怎么了?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵朱嘴,是天一觀的道長倾鲫。 經(jīng)常有香客問我,道長萍嬉,這世上最難降的妖魔是什么乌昔? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮帚湘,結(jié)果婚禮上玫荣,老公的妹妹穿的比我還像新娘。我一直安慰自己大诸,他們只是感情好捅厂,可當我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著资柔,像睡著了一般焙贷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上贿堰,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天辙芍,我揣著相機與錄音,去河邊找鬼羹与。 笑死故硅,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的纵搁。 我是一名探鬼主播吃衅,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼腾誉!你這毒婦竟也來了徘层?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤利职,失蹤者是張志新(化名)和其女友劉穎趣效,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體猪贪,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡跷敬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了热押。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片西傀。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖楞黄,靈堂內(nèi)的尸體忽然破棺而出池凄,到底是詐尸還是另有隱情,我是刑警寧澤鬼廓,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布肿仑,位于F島的核電站,受9級特大地震影響碎税,放射性物質(zhì)發(fā)生泄漏尤慰。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一雷蹂、第九天 我趴在偏房一處隱蔽的房頂上張望伟端。 院中可真熱鬧,春花似錦匪煌、人聲如沸责蝠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽霜医。三九已至齿拂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間肴敛,已是汗流浹背署海。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留医男,地道東北人砸狞。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像镀梭,于是被迫代替她去往敵國和親刀森。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,440評論 2 359