iOS 源碼分析 Class 本質(zhì)颠区,objc_class削锰,class_rw_t,class_ro_t 分析

iOS 源碼分析 Class 本質(zhì)毕莱,objc_class器贩,class_rw_t,class_ro_t 分析

我們先來看下源碼內(nèi)部對clas的定義

typedef struct objc_class *Class;

可以看出來朋截,他就是一個 objc_class 指針


struct objc_class : objc_object {
    // Class ISA;
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }


我們摘出來重要的部分蛹稍,可以看出來,我們的 objc_class 是繼承 objc_object 結(jié)構(gòu)體


struct objc_object {
private:
    isa_t isa;

我們的 objc_object 有 isa 指針部服,所以我們的 class 也有 isa 指針唆姐,這就是為什么 我們可以通過我們的類對象找到我們的元類的原因,因為我們的額class 有 isa 指針廓八,如果沒有isa指針是不行的奉芦,然后還有個 superclass class 指針,只用來指向當(dāng)前類的父類的剧蹂,蘋果有一張圖声功,畫了父類和元類的關(guān)系,指向宠叼,class 的這個結(jié)構(gòu)體先巴,完全可以詮釋那張圖,cache_t 我之前的文章也講過冒冬,是加快查找效率的伸蚯,可以看看我那篇文章,然后简烤,下一個很重要的東西就是 bits

struct class_data_bits_t {

    // Values are the FAST_ flags above.
    uintptr_t bits;

bits 可以理解為一個指針剂邮,里面存放著 class_rw_t 和 class_ro_t 的地址,之前文章也有說過乐埠,蘋果為了節(jié)省空間抗斤,讓一個指針里面保存更多的信息囚企,到時候通過按位與運算,取出不同的值就行瑞眼。

比如當(dāng)我們?nèi)〕鰜砦覀僣lass的data的時候,實際上就是取出來這個類的一些信息

 class_rw_t *data() { 
        return bits.data();
    }

可以看到龙宏,返回的是一個 class_rw_t 結(jié)構(gòu)體,然后調(diào)用 bits.data();伤疙,我們看下實現(xiàn)

class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }

這段代碼什么意思呢银酗?就是就是找到 bits 這個指針中 class_rw_t 這個結(jié)構(gòu)體對應(yīng)的那個data的值,通過按位與運算徒像,#define FAST_DATA_MASK 0x00007ffffffffff8UL 其實就是取出來黍特,bits中第3-64位,然后就拿到了我們想要的值锯蛀,這個設(shè)計是不是很牛灭衷,然后我們看下 class_rw_t 這個結(jié)構(gòu)體


struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;


從結(jié)構(gòu)體重可以看到,累的 方法旁涤,屬性翔曲,協(xié)議,都保存在這里劈愚,然后還有個 const class_ro_t *ro; 這個是什么呢瞳遍?



struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;


1. baseMethodList 方法列表
2. baseProtocols 協(xié)議列表
3. ivars 成員變量列表
4. baseProperties 屬性列表
5. weakIvarLayout weak 成員變量內(nèi)存布局
6. ivarLayout 成員變量 ivar 內(nèi)存布局,是放在我們的 io 里面的菌羽,并且是 const 不允許修改的掠械,也就是說明,我們的 成員變量布局注祖,在編譯階段就確定了猾蒂,內(nèi)存布局已經(jīng)確定了,在運行時是不可以修改了氓轰,這就說明了婚夫,為什么運行時不能往類中動態(tài)添加成員變量浸卦。

class_ro_t 的意思是 readonly 的署鸡,在編譯階段就已經(jīng)確定了,不可以修改限嫌。

class_ro_t 是只讀的靴庆,是再編譯的時候,將累的屬性怒医,方法炉抒,協(xié)議和成員變量,添加到我們的 class_ro_t 中稚叹,然后運行的時候焰薄,會動態(tài)的創(chuàng)建 class_rw_t 然后將 class_ro_t 和分類中的屬性拿诸,協(xié)議方法存儲到 class_rw_t 中,并進行排序塞茅,分類中的存儲在數(shù)組的前部亩码,原始類信息,存儲在數(shù)組的后面野瘦,class_ro_t 是只能的描沟,在運行時是不可以添加進去的

class_rw_t 是運行時可以添加的,比如分類中的方法會在運行時鞭光,添加到 class_rw_t 的 method_array_t methods; 中去吏廉,可以看到,我們的 class_rw_t 中沒有成員變量的信息惰许,成員變量的信息是以編譯就確定添加到 class_ro_t 中去席覆,并且只讀

接著跟著源碼分析下


static Class realizeClassWithoutSwift(Class cls)
{
    runtimeLock.assertLocked();

    const class_ro_t *ro;
    class_rw_t *rw;
    Class supercls;
    Class metacls;
    bool isMeta;

    if (!cls) return nil;
    if (cls->isRealized()) return cls;
    assert(cls == remapClass(cls));

    // fixme verify class is not in an un-dlopened part of the shared cache?

    ro = (const class_ro_t *)cls->data();
    if (ro->flags & RO_FUTURE) {
        // This was a future class. rw data is already allocated.
        rw = cls->data();
        ro = cls->data()->ro;
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // Normal class. Allocate writeable class data.
        rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
        rw->ro = ro;
        rw->flags = RW_REALIZED|RW_REALIZING;
        cls->setData(rw);
    }

    isMeta = ro->flags & RO_META;

    rw->version = isMeta ? 7 : 0;  // old runtime went up to 6


    // Choose an index for this class.
    // Sets cls->instancesRequireRawIsa if indexes no more indexes are available
    cls->chooseClassArrayIndex();

    if (PrintConnecting) {
        _objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
                     cls->nameForLogging(), isMeta ? " (meta)" : "", 
                     (void*)cls, ro, cls->classArrayIndex(),
                     cls->isSwiftStable() ? "(swift)" : "",
                     cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
    }

    // Realize superclass and metaclass, if they aren't already.
    // This needs to be done after RW_REALIZED is set above, for root classes.
    // This needs to be done after class index is chosen, for root metaclasses.
    // This assumes that none of those classes have Swift contents,
    //   or that Swift's initializers have already been called.
    //   fixme that assumption will be wrong if we add support
    //   for ObjC subclasses of Swift classes.
    supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));

#if SUPPORT_NONPOINTER_ISA
    // Disable non-pointer isa for some classes and/or platforms.
    // Set instancesRequireRawIsa.
    bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
    bool rawIsaIsInherited = false;
    static bool hackedDispatch = false;

    if (DisableNonpointerIsa) {
        // Non-pointer isa disabled by environment or app SDK version
        instancesRequireRawIsa = true;
    }
    else if (!hackedDispatch  &&  !(ro->flags & RO_META)  &&  
             0 == strcmp(ro->name, "OS_object")) 
    {
        // hack for libdispatch et al - isa also acts as vtable pointer
        hackedDispatch = true;
        instancesRequireRawIsa = true;
    }
    else if (supercls  &&  supercls->superclass  &&  
             supercls->instancesRequireRawIsa()) 
    {
        // This is also propagated by addSubclass() 
        // but nonpointer isa setup needs it earlier.
        // Special case: instancesRequireRawIsa does not propagate 
        // from root class to root metaclass
        instancesRequireRawIsa = true;
        rawIsaIsInherited = true;
    }
    
    if (instancesRequireRawIsa) {
        cls->setInstancesRequireRawIsa(rawIsaIsInherited);
    }
// SUPPORT_NONPOINTER_ISA
#endif

    // Update superclass and metaclass in case of remapping
    cls->superclass = supercls;
    cls->initClassIsa(metacls);

    // Reconcile instance variable offsets / layout.
    // This may reallocate class_ro_t, updating our ro variable.
    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

    // Set fastInstanceSize if it wasn't set already.
    cls->setInstanceSize(ro->instanceSize);

    // Copy some flags from ro to rw
    if (ro->flags & RO_HAS_CXX_STRUCTORS) {
        cls->setHasCxxDtor();
        if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
            cls->setHasCxxCtor();
        }
    }
    
    // Propagate the associated objects forbidden flag from ro or from
    // the superclass.
    if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
        (supercls && supercls->forbidsAssociatedObjects()))
    {
        rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
    }

    // Connect this class to its superclass's subclass lists
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    // Attach categories
    methodizeClass(cls);

    return cls;
}

我們在 alloc 的時候,會調(diào)用上面的方法汹买,然后緊接著有個判斷

    // This was a future class. rw data is already allocated.
        rw = cls->data();
        ro = cls->data()->ro;
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // Normal class. Allocate writeable class data.
        rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
        rw->ro = ro;
        rw->flags = RW_REALIZED|RW_REALIZING;
        cls->setData(rw);
    }

判斷我們的rw是否創(chuàng)建過了娜睛,如果沒有,我們會創(chuàng)建一個rw的結(jié)構(gòu)體卦睹,然后將ro賦值給rw中的ro畦戒,然后將rw賦值給class,


 supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));

然后遞歸遍歷他的父類和元類结序,同樣的方式分配障斋。最后


 // Attach categories
    methodizeClass(cls);


static void methodizeClass(Class cls)
{
    runtimeLock.assertLocked();

    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro;

    // Methodizing for the first time
    if (PrintConnecting) {
        _objc_inform("CLASS: methodizing class '%s' %s", 
                     cls->nameForLogging(), isMeta ? "(meta)" : "");
    }

    // Install methods and properties that the class implements itself.
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
        rw->methods.attachLists(&list, 1);
    }

    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
        rw->properties.attachLists(&proplist, 1);
    }

    protocol_list_t *protolist = ro->baseProtocols;
    if (protolist) {
        rw->protocols.attachLists(&protolist, 1);
    }

    // Root classes get bonus method implementations if they don't have 
    // them already. These apply before category replacements.
    if (cls->isRootMetaclass()) {
        // root metaclass
        addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO);
    }

    // Attach categories.
    category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
    attachCategories(cls, cats, false /*don't flush caches*/);

    if (PrintConnecting) {
        if (cats) {
            for (uint32_t i = 0; i < cats->count; i++) {
                _objc_inform("CLASS: attached category %c%s(%s)", 
                             isMeta ? '+' : '-', 
                             cls->nameForLogging(), cats->list[i].cat->name);
            }
        }
    }
    
    if (cats) free(cats);

#if DEBUG
    // Debug: sanity-check all SELs; log method list contents
    for (const auto& meth : rw->methods) {
        if (PrintConnecting) {
            _objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-', 
                         cls->nameForLogging(), sel_getName(meth.name));
        }
        assert(sel_registerName(sel_getName(meth.name)) == meth.name); 
    }
#endif
}

可以看到


 method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
        rw->methods.attachLists(&list, 1);
    }

    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
        rw->properties.attachLists(&proplist, 1);
    }

    protocol_list_t *protolist = ro->baseProtocols;
    if (protolist) {
        rw->protocols.attachLists(&protolist, 1);
    }

是將ro里面改的方法列表啊,屬性列表還有協(xié)議列表徐鹤,添加到rw里面,然后緊接著


 // Attach categories.
    category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
    attachCategories(cls, cats, false /*don't flush caches*/);



static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
    if (!cats) return;
    if (PrintReplacedMethods) printReplacements(cls, cats);

    bool isMeta = cls->isMetaClass();

    // fixme rearrange to remove these intermediate allocations
    method_list_t **mlists = (method_list_t **)
        malloc(cats->count * sizeof(*mlists));
    property_list_t **proplists = (property_list_t **)
        malloc(cats->count * sizeof(*proplists));
    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;
    while (i--) {
        auto& entry = cats->list[i];

        method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
        if (mlist) {
            mlists[mcount++] = mlist;
            fromBundle |= entry.hi->isBundle();
        }

        property_list_t *proplist = 
            entry.cat->propertiesForMeta(isMeta, entry.hi);
        if (proplist) {
            proplists[propcount++] = proplist;
        }

        protocol_list_t *protolist = entry.cat->protocols;
        if (protolist) {
            protolists[protocount++] = protolist;
        }
    }

    auto rw = cls->data();

    prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    rw->methods.attachLists(mlists, mcount);
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);

    rw->properties.attachLists(proplists, propcount);
    free(proplists);

    rw->protocols.attachLists(protolists, protocount);
    free(protolists);
}


prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
    rw->methods.attachLists(mlists, mcount);
    free(mlists);
    if (flush_caches  &&  mcount > 0) flushCaches(cls);

    rw->properties.attachLists(proplists, propcount);
    free(proplists);

    rw->protocols.attachLists(protolists, protocount);
    free(protolists);

將分類中的發(fā)昂發(fā)垃环,屬性,協(xié)議返敬,添加到rw中

總結(jié)


struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

最后看一眼我們的 class_ro_t 遂庄,有三個屬性是不允許我們修改的

const uint8_t * ivarLayout;
    
    const char * name;
    
        const ivar_list_t * ivars;

    

當(dāng)我們初始化一個類的時候

  1. 在編譯的時候已經(jīng)確定我們的類的原始信息,并將它存儲在 class_ro_t 結(jié)構(gòu)體中劲赠,并且運行時不能改變
  2. 遞歸初始化他的父類和元類
  3. 將ro中的方法協(xié)議屬性等涛目,添加到rw對應(yīng)的數(shù)組中
  4. 將分類中的屬性方法協(xié)議添加到rw中
  5. 在運行時,不能動態(tài)的想類中添加成員變量凛澎,還有弱引用成員變量霹肝,和修改類名
  6. 因為運行時,我們的rw對ro進行了引用塑煎,ro的方法列表協(xié)議列表添加到了我們的rw對用的數(shù)組中沫换,所以就給我們在運行時對方法等做動態(tài)修改提供了可能。

很多人可能會有疑問最铁,runtime 不是提供了 動態(tài)添加成員變量的方法 class_addIvar() 讯赏,但是蘋果的官方文檔已經(jīng)有明確的說明

This function may only be called after objc_allocateClassPair and before objc_registerClassPair. Adding an instance variable to an existing class is not supported.

必須在alloc 和 register 之間調(diào)用垮兑,之前說過,程序編譯的時候就生成了成員變量布局漱挎,程序啟動后就沒有機會再添加成員變量了甥角,

因為我們的類實例是需要一塊內(nèi)存空間的,他有isa指針指向识樱,如果我們在運行時允許動態(tài)修改成員變量的布局嗤无,那么創(chuàng)建出來的類實例就屬于無效的了,能夠被所以修改怜庸,但是屬性和方法是我們 objc_class 可以管理的当犯,增刪改都不影響我們實例內(nèi)存布局。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末割疾,一起剝皮案震驚了整個濱河市嚎卫,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌宏榕,老刑警劉巖拓诸,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異麻昼,居然都是意外死亡奠支,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門抚芦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來倍谜,“玉大人,你說我怎么就攤上這事叉抡《蓿” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵褥民,是天一觀的道長季春。 經(jīng)常有香客問我,道長消返,這世上最難降的妖魔是什么载弄? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮侦副,結(jié)果婚禮上侦锯,老公的妹妹穿的比我還像新娘。我一直安慰自己秦驯,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布挣棕。 她就那樣靜靜地躺著译隘,像睡著了一般亲桥。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上固耘,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天题篷,我揣著相機與錄音,去河邊找鬼厅目。 笑死番枚,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的损敷。 我是一名探鬼主播葫笼,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼拗馒!你這毒婦竟也來了路星?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤诱桂,失蹤者是張志新(化名)和其女友劉穎洋丐,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體挥等,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡友绝,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了肝劲。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片九榔。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖涡相,靈堂內(nèi)的尸體忽然破棺而出哲泊,到底是詐尸還是另有隱情,我是刑警寧澤催蝗,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布切威,位于F島的核電站,受9級特大地震影響丙号,放射性物質(zhì)發(fā)生泄漏先朦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一犬缨、第九天 我趴在偏房一處隱蔽的房頂上張望喳魏。 院中可真熱鬧,春花似錦怀薛、人聲如沸刺彩。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽创倔。三九已至嗡害,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間畦攘,已是汗流浹背霸妹。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留知押,地道東北人叹螟。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像台盯,于是被迫代替她去往敵國和親罢绽。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容