RunTime源碼中的基本結(jié)構(gòu)體【類,對(duì)象,isa】

關(guān)于我的倉庫

  • 這篇文章是我為面試準(zhǔn)備的iOS基礎(chǔ)知識(shí)學(xué)習(xí)中的一篇
  • 我將準(zhǔn)備面試中找到的所有學(xué)習(xí)資料,寫的Demo,寫的博客都放在了這個(gè)倉庫里iOS-Engineer-Interview
  • 歡迎star????
  • 其中的博客在簡書,CSDN都有發(fā)布
  • 博客中提到的相關(guān)的代碼Demo可以在倉庫里相應(yīng)的文件夾里找到

前言

  • 本篇是解讀RunTime源碼中的第一篇文章,著重講述RunTime中的基本結(jié)構(gòu)體,了解類,對(duì)象中都有些什么東西

準(zhǔn)備工作

  • 請(qǐng)準(zhǔn)備好750.1版本的objc4源碼一份【目前最新的版本】,打開它,找到文章中提到的方法,類型,對(duì)象
  • 一切請(qǐng)以手中源碼為準(zhǔn),不要輕信任何人,任何文章,包括本篇博客
  • 源碼建議從Apple官方開源網(wǎng)站獲取obj4
  • 官網(wǎng)上下載下來需要自己配置才能編譯運(yùn)行,如果不想配置,可以在RuntimeSourceCode中clone

類與對(duì)象

對(duì)象

objc_object

  • 對(duì)象是一個(gè)objc_object類型的結(jié)構(gòu)體

補(bǔ)充知識(shí):OC類的本質(zhì)

  • OC語言在編譯期都會(huì)被編譯為C語言的Runtime代碼,二進(jìn)制執(zhí)行過程中執(zhí)行的都是C語言代碼。而OC的類本質(zhì)上都是結(jié)構(gòu)體,在編譯時(shí)都會(huì)以結(jié)構(gòu)體的形式被編譯到二進(jìn)制中。

找到它?

  • 我們先在源碼中找到objc_object在哪,于是你打開全局搜索,找到了這么一段
#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;
#endif
  • 于是你感覺里面就一個(gè)Class _Nonnull isa OBJC_ISA_AVAILABILITY;
  • 然而,請(qǐng)注意上面的#if !OBJC_TYPES_DEFINED,點(diǎn)進(jìn)去會(huì)發(fā)現(xiàn)該宏是1,也就是說。。。根本不走這個(gè)?。。?/li>
  • 真正的定義是在objc-private文件里【請(qǐng)著重關(guān)注這個(gè)文件,是個(gè)寶藏文件】
struct objc_object {
    // isa結(jié)構(gòu)體
private:
    isa_t isa;     //這里講到了isa的類型是isa_t

public:
  //省略方法
};
  • 先不看方法,發(fā)現(xiàn)這里面只有一個(gè)isa_t類型的isa;

isa_t

  • 點(diǎn)到isa_t里去【這次是唯一的,不會(huì)走錯(cuò)路了】,其定義為
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
};
  • 關(guān)于isa,會(huì)在下面仔細(xì)講解
  • 這里只要知道isa里存儲(chǔ)了對(duì)象所屬類的信息【Class cls】
  • 我們把目光投向Class cls這里

objc_class

  • 點(diǎn)到Class的定義里,此時(shí)你應(yīng)該學(xué)乖了,知道要找objc-private里的
  • 發(fā)現(xiàn)Class的定義為typedef struct objc_class *Class;
  • 再查看下objc_class的定義
struct objc_class : objc_object {
    Class superclass;
    cache_t cache;            
    class_data_bits_t bits;  
    //省略方法
};
  • 這個(gè)時(shí)候你應(yīng)該會(huì)發(fā)現(xiàn)一個(gè)特別特別特別神奇的事,objc_class竟然繼承于objc_object,換句話說,類的本質(zhì)也是個(gè)對(duì)象
  • objc_class中的的內(nèi)容【概念圖】
objc-isa-class-pointer
  • objc_class里一共三個(gè)東西,第一個(gè)Class superclass顯然是我們能理解的,是該類的父類
  • 好,此時(shí)我們思考一個(gè)問題,類也是一個(gè)對(duì)象,類也應(yīng)該有一個(gè)isa,可對(duì)象的isa存儲(chǔ)的是所屬類的信息,類的isa存儲(chǔ)的會(huì)是什么呢?

說明:isa指針

  • 學(xué)習(xí)過程中,會(huì)發(fā)現(xiàn)很多人將isa稱之為isa指針
  • 這個(gè)因?yàn)閕sa雖然是一個(gè)結(jié)構(gòu)體,但其中會(huì)存儲(chǔ)所屬類的地址【類也是有地址的,不在堆也不在棧,類似于單例】
  • 因此很多文章會(huì)說isa指針指向xxx,但請(qǐng)理解,isa不是一個(gè)指針,后面會(huì)講它是如何獲取地址的
  • 為方便講述,下面也開始使用isa指向xxx這種說法

引入概念:元類與根類

  • 元類【meta class】:類的所屬類,類的isa會(huì)指向其元類
  • 根類【root class】:根類,一般情況下就是指NSObject

isa閉環(huán)

  • 首先來看一張閉環(huán)圖
isa閉環(huán)指針圖
  • 圖里把繼承關(guān)系和isa指向關(guān)系講的很清楚,這里請(qǐng)注意兩點(diǎn)
    • Root class根類,它是繼承關(guān)系的頂點(diǎn),它不繼承與任何類
    • Root meta class根元類,它是isa指向的頂點(diǎn),其isa直接指向自己

補(bǔ)充知識(shí):類方法與實(shí)例方法的存儲(chǔ)

  • 假設(shè)我們每生成一個(gè)對(duì)象就要開辟空間存儲(chǔ)他的所有方法,顯然是對(duì)空間的浪費(fèi)
  • 因此使用對(duì)象的實(shí)例方法都是存放在所屬類當(dāng)中
  • 但是類方法怎么辦呢,因此有了元類,將類方法存儲(chǔ)在元類當(dāng)中,讓兩者的存儲(chǔ)統(tǒng)一起來,使得無論是類還是對(duì)象都能通過相同的機(jī)制查找方法的實(shí)現(xiàn)。
  • 類被定義為objc_class結(jié)構(gòu)體,objc_class結(jié)構(gòu)體繼承自objc_object,所以類也是對(duì)象。在應(yīng)用程序中,類對(duì)象只會(huì)被創(chuàng)建一份。在objc_class結(jié)構(gòu)體中定義了對(duì)象的method list、protocol、ivar list等,表示對(duì)象的行為。既然類是對(duì)象,那類對(duì)象也是其他類的實(shí)例。所以Runtime中設(shè)計(jì)出了meta class,通過meta class來創(chuàng)建類對(duì)象,所以類對(duì)象的isa指向?qū)?yīng)的meta class。而meta class也是一個(gè)對(duì)象,所有元類的isa都指向其根元類,根原類的isa指針指向自己。通過這種設(shè)計(jì),isa的整體結(jié)構(gòu)形成了一個(gè)閉環(huán)。

isa詳解

  • 下面來詳細(xì)講解isa即isa_t結(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
};

uintptr_t bits【unsigned long bits】

  • bits中存儲(chǔ)了所屬類地址等信息

union isa_t

  • 請(qǐng)注意,雖然前面將isa_t稱之為結(jié)構(gòu)體,但注意定義里將其定位的是union【共用體】
  • 也就是說,里面的isa_t、cls、 bits 還有結(jié)構(gòu)體共用同一塊地址空間。而 isa 總共會(huì)占據(jù) 64 位的內(nèi)存空間(決定于其中的結(jié)構(gòu)體)
  • 請(qǐng)牢記這一點(diǎn),不然后面會(huì)有困惑

結(jié)構(gòu)分析

  • 在isa_t內(nèi)部定義了一個(gè)結(jié)構(gòu)體,里面只有一個(gè)宏ISA_BITFIELD
  • 查看里面的內(nèi)容
struct {
        ISA_BITFIELD;  // defined in isa.h
    };
    

//ISA_BITFIELD內(nèi)容以及注解
# elif __x86_64__
#   define ISA_MASK        0x00007ffffffffff8ULL
#   define ISA_MAGIC_MASK  0x001f800000000001ULL
#   define ISA_MAGIC_VALUE 0x001d800000000001ULL
#   define ISA_BITFIELD                                   
      uintptr_t nonpointer        : 1;  //說明是不是指針【這是一個(gè)對(duì)象還是一個(gè)類,下文會(huì)繼續(xù)介紹】
      uintptr_t has_assoc         : 1;  // 是否曾經(jīng)或正在被關(guān)聯(lián)引用,如果沒有,可以快速釋放內(nèi)存             
      uintptr_t has_cxx_dtor      : 1;   // 這個(gè)字段存儲(chǔ)類是否有c++析構(gòu)器
      uintptr_t shiftcls          : 44;    //存儲(chǔ)cls類地址 /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ 
      uintptr_t magic             : 6;           //校驗(yàn)          
      uintptr_t weakly_referenced : 1;          // 對(duì)象是否曾經(jīng)或正在被弱引用,如果沒有,可以快速釋放內(nèi)存           
      uintptr_t deallocating      : 1;         // 對(duì)象是否正在釋放內(nèi)存            
      uintptr_t has_sidetable_rc  : 1;             // 是否需要散列表存儲(chǔ)引用計(jì)數(shù)。當(dāng)extra_rc存儲(chǔ)不下時(shí),該值為1        
      uintptr_t extra_rc          : 8       // 引用計(jì)數(shù)數(shù)量,實(shí)際的引用計(jì)數(shù)減一
#   define RC_ONE   (1ULL<<56)
#   define RC_HALF  (1ULL<<7)

補(bǔ)充知識(shí):arm 64與x86_64

  • 這里都是處理器型號(hào)
  • iOS5之后,CPU數(shù)據(jù)吞吐量為64bit(64個(gè)二進(jìn)制位,表示8個(gè)字節(jié)),相較于32位處理器效率提升了一倍,此時(shí)對(duì)應(yīng)寄存器也變成了64位,可以處理更大的數(shù)據(jù)顯示更多的狀態(tài)。
  • i386是針對(duì)intel通用微處理器32位處理器,x86_64是針對(duì)x86架構(gòu)的64位處理器
  • 模擬器32位處理器測(cè)試需要i386架構(gòu),模擬器64位處理器測(cè)試需要x86_64架構(gòu),真機(jī)32位處理器需要armv7,或者armv7s架構(gòu),真機(jī)64位處理器需要arm64架構(gòu)。
  • 這里就以x86_64為例

各個(gè)部分存儲(chǔ)空間

  • 就是ISA_BITFIELD這里的內(nèi)容存儲(chǔ)了該類的信息
123俄
  • 下面我們看一下具體的存儲(chǔ)地址
5566

isa_t的初始化

initIsa

inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{
    assert(!isTaggedPointer()); 
    if (!nonpointer) {
        isa.cls = cls;
    } else {
        assert(!DisableNonpointerIsa);
        assert(!cls->instancesRequireRawIsa());
        isa_t newisa(0);

#if SUPPORT_INDEXED_ISA
        assert(cls->classArrayIndex() > 0);
        newisa.bits = ISA_INDEX_MAGIC_VALUE;
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
        newisa.bits = ISA_MAGIC_VALUE;  //0x001d800000000001ULL
        newisa.has_cxx_dtor = hasCxxDtor;
        newisa.shiftcls = (uintptr_t)cls >> 3;
#endif
        isa = newisa;
    }
}
  • 看到這么大的一段代碼先不要慌,先從三個(gè)參數(shù)開始

補(bǔ)充知識(shí):assert()函數(shù)

  • 其作用是如果它的條件返回錯(cuò)誤,則終止程序執(zhí)行。
  • assert的作用是先計(jì)算表達(dá)式 expression ,如果其值為假(即為0),那么它先向stderr打印一條出錯(cuò)信息,然后通過調(diào)用 abort 來終止程序運(yùn)行。

三個(gè)參數(shù)【Class cls】【bool nonpointer】【bool hasCxxDtor】

  • cls就是其存儲(chǔ)的類
  • nonpointer這個(gè)我們看initInstanceIsa
inline void 
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
    assert(!cls->instancesRequireRawIsa());
    assert(hasCxxDtor == cls->hasCxxDtor());

    initIsa(cls, true, hasCxxDtor);
}
  • 對(duì)于實(shí)例對(duì)象,傳入的nonpointer是true【翻譯一下就是確實(shí)不是一個(gè)指針,有其他內(nèi)容】
  • 我們?cè)诜祷厣厦娴膇nitIsa
inline void 
objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) 
{
    assert(!isTaggedPointer()); 
    if (!nonpointer) {
        isa.cls = cls;
    } else {
                //省略代碼
    }
}
  • 也就是說如果傳入的是false【不是不是指針,是指針】也就是說是在直接訪問類,那么就直接返回cls內(nèi)容就行
  • 回憶下上面說的isa_t是一個(gè)共用體,給cls賦值,也就意味著在結(jié)構(gòu)體里面填完了內(nèi)容

補(bǔ)充知識(shí):ULL

  • ULL:unsiged long long 64bit
  • 在16進(jìn)制數(shù)據(jù)后面加上數(shù)據(jù)類型限制,因?yàn)槟J(rèn)的數(shù)據(jù)類型是int,加數(shù)據(jù)類型是為了防止數(shù)據(jù)越界。32和64位下long long int總是64位的, long int總是32位的。

真正初始化

  • 真正初始化的代碼就只有幾行
isa_t newisa(0);
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.shiftcls = (uintptr_t)cls >> 3;
isa = newisa;
  • 這個(gè)newisa就是初始化出來的結(jié)果
  • 我們要好好理解的是newisa.bits = ISA_MAGIC_VALUE;
  • 首先ISA_MAGIC_VALUE的值為0x001d800000000001ULL,將它換成二進(jìn)制數(shù)11101100000000000000000000000000000000000000000000001【記住前面的高低位】,輸入到bits中【再強(qiáng)調(diào)一遍isa_t是一個(gè)共用體,輸入到bits里就等于到了結(jié)構(gòu)體里】
  • 結(jié)果如下圖【將indexed改成nonpointer】
000
  • 因此初始化的時(shí)候只設(shè)置了nonpointer,magic兩個(gè)部分
  • magic 的值為 0x3b 用于調(diào)試器判斷當(dāng)前對(duì)象是真的對(duì)象還是沒有初始化的空間
  • 在設(shè)置 indexedmagic 值之后,會(huì)設(shè)置 isahas_cxx_dtor,這一位表示當(dāng)前對(duì)象有 C++ 或者 ObjC 的析構(gòu)器(destructor),如果沒有析構(gòu)器就會(huì)快速釋放內(nèi)存。
小心心
  • newisa.shiftcls = (uintptr_t)cls >> 3這句是為了將 Class 指針中無用的后三位清除減小內(nèi)存的消耗,因?yàn)轭惖闹羔樢凑兆止?jié)(8 bits)對(duì)齊內(nèi)存,其指針后三位都是沒有意義的 0。這里首先一個(gè)OC的指針長度是47,而這里的shiftcls占44個(gè)字節(jié),本來

isa諸多用處

獲取cls地址:ISA() 方法

  • 由于現(xiàn)在isa不在只存放地址了,還多了很多附加內(nèi)容,因此需要一個(gè)專門的方法獲取shiftcls中的內(nèi)容
#define ISA_MASK 0x00007ffffffffff8ULL
inline Class 
objc_object::ISA() 
{
    return (Class)(isa.bits & ISA_MASK);
}
  • 進(jìn)行&操作獲取地址

class方法

  • 進(jìn)入源碼以后,可以查看很多內(nèi)容的源碼
+ (Class)class {
    return self;
}

- (Class)class {
    return object_getClass(self);
}

Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}
  • class既是類方法又是實(shí)例方法,類方法直接返回自身,實(shí)例方法返回的就是isa中的內(nèi)容

isMemberOfClass&&isKindOfClass

+ (BOOL)isMemberOfClass:(Class)cls {
    return object_getClass((id)self) == cls;
}

- (BOOL)isMemberOfClass:(Class)cls {
    return [self class] == cls;
}

+ (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = object_getClass((id)self); tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}

- (BOOL)isKindOfClass:(Class)cls {
    for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {
        if (tcls == cls) return YES;
    }
    return NO;
}
  • 這也沒啥好解釋的了,結(jié)合class的內(nèi)容應(yīng)該很好理解了

objc_class擴(kuò)充:bits

  • 回顧下objc_class的內(nèi)容
struct objc_class : objc_object {
    Class superclass;
    cache_t cache;            
    class_data_bits_t bits;  
    //省略方法
};
  • 其中bits是絕對(duì)的主角,里面存儲(chǔ)了類的方法列表等等的信息
  • 看下class_data_bits_t里的內(nèi)容,發(fā)現(xiàn)就一個(gè)uintptr_t bits
  • 看到這很容易聯(lián)想到和前面isa里的bits,事實(shí)上,兩者確實(shí)很想,這也是我把它放在后面來講的原因之一

class_rw_t與class_ro_t

  • bits中最重要的部分就是class_rw_t,class_ro_t兩兄弟
  • 在哪里找到他們呢?返回objc_class的定義,發(fā)現(xiàn)其中有一個(gè)data函數(shù)
 class_rw_t *data() {
        return bits.data();
    }
    
//data()
 class_rw_t* data() {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }

// class_rw_t定義
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;
    // 協(xié)議信息
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;
  //省略方法
};
讓我
  • 就如同注釋里說明的,其中有方法,屬性,協(xié)議等等信息【后面在信息通訊等內(nèi)容里我們會(huì)在仔細(xì)研究這一塊】
  • 但這里有一個(gè)我們不太懂的const class_ro_t是個(gè)啥?
struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;

    const uint8_t * ivarLayout;
    
    const char * name;
    // 方法列表
    method_list_t * baseMethodList;
    // 協(xié)議列表
    protocol_list_t * baseProtocols;
    // 變量列表
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    // 屬性列表
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};
  • 可以看到兩者內(nèi)容基本一樣,就是class_ro_t里的協(xié)議,方法等前面多了個(gè)base

  • 這里先注意中class_rw_t定義的class_ro_t是帶const修飾符的,是不可變的

  • 下面我們通過class的初始化來研究下這兩個(gè)都是這么運(yùn)作的

realizeClass方法

//精取代碼
static Class realizeClass(Class cls)
{
    const class_ro_t *ro;
    class_rw_t *rw;
    Class supercls;
    Class metacls;
    bool isMeta;
    ro = (const class_ro_t *)cls->data(); //編譯期間,cls->data指向的是class_ro_t結(jié)構(gòu)體。cls->data()存放的是即ro中存儲(chǔ)的是編譯時(shí)就已經(jīng)決定的原數(shù)據(jù)
    rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);   //初始化rw
    rw->ro = ro;    //rw中的ro賦值
    rw->flags = RW_REALIZED|RW_REALIZING;
    cls->setData(rw);
    rw->version = isMeta ? 7 : 0; 
    cls->chooseClassArrayIndex();
    methodizeClass(cls);    //運(yùn)行期間,將ro中的內(nèi)容賦值給rw
    return cls;
}

補(bǔ)充知識(shí):addRootClass和addSubclass

static void addSubclass(Class supercls, Class subcls)
{
    runtimeLock.assertLocked();
    if (supercls  &&  subcls) {
        assert(supercls->isRealized());
        assert(subcls->isRealized());
        subcls->data()->nextSiblingClass = supercls->data()->firstSubclass;
        supercls->data()->firstSubclass = subcls;

        if (supercls->hasCxxCtor()) {
            subcls->setHasCxxCtor();
        }

        if (supercls->hasCxxDtor()) {
            subcls->setHasCxxDtor();
        }

        if (supercls->hasCustomRR()) {
            subcls->setHasCustomRR(true);
        }

        if (supercls->hasCustomAWZ()) {
            subcls->setHasCustomAWZ(true);
        }

        // Special case: instancesRequireRawIsa does not propagate 
        // from root class to root metaclass
        if (supercls->instancesRequireRawIsa()  &&  supercls->superclass) {
            subcls->setInstancesRequireRawIsa(true);
        }
    }
}

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

    assert(cls->isRealized());
    cls->data()->nextSiblingClass = _firstRealizedClass;
    _firstRealizedClass = cls;
}
  • 這兩個(gè)方法的職責(zé)是將某個(gè)類的子類串成一個(gè)列表,大致是superClass.firstSubclass -> subClass1.nextSiblingClass -> subClass2.nextSiblingClass -> ...
  • 而存儲(chǔ)這些的同樣是data,也就是說、以通過class_rw_t,獲取到當(dāng)前類的所有子類

methodizeClass方法

//精取代碼
// 設(shè)置類的方法列表、協(xié)議列表、屬性列表,包括category的方法
static void methodizeClass(Class cls)
{
    runtimeLock.assertLocked();

    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();
    auto ro = rw->ro;
    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) {
    //添加協(xié)議
        rw->protocols.attachLists(&protolist, 1);
    }
    //添加協(xié)議【略】
}
  • 也就是說是在methodizeClass中,將rw的內(nèi)容填充完

流程總結(jié)

  • 編寫代碼運(yùn)行后,開始類的方法,成員變量 屬性 協(xié)議等信息都存放在 const class_ro_t中,運(yùn)行過程中,會(huì)將信息整合,動(dòng)態(tài)創(chuàng)建 class_rw_t,然后會(huì)將 class_ro_t中的內(nèi)容(類的原始信息:方法 屬性 成員變量 協(xié)議信息) 和 分類的方法 屬性 協(xié)議 成員變量的信息 存儲(chǔ)到 class_rw_t中.并通過數(shù)組進(jìn)行排序,分類方法放在數(shù)組的前端,原類信息放在數(shù)組的后端.運(yùn)行初始 objc-class中的 bits是指向 class_ro_t的,bits中的data取值是從class_ro_t中獲得,而后創(chuàng)建 class_rw_t,class_rw_t中的 class_ro_t從初始的 class_ro_t中取值,class_rw_t初始化完成后,修改 objc_class中的bits指針指向class_rw_t
  • 所以ro中存儲(chǔ)的是編譯時(shí)就已經(jīng)決定的原數(shù)據(jù),rw才是運(yùn)行時(shí)動(dòng)態(tài)修改的數(shù)據(jù)。
rw與ro

(class_rw_t *)(bits & FAST_DATA_MASK)

  • 這句和isa里的非常像,想必應(yīng)該看了很有感覺了
  • FAST_DATA_MASK0x00007ffffffffff8
  • 這句取出bits中47-3的位置,就是class_rw_t

2019.7.26更新

對(duì)于class_rw_t 以及class_ro_t進(jìn)一步理解

  • 這里其實(shí)從名字上就可以理解這兩個(gè)結(jié)構(gòu)體,rw是指readwrite【可讀寫】,ro是指readonly【只讀】,因此ro是在編譯階段決定的,無法修改,rw是在運(yùn)行時(shí)可以進(jìn)行添加的
  • 這里可以先補(bǔ)充下方法的添加流程,我們知道類的擴(kuò)充可以用extension,而extension中的東西也是添加在ro中,在編譯階段決定的;而category是在運(yùn)行期決定的,添加在rw中
  • 這也解釋上面途中,為什么ro中的methods只要一個(gè)一維數(shù)組就行【因?yàn)槔锩嬷灰4姹绢惖姆椒?,協(xié)議,屬性同理】,rw中就得是個(gè)二維數(shù)組,里面存放著本類以及category中添加的內(nèi)容,具體是怎么操作的到后面分析category中再說

關(guān)于cache_t

  • cache_t中存儲(chǔ)著方法的緩存,通過一個(gè)散列表來存儲(chǔ),方便查找【這里不要怕這個(gè)散列,后面很多內(nèi)容都是散列的】
cache
  • 這里要注意,我們把sel【函數(shù)名】來進(jìn)行hash,查找
  • 同時(shí)存放時(shí)又把sel,與imp都存放了

方法調(diào)用流程

CHPerson *person = [[CHPerson alloc] init];
[person test]
  1. 首先通過isa找到person對(duì)應(yīng)的CHPerson類,先查看其中的cache_t里的緩存方法
  2. 如果找不到該test方法,就去查看bits中的rw的methods查找方法【如果找到了,調(diào)用,且添加到cache_t中】
  3. 如果在CHPerson里找不到,會(huì)查看CHPerson的父類,同樣是先cache_t,后bits,如果查找到了,是存放在自己的cache里,不是父類的【注意!】
  4. 最后一直查看父類會(huì)到達(dá)NSObject根類,如果依然找不到就拋出異常

2019.8.3更新【ivar】

  • 想不到這篇文章越寫越長,也不想重新開博客了,就越寫越多
  • 這部分主要涉及了property和ivar以及Non Fragile ivars

property和ivar

  • 在剛學(xué)習(xí)OC的時(shí)候我們就了解過了屬性與實(shí)例變量的區(qū)別
  • property = ivar + get + set
  • 下面我們探討兩者真正的關(guān)系

ivar

  • 在class_ro_t結(jié)構(gòu)體中,有const ivar_list_t * ivars;
  • 這里存儲(chǔ)著所有ivar【實(shí)例變量】
//ivar_t
struct ivar_t {
    int32_t *offset;
    const char *name;
    const char *type;
    uint32_t alignment_raw;
    uint32_t size;
};
  • 當(dāng)我們編譯的時(shí)候會(huì)決定ro結(jié)構(gòu)體,也就是會(huì)將變量確定下來

property

  • 在ro,rw中都會(huì)有屬性列表,查看property_t結(jié)構(gòu)體
struct property_t {
    const char *name;
    const char *attributes;
};
  • 這里我們可以看到存儲(chǔ)一個(gè)屬性的時(shí)候,我們只存儲(chǔ)了其name和屬性關(guān)鍵字,其余信息都不知道

兩者關(guān)系

  • 在編譯時(shí),對(duì)于我們的屬性,其實(shí)會(huì)做兩次添加,假設(shè)我們寫了一個(gè)nameLabel屬性,第一次是在屬性列表里添加,內(nèi)容為nameLabel,之后會(huì)在ivars中同步添加,加入名為_nameLabel的變量
  • 因此在真正訪問的時(shí)候,其實(shí)真正訪問到的還是變量
  • 與此同時(shí),會(huì)把屬性自動(dòng)生成的set,get方法寫入方法列表中
  • 這就是為什么我們剛學(xué)OC的時(shí)候有這么一句話:property = ivar + get + set

Non Fragile ivars

  • Non Fragile ivars是Apple在OC2.0時(shí)推出的新特性
  • 什么意思呢?我們現(xiàn)在有一個(gè)類A,在A里面的ivars中有兩個(gè)ivar【a, b】,現(xiàn)在我們寫了一個(gè)類B繼承于A,在B中我們寫了兩個(gè)ivar【c,d】
  • 我們假設(shè)A是頂點(diǎn),不繼承于任何類了
  • B里面會(huì)只有兩個(gè)ivar嗎,只要有些許OC基礎(chǔ)知識(shí)的人都會(huì)知道,不會(huì),ab兩個(gè)ivar也會(huì)跟著在列表里
  • 下面是圖解:
img

遇到問題了

  • 看起來非常合理,但其實(shí)存在一個(gè)問題
  • 我們知道內(nèi)存的分配是在編譯器決定的,假如現(xiàn)在我們?cè)贏里面又加了兩個(gè)ivar【e,f】,在沒有重新分配內(nèi)存的情況下就會(huì)出現(xiàn)B中我們自己添加的cd兩個(gè)ivar正好疊在了父類的ef兩個(gè)新的ivar上,當(dāng)我們根據(jù)內(nèi)存地址去訪問的時(shí)候就會(huì)出現(xiàn)錯(cuò)誤
img
  • 當(dāng)然,這時(shí)候你肯定會(huì)說fnndp,修改完A后進(jìn)行編譯運(yùn)行肯定會(huì)重新分配內(nèi)存,那會(huì)出現(xiàn)這么愚蠢的情況?
  • 可問題是,OC作為一門動(dòng)態(tài)語言,對(duì)于靜態(tài)庫和蘋果的官方庫只是會(huì)進(jìn)行加載
  • 在這個(gè)問題沒有解決的時(shí)代,只要靜態(tài)庫更新下,所有使用該靜態(tài)庫的app都得下架重新編譯
  • 于是我們的新特性Non Fragile ivars就閃亮登場(chǎng)了

動(dòng)態(tài)偏移

  • runtime在加載MyObject類的時(shí)候(注:runtime加載類是在main函數(shù)跑起來之前),會(huì)計(jì)算基類的大小.runtime在運(yùn)行期判斷子類的instanceStart大小和父類instanceSize大小(關(guān)于這兩個(gè)成員請(qǐng)看文章開頭展示的結(jié)構(gòu)體內(nèi)容),如果子類的instanceStart小于父類的instanceSize,說明父類新增了成員變量,子類的成員變量需要進(jìn)行偏移.
  • 在上圖的例子中,當(dāng)MyObjectinstanceStart小于NSObjectinstanceSize,MyObject在編譯器確定的結(jié)構(gòu)體將會(huì)動(dòng)態(tài)調(diào)整成員變量偏移,因此程序無需重新編譯,就能在新版本的系統(tǒng)上運(yùn)行.
img
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容