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

在前一篇文章中,我們已經(jīng)探討了iOS底層原理--isa與類關(guān)聯(lián)的原理,isa包含了Class類,從而將isaClass類進(jìn)行了關(guān)聯(lián)。
那么,我們現(xiàn)在來(lái)看下類的結(jié)構(gòu)分析

首先我們還是看一段代碼

//
//  main.m
//  KCObjc
//
//  Created by Cooci on 2020/7/24.
//

#import <Foundation/Foundation.h>
#import "LGPerson.h"
#import <objc/runtime.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        // objc_alloc VS alloc
        // NSObject LGPerson
        // NSObject 根類
        // sel 處理 llvm
        
        //   define ISA_MASK        0x00007ffffffffff8ULL
        NSObject *objc1 = [NSObject alloc];
        LGPerson *objc2 = [LGPerson alloc];
        // LGPerson *objc3 = [LGPerson alloc];

        NSLog(@"Hello, World!  %@",objc2);
    }
    return 0;
}
  • 我們將斷點(diǎn)打到objc2處,然后查看objc2的內(nèi)存情況。
    image.png

x/4gx表示查看objc2的內(nèi)存情況

  • 通過上面的輸出,我們看到了內(nèi)存,然后我們知道,內(nèi)存中第一位代表了對(duì)象的isa指針的地址,所以0x001d800100002205這個(gè)表示isa指針的地址。
  • 通過iOS底層原理--isa與類關(guān)聯(lián)的原理這篇文章,我們將isaISA_MASK進(jìn)行與運(yùn)算可以得的isa指針指向的類的地址,我們進(jìn)行驗(yàn)證。

ISA_MASKx86_64中的地址0x00007ffffffffff8ULL

LGPerson.png
  • 通過上面的圖片我們可以看到,經(jīng)過與運(yùn)算后,我們得出了最后得到的地址中存放了LGPerson,至此我們可以得到結(jié)論。對(duì)象的isa指針指向了類。

  • 然后我們?cè)賮?lái)一步,將類的isa拿出來(lái)和ISA_MASK進(jìn)行與運(yùn)算,得到什么呢?我們驗(yàn)證一下。

    LGPerson.png

  • 看到輸出結(jié)果,顯示還是LGPerson,為什么呢?這就是所謂的元類。

元類(Meta Class)

  • 我們都知道對(duì)象的isa是指向,的其實(shí)也是一個(gè)對(duì)象,可以稱為類對(duì)象,其isa的位域指向蘋果定義的元類

  • 元類是系統(tǒng)給的,其定義和創(chuàng)建都是由編譯器自動(dòng)完成,在這個(gè)過程中,類的歸屬來(lái)自于元類

  • 元類是類對(duì)象的類,每個(gè)類都有一個(gè)獨(dú)一無(wú)二的元類用來(lái)存儲(chǔ) 類方法的相關(guān)信息。

  • 元類本身是沒有名稱的,由于與類相關(guān)聯(lián),所以使用了同類名一樣的名稱

上面這段話摘自Style_月月的文章iOS-底層原理 08:類 & 類結(jié)構(gòu)分析,大家也可以去看看她的其他文章,都寫的非常詳細(xì)。

那么我們接著按照上面的流程進(jìn)行探索

  • 首先我們得到LGPerson的元類

    元類

  • 此事可以看到,在LGPerson的元類中,我們?nèi)匀豢梢阅玫?code>isa指針。那么我們用isaISA_MASK進(jìn)行與運(yùn)算,得到什么呢?請(qǐng)往下看。

    NSObject.png

可以看到,經(jīng)過一系列的操作,LGPerson的元類最終竟然指向了NSObject。
而且NSObject的首地址和isa指針的地址相同,所以也可以說(shuō)明NSObject指向的是自己。

  • 那我們看看NSObject的情況,
NSObject.png

可以看到,這次打印的地址跟我們之前得到的NSObject地址不一樣,難道NSObject在內(nèi)存中有多份嗎?那我們?cè)俅蛴∫幌峦暾牡刂贰?br>

NSObject.png

再和之前的地址對(duì)比一下,可以看到一樣。

所以得出結(jié)論,LGPerson元類的isa指針并不是指向NSObject,而是指向NSObject的元類,即根元類,而NSObject也是指向根元類,而根元類最終指向自己。

走位圖.png

上面就是isa繼承關(guān)系的走勢(shì)圖,總結(jié)如下:

isa

  • 實(shí)例對(duì)象isa指向類對(duì)象
  • 類對(duì)象isa指向元類
  • 元類isa指根(NSObject)元類
  • 根(NSObject)元類指向自己

superclass(繼承關(guān)系)

  • 子類繼承自父類
  • 類才有繼承關(guān)系,而實(shí)例對(duì)象不存在繼承關(guān)系
  • 所有類都繼承NSObject,而NSObject繼承自nil
  • NSObject的元類也繼承自NSObject

objc_object & objc_class

在我們之前的分析中,通過clang進(jìn)行解析

#ifndef _REWRITER_typedef_LGPerson
#define _REWRITER_typedef_LGPerson
typedef struct objc_object LGPerson;
typedef struct {} _objc_exc_LGPerson;
#endif

extern "C" unsigned long OBJC_IVAR_$_LGPerson$_name;
struct LGPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS;  //isa
    NSString *_name;
};

可以看到LGPersonobjc_object類型的 ,LGPerson的底層編譯LGPerson_IMPL中又包含了NSObject_IMPL,NSObject_IMPL是一個(gè)結(jié)構(gòu)體,包含了Class isa

struct NSObject_IMPL {
    Class isa;
};

Class又是一個(gè)objc_class類型的結(jié)構(gòu)提體。

typedef struct objc_class *Class;

所以,objc_object就是一個(gè)根類,而objc_class定義了一個(gè)Class,這證明了每個(gè)對(duì)象都有isa指針。
而從我們的源碼,可以看出來(lái)objc_class繼承自objc_object

objc_object & objc_class.png

內(nèi)存偏移

在接著往下分析之前,我們來(lái)了解一個(gè)知識(shí) -- 內(nèi)存偏移

普通指針

先看下面的案例,打印a、b的值和地址。

   int a = 10;
   int b = 10;
   //打印地址和
   NSLog(@"%d --- %p",a,&a);
   NSLog(@"%d --- %p",b,&b);

如下圖所示


image.png
  • a、b都指向10,但是a、b的地址不一樣,這是一種拷貝,屬于值拷貝,也稱為淺拷貝。

  • a,b的地址之間相差 4 個(gè)字節(jié),這取決于a、b的類型

對(duì)象指針

   LGPerson *p1 = [LGPerson alloc];
   LGPerson *p2 = [LGPerson alloc];
   NSLog(@"%@ --- %p",p1,&p1);
   NSLog(@"%@ --- %p",p2,&p2);

打印結(jié)果如下所示:

image.png
  • p1、p2 是指針,p1 是 指向[LGPerson alloc]創(chuàng)建的空間地址,即內(nèi)存地址,p2 同理

&p1、&p2是指向 p1、p2對(duì)象指針的地址,這個(gè)指針 就是二級(jí)指針

數(shù)組指針

   //數(shù)組指針
   int c[4] = {1, 2, 3, 4};
   int *d = c;
   NSLog(@"%p -- %p - %p", &c, &c[0], &c[1]);
   NSLog(@"%p -- %p - %p", d, d+1, d+2);

打印結(jié)果如下:


image.png
  • &c&c[0]都是取 首地址,即數(shù)組名等于首地址
  • &c&c[1] 相差4個(gè)字節(jié),地址之間相差的字節(jié)數(shù),主要取決于存儲(chǔ)的數(shù)據(jù)類型
  • 可以通過 首地址+偏移量取出數(shù)組中的其他元素,其中偏移量是數(shù)組的下標(biāo),內(nèi)存中首地址實(shí)際移動(dòng)的字節(jié)數(shù) 等于偏移量 * 數(shù)據(jù)類型字節(jié)數(shù)

類結(jié)構(gòu)

我們已經(jīng)知道,類在底層最終會(huì)轉(zhuǎn)化成objc_class,objc_class結(jié)構(gòu)如下所示,

image.png

bits有我們需要的所有信息,那么要拿到bits,我們需要進(jìn)行內(nèi)存偏移
那么看看bits前面的東西有多大,進(jìn)行多少偏移。

  • isa屬性:繼承自objc_object的isa,占 8字節(jié)
  • superclass屬性:Class類型,Class是由objc_object定義的,是一個(gè)指針,占8字節(jié)
  • cache,是一個(gè)cache_t類型的結(jié)構(gòu)體,所以我們來(lái)分析下cache

cache_t

我們來(lái)看看cache_t的結(jié)構(gòu),進(jìn)入cache類cache_t的定義(只貼出了結(jié)構(gòu)體中非static修飾的屬性,主要是因?yàn)閟tatic類型的屬性 不存在結(jié)構(gòu)體的內(nèi)存中),有如下幾個(gè)屬性

struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    explicit_atomic<struct bucket_t *> _buckets; // 是一個(gè)結(jié)構(gòu)體指針類型,占8字節(jié)
    explicit_atomic<mask_t> _mask; //是mask_t 類型,而 mask_t 是 unsigned int 的別名,占4字節(jié)
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic<uintptr_t> _maskAndBuckets; //是指針,占8字節(jié)
    mask_t _mask_unused; //是mask_t 類型,而 mask_t 是 uint32_t 類型定義的別名,占4字節(jié)
    
#if __LP64__
    uint16_t _flags;  //是uint16_t類型,uint16_t是 unsigned short 的別名,占 2個(gè)字節(jié)
#endif
    uint16_t _occupied; //是uint16_t類型,uint16_t是 unsigned short 的別名,占 2個(gè)字節(jié)
  • 【情況一】if流程

    • buckets 類型是struct bucket_t *,是結(jié)構(gòu)體指針類型,占8字節(jié)

    • maskmask_t類型,而mask_tunsigned int 的別名,占4字節(jié)

  • 【情況二】elseif流程

    • _maskAndBucketsuintptr_t類型,它是一個(gè)指針,占8字節(jié)

    • _mask_unused是mask_t 類型,而mask_t uint32_t 類型定義的別名,占4```字節(jié)

    • _flagsuint16_t類型,uint16_tunsigned short 的別名,占2個(gè)字節(jié)

    • _occupieduint16_t類型,uint16_t是 unsigned short 的別名,占 2```個(gè)字節(jié)

總結(jié):所以最后計(jì)算出cache類的內(nèi)存大小 = 12 + 2 + 2 = 16字節(jié)

所以最終要拿到bits需要平移32位。

class_data_bits_t

  • 首先打印出LGPerson
    LGPerson.png
  • 拿出首地址0x100002200平移32位 得到0x100002220,得到bits
    bits .png
  • 獲取data()
    data() .png
  • 獲取class_rw_t
    class_rw_t .png
  • 獲取屬性列表properties()
    properties .png
  • 我們可以看到里面是一個(gè)list,獲取其中的元素
    image.png

    可以看到list中的屬性與我們LGPerson中的屬性一致。
?著作權(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),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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