iOS底層原理:類&類的結(jié)構(gòu)分析

在分析類的結(jié)構(gòu)之前,我們需要先搞清楚類的內(nèi)存分布情況。

類的內(nèi)存分布

首先創(chuàng)建一個(gè)Person類:

// Person 類
@interface Person : NSObject

- (void)sayHello;
+ (void)sayHi;

@end

@implementation Person

- (void)sayHello {
    NSLog(@"Say Hello");
}

+ (void)sayHi {
    NSLog(@"Say Hi");
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        Person *person = [[Person alloc] init];
        
        NSLog(@"%@",person);
    }
    return 0;
}
類的lldb調(diào)試分析

通過上圖中可以看出 0x00000001000012e80x00000001000012c0 為什么打印出的結(jié)果都是 Person 呢?這個(gè)東西其實(shí)就是元類。

那么什么是元類呢?

元類

每一個(gè)對象都對應(yīng)一個(gè)類。Person 類就是 person 對象的類,換句話說就是 person 對象的isa指針指向 Person 對應(yīng)的結(jié)構(gòu)體;[Person class] 也是對象,描述它的類就是元類,換句話說[Person class] 對象的isa指向的就是元類

元類的定義和創(chuàng)建都是由編譯器自動(dòng)完成。

元類保存了類方法的列表。當(dāng)一個(gè)類方法被調(diào)用時(shí),元類會(huì)首先查找它本身是否有該類的方法的實(shí)現(xiàn),如果沒有則該類會(huì)向它的父類查找該方法,知道一直找到繼承鏈的頭。

元類分析
元類跟蹤

從上圖中我們可以看到,我們通過isa可以一直找到NSObject類。通過 person對象的isa指針可以找到Person類,通過Person的isa指針可以找到元類Person,而通過元類Person的isa指針又可以找元類Person的元類NSObject,通過元類NSObject的isa指針找到的還是NSObject,其實(shí)這個(gè)時(shí)候的NSObject叫做根元類

但是NSObject的類和 NSObject.class 的指針地址不一樣呢?是NSObject類會(huì)在內(nèi)存中存在多份嗎?

我們通過如下代碼打印來看下:

void getClassInfo() {
    Class class1 = [Person class];
    Class class2 = [Person alloc].class;
    Class class3 = object_getClass([Person alloc]);
    
    NSLog(@"\n%p\n%p\n%p\n",class1,class2,class3);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        getClassInfo();
    }
    return 0;
}

輸出結(jié)果如下:

2020-09-15 20:49:21.406345+0800 ObjectAnalysis[34085:811450] 
0x1000012f0
0x1000012f0
0x1000012f0
Program ended with exit code: 0

可以看出其實(shí)類對象在整個(gè)內(nèi)存中只有一份。

因此我們可以得出一個(gè)著名的isa的走位圖。

isa的走位圖&繼承圖

走位圖

isa查找流程

  • 實(shí)例對象的isa指針類對象
  • 類對象的isa指向元類對象
  • 元類對象的isa指向根元類
  • 根元類的isa指向它自己本身,從而形成了閉環(huán)

繼承關(guān)系

類對象的繼承關(guān)系

  • 繼承于它的父類
  • 父類繼承它的父類
    ...
  • 直到找到根類,也就是NSObject
  • NSObject 則繼承于nil,這也就是所有的根源,即無中生有

元類的繼承關(guān)系

  • 子類的元類繼承與 父類的元類
  • 父類的元類繼承它的父類的元類
    ...
  • 直到找到根元類
  • 根元類則是繼承于NSObject

tips:實(shí)例對象是沒有繼承關(guān)系的,只有才有繼承關(guān)系

對象的本質(zhì)

對象的本質(zhì)其實(shí)就是結(jié)構(gòu)體,而編譯到底層都是繼承自objc_class的結(jié)構(gòu)體

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() const {
        return bits.data();
    }

    // 代碼過多,自動(dòng)省略
    ...
};

// objc_object
struct objc_object {
private:
    isa_t isa;

public:

    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();

    // rawISA() assumes this is NOT a tagged pointer object or a non pointer ISA
    Class rawISA();

    // getIsa() allows this to be a tagged pointer object
    Class getIsa();

    // 代碼過多,自動(dòng)省略
    ...
};

通過源碼可以看出 objc_class 其實(shí)就是繼承自 objc_object。

所以我們可以看出所有的 對象、元類都有isa指針,是因?yàn)槎祭^承自 objc_object,而 objc_object中有個(gè)私有變量isa。

objc_object、objc_class、isa、object、NSObject的關(guān)系如下圖所示:

關(guān)系圖

我們接下來重點(diǎn)就是分析類的結(jié)構(gòu)了

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

再開始前,先分析下objc_class的結(jié)構(gòu)圖

objc_class

  • isa 是指向元類的指針
  • superclass 是指向當(dāng)前類的父類
  • cache 是用于緩存方法的,用于加速方法的調(diào)用
  • bits 是存儲(chǔ)類的方法、屬性、協(xié)議等信息的地方

一般我們獲取到bits,然后才能獲取到我們需要的信息,那么我們就需要進(jìn)行32位指針的偏移,為什么是32位指針的偏移,請看下面的代碼段分析:

struct objc_class : objc_object {
    // Class ISA;  // 指針占 8 字節(jié)
    Class superclass; // 指針占 8 字節(jié)
    cache_t cache;   // 占 16 字節(jié),具體分析,看cache_t 結(jié)構(gòu)體的分析
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

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

    // 代碼過多,自動(dòng)省略
    ...
};

// mask_t 的定義
#if __LP64__
typedef uint32_t mask_t;  // x86_64 & arm64 asm are less efficient with 16-bits
#else
typedef uint16_t mask_t;
#endif

// 結(jié)構(gòu)體指針字節(jié)大小,看里面的成員變量,而大部分都是 static 和方法都不存在結(jié)構(gòu)體里面
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    explicit_atomic<struct bucket_t *> _buckets;  // 結(jié)構(gòu)體指針 8 字節(jié)
    explicit_atomic<mask_t> _mask;  // uint32_t 占 4 字節(jié)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic<uintptr_t> _maskAndBuckets;
    mask_t _mask_unused;
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    explicit_atomic<uintptr_t> _maskAndBuckets;
    mask_t _mask_unused;
#if __LP64__
    uint16_t _flags; // 占 2 字節(jié)
#endif
    uint16_t _occupied; // 占 2 字節(jié)

    // 方法代碼過多,自動(dòng)省略
    ...
};

class_data_bits_t

objc_class結(jié)構(gòu)體中的注釋中已經(jīng)寫到class_data_bits_t相當(dāng)于class_rw_t指針加上rr/alloc的標(biāo)志。

class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

而在class_data_bits_t結(jié)構(gòu)體中為我們提供了一個(gè)用于返回其中的class_rw_t *的方法

struct class_data_bits_t {
    friend objc_class;

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

     // 代碼過多,自動(dòng)省略
    ...
public:

    class_rw_t* data() const {
        return (class_rw_t *)(bits & FAST_DATA_MASK);
    }
    // 代碼過多,自動(dòng)省略
    ...
};

class_rw_t

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint16_t witness;
#if SUPPORT_INDEXED_ISA
    uint16_t index;
#endif

    explicit_atomic<uintptr_t> ro_or_rw_ext;

    Class firstSubclass;
    Class nextSiblingClass;

    // 省略過多的代碼  
    ...

    const method_array_t methods() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->methods;
        } else {
            return method_array_t{v.get<const class_ro_t *>()->baseMethods()};
        }
    }

    const property_array_t properties() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->properties;
        } else {
            return property_array_t{v.get<const class_ro_t *>()->baseProperties};
        }
    }

    const protocol_array_t protocols() const {
        auto v = get_ro_or_rwe();
        if (v.is<class_rw_ext_t *>()) {
            return v.get<class_rw_ext_t *>()->protocols;
        } else {
            return protocol_array_t{v.get<const class_ro_t *>()->baseProtocols};
        }
    }
};

可以看出,我們可以通過class_rw_t結(jié)構(gòu)體中提供的methods()、properties()、protocols()獲取到對應(yīng)的方法、屬性和協(xié)議。

那么接下來我們通過lldb調(diào)試來查找對應(yīng)的class_data_bist_t bits,查看相應(yīng)的信息。

class_rw_t

當(dāng)我們獲取到class_rw_t結(jié)構(gòu)體信息后,接下來想要獲取到方法、屬性或者協(xié)議,就需要通過調(diào)用其提供的方法來進(jìn)行獲取。

屬性列表 properties()

屬性信息
  • 1、通過properties()可以獲取到對應(yīng)的屬性列表
  • 2、通過get方法訪問第一個(gè)屬性,可以發(fā)現(xiàn)是我們定義的name屬性

【error】但是嘗試去獲取第二個(gè)屬性的時(shí)候,發(fā)現(xiàn)數(shù)組越界了,這是為什么呢?我們不是還定義了一個(gè)hobby嗎?

由此引出成員變量、實(shí)例變量屬性的區(qū)別

成員變量/實(shí)例變量/屬性

成員變量: Member Variable declarations
類: Class (description/template for an object)
實(shí)例: Instance (manifestation of a class)
消息: Message (sent to object to make it act)
方法: Method (code invoked by a Message)
實(shí)例變量: Instance Variable (object-specific storage)
超類/子類: Superclass/Subclass (Inheritance)
協(xié)議:  Protocol (non-class-specific methods)

從上面的解釋,可以看出,實(shí)例是針對而言的,。實(shí)例是指類的聲明,由此推理,實(shí)例變量是指由類聲明的對象。

而屬性和成員變量的區(qū)別在于有無setter、getter方法。

方法列表 methods()

我們還是先通過lldb調(diào)試來查找方法列表。

methods

通過上圖的lldb調(diào)試過程,我們可以發(fā)現(xiàn):

  • method_list_t中有方法的總個(gè)數(shù) count=4
  • 會(huì)優(yōu)先存儲(chǔ)我們定義的實(shí)例方法
  • 會(huì)有一個(gè)隱藏的析構(gòu)函數(shù) .cxx_destruct,實(shí)現(xiàn)ARC下自動(dòng)釋放內(nèi)存的工作
  • setter、getter方法的存儲(chǔ)

其實(shí)探索到這里,發(fā)現(xiàn)我們還遺留了兩個(gè)問題:
1、成員變量的存儲(chǔ)
2、類方法的存儲(chǔ)

成員變量的存儲(chǔ)

其實(shí)通過objc_class的源碼,我們可以知道里面其實(shí)還有個(gè)ro方法,會(huì)返回一個(gè)class_ro_t的結(jié)構(gòu)體的信息。打印信息如下:

ivars

class_ro_t結(jié)構(gòu)體中其實(shí)有很多的成員變量和方法,其實(shí)class_rw_t中的ro,在類的編譯期就已經(jīng)確定了屬性、方法以及要遵循的協(xié)議,objc_class中的bits中的data部分存放了ro信息。而在runtime運(yùn)行之后,準(zhǔn)確的說是在運(yùn)行runtimerealizeClass方法時(shí),會(huì)生成class_rw_t結(jié)構(gòu)體,該結(jié)構(gòu)體包含了class_ro_t,并且更新了data部分。換成了class_rw_t結(jié)構(gòu)體的地址。

由上圖的llbd打印出的ivars信息中,可以看到,我們生命的屬性,也會(huì)在ivars中生成一個(gè) _name的成員變量。這也就是我們?yōu)槭裁纯梢酝ㄟ^ _nameself.name來訪問屬性的原因了。

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;

    // This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
    _objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];

    _objc_swiftMetadataInitializer swiftMetadataInitializer() const {
        if (flags & RO_HAS_SWIFT_INITIALIZER) {
            return _swiftMetadataInitializer_NEVER_USE[0];
        } else {
            return nil;
        }
    }

    method_list_t *baseMethods() const {
        return baseMethodList;
    }

    class_ro_t *duplicate() const {
        if (flags & RO_HAS_SWIFT_INITIALIZER) {
            size_t size = sizeof(*this) + sizeof(_swiftMetadataInitializer_NEVER_USE[0]);
            class_ro_t *ro = (class_ro_t *)memdup(this, size);
            ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0];
            return ro;
        } else {
            size_t size = sizeof(*this);
            class_ro_t *ro = (class_ro_t *)memdup(this, size);
            return ro;
        }
    }
};

類方法的存儲(chǔ)

其實(shí)我們從上面的isa走位圖中就可以知道一點(diǎn),所有的實(shí)例對象的isa都是指向其元類的,那么我們是否可以查看其元類中的方法列表。下面我們通過lldb調(diào)試來看一下。

類方法

所以可以看出對象中的類方法都是存儲(chǔ)在其元類中的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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