runtime剖析的真是越來越復(fù)雜了改执。啸蜜。。

isa_t類型詳解

在新版的runtime源碼中辈挂,NSObject類型最終會轉(zhuǎn)化為object_class類型衬横,而object_class集成自objc_object,在結(jié)構(gòu)體objc_object中就含有isa_t類型的成員isa

查看isa_t的源碼,其中處理兩個構(gòu)造函數(shù)外终蒂,有一個cls指針蜂林,還有一個uintptr_t類型的成員bits以及一個結(jié)構(gòu)體:

union isa_t {
      isa_t(){ }
      isa_t(uintptr_t value) : bits(value) {  }

      Class cls;
      uintptr_t bits;
  #if defined(ISA_BITFIELD)
      struct {
          ISA_BITFIELD;  // defined in isa.h
        };
   #endif
}

查看結(jié)構(gòu)體的源碼可以發(fā)現(xiàn),在結(jié)構(gòu)體中使用位域來存儲了很多信息后豫,此處展示arm64架構(gòu)下的源碼信息

#define ISA_MASK        0x0000000ffffffff8ULL
#define ISA_MAGIC_MASK  0x000003f000000001ULL
#define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct {
    uintptr_t nonpointer        : 1;                                       
    uintptr_t has_assoc         : 1;                                       
    uintptr_t has_cxx_dtor      : 1;                                       
    uintptr_t shiftcls          : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ 
    uintptr_t magic             : 6;                                       
    uintptr_t weakly_referenced : 1;                                       
    uintptr_t deallocating      : 1;                                       
    uintptr_t has_sidetable_rc  : 1;                                       
    uintptr_t extra_rc          : 19
};

isa_t位域存放信息類型

isa_t作為公用體悉尾,內(nèi)部使用8個字節(jié)的內(nèi)存空間突那,共64位二進(jìn)制挫酿,存放了以下信息
 * nonpointer代表是否是優(yōu)化過的isa指針,占用1位愕难。
  * 1:表示新版本的isa指針早龟,使用位域來存儲信息
  * 0:舊版本普通的isa指針,直接存儲Class和Meta-Class內(nèi)存地址
  * has_assoc代表是否有關(guān)聯(lián)對象猫缭,占用1位葱弟,一旦設(shè)置過關(guān)聯(lián)對象,則會置為1猜丹。如果添加過關(guān)聯(lián)對象芝加,在釋放時會檢測是否有關(guān)聯(lián)對象,所以釋放會更慢射窒。
  * has_cxx_dtor代表是否實現(xiàn)了C++的析構(gòu)函數(shù)(.cxx_destruct)藏杖,如果沒有将塑,釋放時的速度會更快。占用1位
  * shiftcls中存放著類或者元類的內(nèi)存地址蝌麸,占用33位点寥。
  * magic是調(diào)試時用來判斷對象是否完成初始化,占用6位
  * weakly_referenced代表是否被弱引用指向過来吩,占用1位敢辩,如果為0,則釋放時速度會更快
  * deallocating用來表示對象是否正在釋放弟疆,占用1位
  * extra_rc用來存儲引用計數(shù)的值戚长,占用19位,此處需要注意的時兽间,它存儲的是引用計數(shù)的值-1历葛。如果對象的引用計數(shù)為1,則extra_rc中存儲的值為0
  * has_sidetable_rc用來表示是否將引用計數(shù)存儲在SideTable中嘀略,引用計數(shù)的值過大恤溶,在extra_rc無法存儲,則會將引用計數(shù)存放到SideTable當(dāng)中帜羊。

Class底層結(jié)構(gòu)分析

* 上面我們已經(jīng)分析來isa_t的結(jié)構(gòu)

struct objc_object {
private:
    isa_t isa;
public:
    ......
}

* objc_class繼承自結(jié)構(gòu)體objc_object,結(jié)構(gòu)可以簡化如下

struct objc_class{
    Class ISA;                       //isa指針咒程,通過位域存放多個信息
    Class superclass;           //supperClass
    cache_t cache;              // 方法緩存
    class_data_bits_t bits;    // 用來獲取類的具體信息
}
ojbc_class 除了有isa指針外,還保存了父類的class讼育,方法緩存以及當(dāng)前類的一些基本信息帐姻。

查找class_data_bits_t

  • 其實一開始是沒有class_rw_t的,而存放的是class_ro_t,class_rw_t是在之后進(jìn)行創(chuàng)建的奶段。
  • class_rw_t是可讀可寫的饥瓷,它包含了類的初始內(nèi)容,分類的內(nèi)容痹籍。
  • class_ro_t是只讀的呢铆,它包含了類初始化的內(nèi)容,并且在編譯完成后就決定了蹲缠,在運行時無法進(jìn)行修改棺克。

源碼分析

上文提到,在類初始化的時候其實class中保存的是class_ro_t而不是class_rw_t线定,這一點可以通過objc-runtime-new.mm中的realizeClassWithoutSwift函數(shù)可以看出

static Class realizeClassWithoutSwift(Class cls){
    const class_ro_t *ro;
    class_rw_t *rw;
    Class supercls;
    Class metacls;
    bool isMeta;
    
    if (!cls) return nil;
    //如果class已經(jīng)初始化娜谊,則直接返回當(dāng)前class
    if (cls->isRealized()) return cls;
    assert(cls == remapClass(cls));
    //首先通過class的data()函數(shù)取到class中bits中存放的class_ro_t
    ro = (const class_ro_t *)cls->data();
    if (ro->flags & RO_FUTURE) {
        //如果當(dāng)前的cls是future class,并且rw已經(jīng)被創(chuàng)建斤讥,則直接拿到rw和rw中的ro
        rw = cls->data();
        ro = cls->data()->ro;
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        //如果是普通的class纱皆,創(chuàng)建rw
        rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
        //將ro賦值給rw中的ro
        rw->ro = ro;
        //設(shè)置rw的flags
        rw->flags = RW_REALIZED|RW_REALIZING;
        //將rw設(shè)置到cls中的bits中去
        cls->setData(rw);
    }
    
    ......
    
    //遞歸初始化父類
    supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
    //遞歸初始化元類,通過isa指針來獲取到cls的元類
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));
    
    ......
    
    //修改rw中的方法列表,屬性列表和協(xié)議列表派草,并且將分類中的方法列表撑帖,屬性列表和協(xié)議列表附加到rw中去
    methodizeClass(cls);
}


在類初始化時,cls通過data()函數(shù)獲取到的其實是class_to_t,內(nèi)部存放了類初始的方法列表澳眷、屬性列表和協(xié)議列表胡嘿。如果當(dāng)前cls是普通的class,則通過calloc函數(shù)創(chuàng)建rw钳踊,然后將rw中的ro指針指向原始的ro(class_ro_t),之后重置rw中的flags衷敌,并將rw的內(nèi)存地址保存到cls的bits中去。并且拓瞪,函數(shù)中首先是通過遞歸初始化當(dāng)前父類以及元類缴罗。最后才初始化當(dāng)前類。

創(chuàng)建完rw(class_rw_t)后祭埂,則會重新整理cls中的方法列表面氓、屬性列表和協(xié)議列表。具體查看methodizeClass函數(shù)源碼:

static void methodizeClass(Class cls){
    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro;
    // 從ro中拿到baseMethodList
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
        //將baseMethodList附加到rw的methods中去
        rw->methods.attachLists(&list, 1);
    }
    // 從ro中拿到baseProperties
    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
        //將baseProperties附加到rw的properties中去
        rw->properties.attachLists(&proplist, 1);
    }
    //從ro中拿到baseProtocols
    protocol_list_t *protolist = ro->baseProtocols;
    if (protolist) {
        //將baseProtocols附加到rw的protocols中去
        rw->protocols.attachLists(&protolist, 1);
    }

    //最后將所有Category的方法列表蛆橡、屬性列表和協(xié)議列表附加到cls
    category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
    attachCategories(cls, cats, false /*don't flush caches*/);
    
}

methodizeClass 函數(shù)中首先會拿到ro中的方法列表舌界、屬性列表和協(xié)議列表,然后將拿到的方法泰演、屬性列表和協(xié)議列表通過對應(yīng)的attachLists函數(shù)附加到rw的二維數(shù)組中去呻拌。

void attachLists(List* const * addedLists, uint32_t addedCount) {
    if (addedCount == 0) return;
    //這里以方法列表為例
    //array()->lists表示原來類中的方法列表
    //addedLists表示所有Category中的方法列表
    if (hasArray()) {
        //獲取原來類中方法列表的長度
        uint32_t oldCount = array()->count;
        //得到方法合并之后的新的數(shù)組長度
        uint32_t newCount = oldCount + addedCount;
        //給array重新分配長度為newCount的內(nèi)存空間
        setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
        array()->count = newCount;
        //將原來array()->lists中的數(shù)據(jù)移動到數(shù)組中oldCount的位置
        //也就是相當(dāng)于將array()->lists的數(shù)據(jù)在內(nèi)存中往后移動了addedCount個位置
        memmove(array()->lists + addedCount, array()->lists,
                oldCount * sizeof(array()->lists[0]));
        //將Category中的方法列表copy到array()->lists中
        //并且是從數(shù)組的起始地址開始存放
        memcpy(array()->lists, addedLists,
               addedCount * sizeof(array()->lists[0]));
    }
    else if (!list  &&  addedCount == 1) {
        // 0 lists -> 1 list
        list = addedLists[0];
    }
    else {
        // 1 list -> many lists
        List* oldList = list;
        uint32_t oldCount = oldList ? 1 : 0;
        uint32_t newCount = oldCount + addedCount;
        setArray((array_t *)malloc(array_t::byteSize(newCount)));
        array()->count = newCount;
        if (oldList) array()->lists[addedCount] = oldList;
        memcpy(array()->lists, addedLists,
               addedCount * sizeof(array()->lists[0]));
    }

安裝完類本身的方法、屬性和協(xié)議后睦焕,會繼續(xù)通過attachCategories函數(shù)拿到class的所有Category中的方法藐握、屬性和協(xié)議列表,然后調(diào)用attachLists函數(shù)附加到rw中的二維數(shù)組中去垃喊。

//將方法列表猾普、屬性列表、協(xié)議列表附加到類中去
//假設(shè)cats中的所有的類別都是按順序進(jìn)行加載和排序的本谜,最早裝載進(jìn)內(nèi)存的類別是第一個
static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);
    //用來判斷是否是元類
    bool isMeta = cls->isMetaClass();

    //申請連續(xù)內(nèi)存空間初家,創(chuàng)建一個二維數(shù)組,里面存放著所有的method_list_t
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
    //申請連續(xù)內(nèi)存空間耕突,創(chuàng)建一個二維數(shù)組笤成,里面存放著所有的property_list_t
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    //申請連續(xù)內(nèi)存空間评架,創(chuàng)建一個二維數(shù)組眷茁,里面存放著所有的protocol_list_t
    protocol_list_t **protolists = (protocol_list_t **)
        malloc(cats->count * sizeof(*protolists));

    // Count backwards through cats to get newest categories first
    int mcount = 0;
    int propcount = 0;
    int protocount = 0;
    int i = cats->count;
    bool fromBundle = NO;
    //獲取到category_list之后,通過逆序遍歷來取出Category內(nèi)部的方法纵诞、屬性和協(xié)議列表
    while (i--) {
        auto& entry = cats->list[i];
        //遍歷cls所有的category_t上祈,將category_t中的method_list_t取出,存放到二維數(shù)組mlists中
        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }
        // 將category_t中的property_list_t取出,存放到二維數(shù)組proplists中
        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;
        }
        //將category_t中的protocol_list_t取出登刺,存放到二維數(shù)組protolists中
        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }
    //拿到類對象cls的class_rw_t類型的成員data籽腕,它是可讀可寫的
    auto rw = cls->data();

    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    //將方法列表合并到rw的方法列表中去,并且插入到表頭位置
    rw->methods.attachLists(mlists, mcount);
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);
    //將屬性列表合并到rw的屬性列表中去纸俭,并且插入到表頭位置
    rw->properties.attachLists(proplists, propcount);
    free(proplists);
    //將協(xié)議列表合并到rw的協(xié)議列表中去皇耗,并且插入到表頭位置
    rw->protocols.attachLists(protolists, protocount);
    free(protolists);
}

因為是先附加類本身的方法、屬性和協(xié)議揍很,之后附加Category的方法郎楼、屬性和協(xié)議,并且attachLists操作從數(shù)組的頭部開始進(jìn)行附加窒悔,所以先執(zhí)行附加操作的方法呜袁、屬性和協(xié)議會放到數(shù)組的后面,因此類本身實現(xiàn)的方法简珠、屬性和協(xié)議肯定放在rw二維數(shù)組的最后一個元素阶界。

說一下對isa指針的理解

isa等價于 is kind of

  • 實例對象isa指向類對象
  • 類對象isa指向元類對象
  • 元類對象isa指向元類的基類

isa有兩種類型
* 純指針,指向內(nèi)存地址 Class 和 metaClass的內(nèi)存地址
* NON_POINTER_ISA聋庵,除了內(nèi)存地址外膘融,還存放了一些其他的信息

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市祭玉,隨后出現(xiàn)的幾起案子托启,更是在濱河造成了極大的恐慌,老刑警劉巖攘宙,帶你破解...
    沈念sama閱讀 216,744評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屯耸,死亡現(xiàn)場離奇詭異,居然都是意外死亡蹭劈,警方通過查閱死者的電腦和手機(jī)疗绣,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铺韧,“玉大人多矮,你說我怎么就攤上這事」颍” “怎么了塔逃?”我有些...
    開封第一講書人閱讀 163,105評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長料仗。 經(jīng)常有香客問我湾盗,道長,這世上最難降的妖魔是什么立轧? 我笑而不...
    開封第一講書人閱讀 58,242評論 1 292
  • 正文 為了忘掉前任格粪,我火速辦了婚禮躏吊,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘帐萎。我一直安慰自己比伏,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,269評論 6 389
  • 文/花漫 我一把揭開白布疆导。 她就那樣靜靜地躺著赁项,像睡著了一般。 火紅的嫁衣襯著肌膚如雪澈段。 梳的紋絲不亂的頭發(fā)上肤舞,一...
    開封第一講書人閱讀 51,215評論 1 299
  • 那天,我揣著相機(jī)與錄音均蜜,去河邊找鬼李剖。 笑死,一個胖子當(dāng)著我的面吹牛囤耳,可吹牛的內(nèi)容都是我干的篙顺。 我是一名探鬼主播,決...
    沈念sama閱讀 40,096評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼充择,長吁一口氣:“原來是場噩夢啊……” “哼德玫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起椎麦,我...
    開封第一講書人閱讀 38,939評論 0 274
  • 序言:老撾萬榮一對情侶失蹤宰僧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后观挎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體琴儿,經(jīng)...
    沈念sama閱讀 45,354評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,573評論 2 333
  • 正文 我和宋清朗相戀三年嘁捷,在試婚紗的時候發(fā)現(xiàn)自己被綠了造成。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,745評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡雄嚣,死狀恐怖晒屎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情缓升,我是刑警寧澤鼓鲁,帶...
    沈念sama閱讀 35,448評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站港谊,受9級特大地震影響骇吭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜封锉,卻給世界環(huán)境...
    茶點故事閱讀 41,048評論 3 327
  • 文/蒙蒙 一绵跷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧成福,春花似錦碾局、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蕴潦,卻和暖如春像啼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背潭苞。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評論 1 269
  • 我被黑心中介騙來泰國打工忽冻, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人此疹。 一個月前我還...
    沈念sama閱讀 47,776評論 2 369
  • 正文 我出身青樓僧诚,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蝗碎。 傳聞我的和親對象是個殘疾皇子湖笨,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,652評論 2 354