前言
- 通過上一篇 《OC底層系列四》-isa&superclass分析》中我們分析了isa和superclass的走向,知道了:
- OC類的isa(其位域成員shiftcls)存儲著其元類的信息
- NSObject類的superclass指向nil,NSObject元類的superclass指向NSObject類。
- 今天對類的結(jié)構(gòu)進(jìn)行分析,探究類的屬性、成員變量、實(shí)例方法、類方法在底層的實(shí)現(xiàn)。
目錄
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_class的data()方法返回的內(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_t的properties() 方法返回一個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_t的methods()返回該一個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。