《OC底層系列五》-類的結(jié)構(gòu)分析

前言

  • 通過上一篇 《OC底層系列四》-isa&superclass分析》中我們分析了isa和superclass的走向,知道了:
    • OC類的isa(其位域成員shiftcls)存儲著其元類的信息
    • NSObject類的superclass指向nil,NSObject元類的superclass指向NSObject類
  • 今天對類的結(jié)構(gòu)進(jìn)行分析,探究類的屬性、成員變量、實(shí)例方法、類方法在底層的實(shí)現(xiàn)。

目錄

目錄.png

1、簡介

本文主要從結(jié)合底層源碼,結(jié)合lldb來分析探究類的屬性、成員變量、實(shí)例方法、類方法在底層的實(shí)現(xiàn)。

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

OC類的底層為objc_class的結(jié)構(gòu)體,對類進(jìn)行探索,即分析objc_class包含成員的和作用。
781源碼中關(guān)于objc_class定義如下:

// objc-runtime-new.h
struct objc_class : objc_object {
    // Class ISA; 繼承自objc_object,包含isa
    Class superclass;  // 父類指針
    cache_t cache;             // formerly cache pointer and vtable,用于緩存指針和 vtable 
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
                               // class_rw_t 指針加上 rr/alloc 的標(biāo)志
                               // bits 用于存儲類的方法、屬性、遵循的協(xié)議等信息的地方
    // 返回一個 class_rw_t 類型的結(jié)構(gòu)體變量
    // Objective-C 類中的屬性、方法還有遵循的協(xié)議等信息都保存在 class_rw_t 中
     class_rw_t *data() const {
        return bits.data();
    }
...
}

我們要探究的類的屬性、方法等都在bits里面,為了能夠獲取到bit的內(nèi)存信息,我們需要計(jì)算出isa、superclass、cache占的大小,然后運(yùn)用內(nèi)存偏移獲取到bits內(nèi)存信息。

2.1、isa

isa繼承自objc_object,因此第1個成員為isa,前面我們分析過其占8字節(jié)

2.2、superclass

superclass為Class類型,即一個指向objc_class結(jié)構(gòu)體的指針,占8字節(jié)

// objc.h
typedef struct objc_class *Class;

2.3、cache

是一個cache_t的成員,cache_t定義關(guān)鍵代碼如下如下:

struct cache_t {
// 非ARM設(shè)備
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    explicit_atomic<struct bucket_t *> _buckets;  // 結(jié)構(gòu)體指針,8字節(jié)
    explicit_atomic<mask_t> _mask; // mask_t即uint32_t類型,4字節(jié)

// ARM64位
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic<uintptr_t> _maskAndBuckets;  // uintptr_t即unsigned long類型,8字節(jié)
    mask_t _mask_unused; // mask_t即uint32_t類型,4字節(jié)

// 非ARM64位,即ARM32位
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    // _maskAndBuckets stores the mask shift in the low 4 bits, and
    // the buckets pointer in the remainder of the value. The mask
    // shift is the value where (0xffff >> shift) produces the correct
    // mask. This is equal to 16 - log2(cache_size).
    explicit_atomic<uintptr_t> _maskAndBuckets; // uintptr_t即unsigned long類型,8字節(jié)
    mask_t _mask_unused; // mask_t即uint32_t類型,4字節(jié)
#else
#error Unknown cache mask storage type.
#endif
  
// uint16_t即unsigned short類型,占2字節(jié)

// 64位系統(tǒng)下
#if __LP64__
    uint16_t _flags;
#endif

    uint16_t _occupied;

cache_t是一個結(jié)構(gòu)體,緩存指針和函數(shù)表,由注釋可以知道,無論是ARM64、ARM32還是模擬器環(huán)境下,其成員所占字節(jié)依次為8、4、2、2字節(jié),根據(jù)內(nèi)存對齊(參考帶你深入理解iOS-內(nèi)存對齊)可以知道,cache占16字節(jié)

2.4、bits

  • 綜上我們可以知道,將類的首地址偏移32字節(jié)即可得到bits的內(nèi)存地址信息。
  • bits是一個class_data_bits_t類型的結(jié)構(gòu)體指針,根據(jù)內(nèi)存偏移可以得出class_data_bits_t的地址為:
    • 方式一:通過**p/x Person.class **計(jì)算得出類首地址然后+32得出。
    • 方式二:使用x/6gx 獲取其類首地址開始6個8字節(jié)可以得出其內(nèi)存地址

對Person和main做了修改,重新運(yùn)行了項(xiàng)目,對應(yīng)的內(nèi)存地址發(fā)生了變換,但是不影響對bits的分析:

// Person.h
@interface Person : NSObject
{
    NSString *_title;
}

@property(nonatomic,copy) NSString *name;

- (void)updateTitle:(NSString *)title;

@end

// Person.m
@implementation Person

- (void)updateTitle:(NSString *)title {
    _title = title;
}

@end

// main.m
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        Person *p1 = [[Person alloc] init];
        p1.name = @"jack";
        [p1 updateTitle:@"Teacher"];
        
        NSLog(@"Hello, World!,p1:%p",[p1 class]);
        
    }
    return 0;
}

使用方式二,log處添加斷點(diǎn),lldb驗(yàn)證如下:

// 打印Person首地址開始的6個8字節(jié)內(nèi)存空間存儲的值
(lldb) x/6gx Person.class
// 1-16字節(jié)
0x100008208: 0x00000001000081e0 0x0000000100333028
// 16-32字節(jié)
0x100008218: 0x0000000102028c10 0x0001802400000007
// 32-48字節(jié),0x100008208即bits的內(nèi)存地址,0x100008228即bits的內(nèi)存地址
0x100008228: 0x0000000102028654 0x00000001000ac900
// 強(qiáng)轉(zhuǎn)為class_data_bits_t指針
(lldb) p (class_data_bits_t *)0x100008208
(class_data_bits_t *) $1 = 0x0000000100008228
// 獲取bits的data()
(lldb) p/x  $1->data()
(class_rw_t *) $2 = 0x0000000101b04670
// 打印bits中的data信息
(lldb) p *$2
class_rw_t) $3 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000232
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}

我們獲取了objc_classdata()方法返回的內(nèi)容,但是沒有看到屬性列表、方法等,data()返回一個class_rw_t的結(jié)構(gòu)體指針,關(guān)鍵代碼定義如下

// objc-runtime-new.h
struct class_rw_t {
...
    // 存儲類的成員變量
    const class_ro_t *ro() const {
        auto v = get_ro_or_rwe();
        if (slowpath(v.is<class_rw_ext_t *>())) {
            return v.get<class_rw_ext_t *>()->ro;
        }
        return v.get<const class_ro_t *>();
    }

  // 存儲類的實(shí)例方法
  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};
        }
    }

    // 存儲類的協(xié)議
    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};
        }
    }
// 類方法存儲在元類中
}

筆者已經(jīng)把探索的結(jié)果寫在了上面的注釋里,下面通過lldb來進(jìn)行驗(yàn)證。

2.4.1 屬性

class_rw_tproperties() 方法返回一個property_array_t繼承自list_array_tt的c++類,類的屬性就存儲在list中。

class list_array_tt {
...
private:
    union {
        List* list;
        uintptr_t arrayAndFlag;
    };
...
}

lldb驗(yàn)證:

(class_rw_t) $3 = {
  flags = 2148007936
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000232
    }
  }
  firstSubclass = nil
  nextSiblingClass = NSUUID
}

// 返回類中的屬性列表信息,一個property_array_t類
(lldb) p $3.properties()
(const property_array_t) $4 = {
  list_array_tt<property_t, property_list_t> = {
     = {
      list = 0x00000001000081a0
      arrayAndFlag = 4295000480
    }
  }
}

// 返回?cái)?shù)組list的指針
(lldb) p $4.list
(property_list_t *const) $5 = 0x00000001000081a0

// 獲取數(shù)組list存儲的信息
(lldb) p *$5
(property_list_t) $6 = {
  entsize_list_tt<property_t, property_list_t, 0> = {
    entsizeAndFlags = 16
    count = 1
    first = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
  }
}

// 獲取第一個屬性
(lldb) p $6.get(0)
(property_t) $7 = (name = "name", attributes = "T@\"NSString\",C,N,V_name")
// 獲取第二個屬性,越界,說明不存在
(lldb) p $6.get(1)
Assertion failed: (i < count), function get, file /Users/a002/PrivateReposity/github/xxerz/objc4/objc4-781/runtime/objc-runtime-new.h, line 438.
error: Execution was interrupted, reason: signal SIGABRT.
The process has been returned to the state before expression evaluation.
  • 通過上面的lldb打印結(jié)果,可以知道類的屬性存儲路徑為:
    Person類屬性列表->objc_class.bits->class_rw_t.properties()->property_list_t.list->list,通過objc_class中的bits獲取到類的屬性列表
2.4.2 成員變量

通過class_rw_t中的ro()方法獲取,ro()返回一個class_ro_t結(jié)構(gòu)體指針,其class_ro_t結(jié)構(gòu)體中的ivars存儲類的成員變量列表
class_ro_t定義如下:

// objc-runtime-new.h
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;

驗(yàn)證ivars中存儲著類的成員變量:

// 獲取class_rw_t中的ro()返回值,返回一個結(jié)構(gòu)體指針
lldb) p $3.ro()
(const class_ro_t *) $8 = 0x00000001000080a8
// 打印class_ro_t結(jié)構(gòu)體的內(nèi)容
(lldb) p *$8
(const class_ro_t) $9 = {
  flags = 388
  instanceStart = 8
  instanceSize = 24
  reserved = 0
  ivarLayout = 0x0000000100003f7f "\x02"
  name = 0x0000000100003f78 "Person"
  baseMethodList = 0x00000001000080f0
  baseProtocols = 0x0000000000000000
  ivars = 0x0000000100008158
  weakIvarLayout = 0x0000000000000000
  baseProperties = 0x00000001000081a0
  _swiftMetadataInitializer_NEVER_USE = {}
}
// 獲取類的成員變量列表的指針
(lldb) p $9.ivars
(const ivar_list_t *const) $10 = 0x0000000100008158
(lldb) p *$10
// 獲取成員變量列表
(const ivar_list_t) $11 = {
  entsize_list_tt<ivar_t, ivar_list_t, 0> = {
    entsizeAndFlags = 32
    count = 2
    first = {
      offset = 0x00000001000081d0
      name = 0x0000000100003f1a "_title"
      type = 0x0000000100003f89 "@\"NSString\""
      alignment_raw = 3
      size = 8
    }
  }
}
// 獲取第一個成員變量
(lldb) p $11.get(0)
(ivar_t) $12 = {
  offset = 0x00000001000081d0
  name = 0x0000000100003f1a "_title"
  type = 0x0000000100003f89 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
// 獲取第二個成員變量
(lldb) p $11.get(1)
(ivar_t) $13 = {
  offset = 0x00000001000081d8
  name = 0x0000000100003f21 "_name"
  type = 0x0000000100003f89 "@\"NSString\""
  alignment_raw = 3
  size = 8
}
  • 通過p $9.ivars獲取到了成員變量列表的指針。
  • p *$10中獲取成員變量列表,其存儲在ivar_list_t結(jié)構(gòu)體中,count=2說明有2個成員變量,依次打印為_title,_name
  • 我們可以得到Person類的成員變量存儲路徑為:
    Person類成員變量列表->objc_class.bits->class_rw_t.ro()->class_ro_t.ivars->ivars
2.4.3 實(shí)例方法

class_rw_tmethods()返回該一個method_array_t類,其list成員中可以獲取到類的實(shí)例方法。
驗(yàn)證過程如下:

// 獲取class_rw_t中的methods返回值,返回一個method_array_t的類,繼承自list_array_tt
(lldb) p $3.methods()
(const method_array_t) $14 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x00000001000080f0
      arrayAndFlag = 4295000304
    }
  }
}

// 獲取method_array_t中的list指針
(lldb) p $14.list
(method_list_t *const) $15 = 0x00000001000080f0

// 打印list內(nèi)容,list即存儲著類的實(shí)例方法信息
(lldb) p *$15
(method_list_t) $16 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    // 有4個實(shí)例方法  
    count = 4
    first = {
      name = ".cxx_destruct"
      types = 0x0000000100003f81 "v16@0:8"
      imp = 0x0000000100003e30 (objc4-test`-[Person .cxx_destruct])
    }
  }
}

(lldb) p $16.get(0)
(method_t) $17 = {
  name = ".cxx_destruct"
  types = 0x0000000100003f81 "v16@0:8"
  imp = 0x0000000100003e30 (objc4-test`-[Person .cxx_destruct])
}
(lldb) p $16.get(1)
(method_t) $18 = {
  name = "name"
  types = 0x0000000100003fa0 "@16@0:8"
  imp = 0x0000000100003dd0 (objc4-test`-[Person name])
}
(lldb) p $16.get(2)
(method_t) $19 = {
  name = "setName:"
  types = 0x0000000100003f95 "v24@0:8@16"
  imp = 0x0000000100003e00 (objc4-test`-[Person setName:])
}
(lldb) p $16.get(3)
(method_t) $20 = {
  name = "updateTitle:"
  types = 0x0000000100003f95 "v24@0:8@16"
  imp = 0x0000000100003d80 (objc4-test`-[Person updateTitle:])
}
  • 通過p *$15獲取到了類的實(shí)例方法列表** method_list_t**。
  • p *$15打印方法列表信息,由count=4得知有4個方法,依次為
    -[Person .cxx_destruct]、-[Person . name]、-[Person . setName:]、-[Person .updateTitle:]
2.4.4 類方法
  • 類的實(shí)例方法存儲在類中,那么類的類方法存儲在哪里?
  • 前面分析isa走位的時候提到了元類類的isa指向元類
    類的實(shí)例的isa指向類,且類的實(shí)例方法存儲在類的bits中
    我們可以推測類的類方法存儲在元類的bits里
    驗(yàn)證如下:
// 獲取類的指針
lldb) p/x Person.class
(Class) $0 = 0x0000000100008208 Person

// 獲取類的內(nèi)存信息
(lldb) x/4gx 0x0000000100008208
0x100008208: 0x00000001000081e0 0x0000000100333028
0x100008218: 0x0000000100693f70 0x0001802400000007
// 獲取元類的指針
(lldb) p/x 0x00000001000081e0 & 0x00007ffffffffff8ULL
(unsigned long long) $1 = 0x00000001000081e0

// 獲取元類的內(nèi)存信息
(lldb) x/6gx 0x00000001000081e0
0x1000081e0: 0x0000000100333000 0x0000000100333000
0x1000081f0: 0x0000000100693ff0 0x0001e03500000007
// 0x100008200即元類的bits指針
0x100008200: 0x0000000100693e94 0x00000001000081e0

// 強(qiáng)轉(zhuǎn)為class_data_bits_t *類型
(lldb) p (class_data_bits_t *)0x100008200
(class_data_bits_t *) $2 = 0x0000000100008200

// 獲取元類的bits信息
(lldb) p $2->data()
(class_rw_t *) $3 = 0x0000000100693e90

// 打印元類的bits信息
(lldb) p *$3
(class_rw_t) $4 = {
  flags = 2684878849
  witness = 1
  ro_or_rw_ext = {
    std::__1::atomic<unsigned long> = {
      Value = 4295000128
    }
  }
  firstSubclass = nil
  nextSiblingClass = 0x00007fff900cdcd8
}
// 獲取元類的示例方法列表,返回一個method_array_t類
(lldb) p $4.methods()
(const method_array_t) $5 = {
  list_array_tt<method_t, method_list_t> = {
     = {
      list = 0x0000000100008088
      arrayAndFlag = 4295000200
    }
  }
}

// 獲取method_array_t中l(wèi)ist的內(nèi)容,返回一個method_list_t結(jié)構(gòu)體指針
(lldb) p $5.list
(method_list_t *const) $6 = 0x0000000100008088
// 打印list內(nèi)容
(lldb) p *$6
(method_list_t) $7 = {
  entsize_list_tt<method_t, method_list_t, 3> = {
    entsizeAndFlags = 26
    // 有一個實(shí)例方法
    count = 1
    first = {
      name = "eat"
      types = 0x0000000100003f81 "v16@0:8"
      imp = 0x0000000100003d70 (objc4-test`+[Person eat])
    }
  }
}
(lldb) p $7.get(0)
(method_t) $8 = {
  name = "eat"
  types = 0x0000000100003f81 "v16@0:8"
  imp = 0x0000000100003d70 (objc4-test`+[Person eat])
}

  • 結(jié)果證明了類的類方法存儲在其元類的bits里Person元類中的示例方法只有一個,為+[Person eat]

3、總結(jié)

  • 本文使用lldb命令分析了類的屬性、成員變量、實(shí)例方法、類方法在OC底層如何獲取。
  • 類的屬性:通過@property定義的屬性,存儲在類的bits里,路徑為
    objc_class.bits->class_data_bits_t.data()->class_rw_t.properties()->property_array_t.list->list
  • 類的成員變量:通過{}定義的成員變量,存儲在類的bits里,路徑為
    objc_class.bits->class_data_bits_t.data()->class_rw_t.ro()->class_ro_t.ivars->ivars
  • 類的實(shí)例方法:存儲在類的bits里路徑為
    objc_class.bits->class_data_bits_t.data()->class_rw_t.methods()-> method_list_t.list->list
  • 類的類方法:存儲在元類bits里,路徑為
    該類的元類的objc_class.bits->class_data_bits_t.data()->class_rw_t.methods()-> method_list_t.list->list
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,958評論 2 373