萬物皆對象
我們知道在iOS中旅掂,id
可以指向所有的實例對象,Class
可以指向所有的類访娶,我們來看一下他們的聲明:
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// A pointer to an instance of a class.
typedef struct objc_object *id;
從聲明中可以看出商虐,OC的所有實例對象都是由objc_object
結(jié)構(gòu)體擴(kuò)展而來,我們知道objc_object
結(jié)構(gòu)體中有一個isa
指針崖疤,所以所有的對象都有isa
指針秘车。
我們再來看一下objc_class
的聲明:
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();
}
省略。劫哼。叮趴。
}
從objc_class
的結(jié)構(gòu)可以得出,OC中所有的類
其實也是對象
权烧,也存在isa
指針眯亦,所以這就是我們常說的萬物皆對象
伤溉。
對象、類妻率、元類乱顾、根元類
在前一篇iOS之isa文章的最后,插入了一張圖舌涨,如下:
很多iOS開發(fā)者糯耍,肯定不止一次見到過這張圖,我想肯定有人會有疑問囊嘉,真的像圖中這樣嗎温技?下面我們驗證一下。
@interface PYTeacher : NSObject
@end
@implementation PYTeacher
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
PYTeacher *teacher = [PYTeacher alloc];
NSLog(@"%@", teacher);
}
return 0;
}
依然是上面這段代碼扭粱,我們可以通過打印對象的內(nèi)存分布來驗證舵鳞,依然以x86_64
為例,ISA_MASK
為0x00007ffffffffff8ULL
:
對象的 isa
指向類
(lldb) x/2gx teacher
0x100712980: 0x001d800100002359 0x0000000000000000
// isa & ISA_MASK
(lldb) po 0x001d800100002359 & 0x00007ffffffffff8ULL
PYTeacher
從上面的打印結(jié)果琢蛤,我們得出 teacher
對象的 isa
指向 PYTeacher
類:
類的 isa
指向元類
由第一節(jié)我們得出蜓堕,類也是對象,也存在isa指針博其,那類的isa指針指向哪里呢套才?
我們打印一下類的isa
:
(lldb) x/2gx PYTeacher.class
0x100002358: 0x0000000100002330 0x00007fff940b3118
// 打印 PYTeacher 的元類
(lldb) po 0x0000000100002330 & 0x00007ffffffffff8ULL
PYTeacher
從打印結(jié)果,我們看到慕淡,PYTeacher
的isa
也指向PYTeacher
背伴,那這兩個PYTeacher
類是同一個嗎?我們打印一下他們各自的地址:
(lldb) p/x 0x0000000100002330 & 0x00007ffffffffff8ULL
// PYTeacher元類的地址
(unsigned long long) $4 = 0x0000000100002330
(lldb) p/x PYTeacher.class
// PYTeacher類的地址
(Class) $5 = 0x0000000100002358 PYTeacher
我們看到這兩個PYTeacher
的地址并不相同峰髓,實際上PYTeacher
的isa
指向了他的元類 PYTeacher
:
元類的 isa
指向根元類
得到上面isa
的指向流程以后傻寂,我們肯定會想,元類的 isa 指向誰呢携兵?
我們還是通過打印來看一下:
(lldb) x/2gx 0x0000000100002330 // PYTeacher元類的地址
0x100002330: 0x00007fff940b30f0 0x00007fff940b30f0
// 打印根元類
(lldb) po 0x00007fff940b30f0 & 0x00007ffffffffff8ULL
NSObject
從打印結(jié)果看疾掰,元類的isa
指向NSObject
,這時徐紧,我們會想這個NSObject
是我們常用的那個NSObject
嗎静檬?我們來驗證一下:
(lldb) p/x 0x00007fff940b30f0 & 0x00007ffffffffff8ULL
(unsigned long long) $7 = 0x00007fff940b30f0 // 剛才得出的NSObject的地址
(lldb) p/x NSObject.class
(Class) $8 = 0x00007fff940b3118 NSObject // 我們常用的NSObject的地址
(lldb) x/2gx NSObject.class
0x7fff940b3118: 0x00007fff940b30f0 0x0000000000000000
(lldb) p/x 0x00007fff940b30f0 & 0x00007ffffffffff8ULL
(unsigned long long) $10 = 0x00007fff940b30f0 // NSObject的元類的地址
我們看到剛才得出的NSObject
是NSObject
的元類
,也就是根元類
:
根元類的 isa
指向自己
驗證到這里并级,肯定有人會想拂檩,根元類的 isa 又指向哪里呢?
我們繼續(xù)驗證:
(lldb) x/2gx 0x00007fff940b30f0 // 根元類的地址
0x7fff940b30f0: 0x00007fff940b30f0 0x00007fff940b3118
(lldb) p/x 0x00007fff940b30f0 & 0x00007ffffffffff8ULL
(unsigned long long) $11 = 0x00007fff940b30f0 // 根元類isa指向的地址
(lldb) po 0x00007fff940b30f0 & 0x00007ffffffffff8ULL
NSObject
打印結(jié)果得出:根元類的 isa
指向了自己死遭。
類的結(jié)構(gòu)
我們在iOS的源碼中看到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();
}
省略。凯旋。呀潭。
}
objc_class
的成員變量在內(nèi)存中的分布如下:
從內(nèi)存分布可以看出钉迷,我們可以通過移動
pointer
(類的地址指針),來讀取不同地址的信息钠署。我們知道 isa
是 8
個字節(jié)糠聪,所以對 pointer
移動8
個字節(jié),就可以讀取superclass
的內(nèi)容谐鼎,下面我們可以驗證一下:
@interface PYEnglishTeacher : PYTeacher
@end
@implementation PYEnglishTeacher
@end
我們創(chuàng)建了一個PYEnglishTeacher
類繼承自PYTeacher
類:
(lldb) p/x PYEnglishTeacher.class
(Class) $0 = 0x0000000100002438 PYEnglishTeacher
(lldb) po 0x0000000100002438 + 8
<PYTeacher: 0x100002440>
所以舰蟆,對PYEnglishTeacher
類的地址移動8
個字節(jié),就可以讀取到superclass
狸棍。
Methods
我們發(fā)現(xiàn)在objc_class
中身害,有isa
、superclass
草戈、cache
塌鸯、bits
等成員變量,但是我們自己聲明的屬性唐片、方法丙猬、協(xié)議存儲在哪呢?我們知道费韭,在以前的objc_class
定義中茧球,是有objc_ivar_list
、objc_method_list
星持、objc_cache
抢埋、objc_protocol_list
等成員變量的,那現(xiàn)在蘋果是如何設(shè)計的呢钉汗?我們發(fā)現(xiàn)objc_class
中有class_rw_t *data()
的方法羹令,class_rw_t
定義如下:
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 *>();
}
void set_ro(const class_ro_t *ro) {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
v.get<class_rw_ext_t *>()->ro = ro;
} else {
set_ro_or_rwe(ro);
}
}
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),我們得出可以通過 class_rw_t
來讀取方法列表等卢未。所以首先我們要知道肪凛,需要移動多少字節(jié),才能讀取objc_class
中的bits
辽社。
我們知道isa
為8字節(jié)伟墙,superclass
為8字節(jié),那cache
是多少字節(jié)呢滴铅?我們看一下cache_t
的結(jié)構(gòu):
struct cache_t {
explicit_atomic<struct bucket_t *> _buckets; // 8字節(jié)
explicit_atomic<mask_t> _mask; // 4字節(jié)
省略戳葵。。
#if __LP64__
uint16_t _flags; // 2字節(jié)
#endif
uint16_t _occupied; // 2字節(jié)
}
所以 cache
是16字節(jié)汉匙。所以pointer
移動32個字節(jié)就是bits
的地址拱烁。我們打印一下PYTeacher
:
(lldb) p/x PYTeacher.class
(Class) $0 = 0x0000000100002208 PYTeacher
// 打印class_data_bits_t bits地址
(lldb) p (class_data_bits_t *)(0x0000000100002208 + 32)
(class_data_bits_t *) $1 = 0x0000000100002228
$1
為class_data_bits_t
結(jié)構(gòu)體的指針生蚁,通過調(diào)用data()
方法可以得到class_rw_t
的指針:
(lldb) p $1->data()
(class_rw_t *) $2 = 0x00000001021100b0
我們打印$2
指向的class_rw_t
結(jié)構(gòu)體:
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2148007936
witness = 0
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975600
}
firstSubclass = PYEnglishTeacher
nextSiblingClass = NSUUID
}
現(xiàn)在我們PYTeacher
添加一些方法和屬性:
@interface PYTeacher : NSObject {
NSString *name;
}
@property (nonatomic, assign) int age;
- (void)teach;
+ (void)study;
@end
@implementation PYTeacher
- (void)teach {
}
+ (void)study {
}
@end
這時,我們再打印class_rw_t
的內(nèi)容:
(lldb) p/x PYTeacher.class
(Class) $0 = 0x0000000100002300 PYTeacher
// 打印bits的地址
(lldb) p (class_data_bits_t *)(0x0000000100002300 + 32)
(class_data_bits_t *) $1 = 0x0000000100002320
// 獲取class_rw_t的指針
(lldb) p $1->data()
(class_rw_t *) $2 = 0x0000000100677d80
// 打印 class_rw_t 內(nèi)容
(lldb) p *$2
(class_rw_t) $3 = {
flags = 2148007936
witness = 0
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975632
}
firstSubclass = PYEnglishTeacher
nextSiblingClass = NSUUID
}
// 獲取class_rw_t中的方法列表
(lldb) p $3.methods()
(const method_array_t) $4 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x00000001000020d8
arrayAndFlag = 4294975704
}
}
}
// 獲取method_list_t的數(shù)組指針
(lldb) p $4.list
(method_list_t *const) $5 = 0x00000001000020d8
// 打印method_list_t數(shù)組戏自,其實就是數(shù)組的第0位
(lldb) p *$5
(method_list_t) $6 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 4
first = {
name = "teach"
types = 0x0000000100000f74 "v16@0:8"
imp = 0x0000000100000de0 (KCObjc`-[PYTeacher teach] at main.m:24)
}
}
}
// 打印數(shù)組中的內(nèi)容
(lldb) p $6.get(0)
(method_t) $7 = {
name = "teach"
types = 0x0000000100000f74 "v16@0:8"
imp = 0x0000000100000de0 (KCObjc`-[PYTeacher teach] at main.m:24)
}
(lldb) p $6.get(1)
(method_t) $8 = {
name = ".cxx_destruct"
types = 0x0000000100000f74 "v16@0:8"
imp = 0x0000000100000e30 (KCObjc`-[PYTeacher .cxx_destruct] at main.m:22)
}
(lldb) p $6.get(2)
(method_t) $9 = {
name = "age"
types = 0x0000000100000f8a "i16@0:8"
imp = 0x0000000100000df0 (KCObjc`-[PYTeacher age] at main.m:15)
}
(lldb) p $6.get(3)
(method_t) $10 = {
name = "setAge:"
types = 0x0000000100000f92 "v20@0:8i16"
imp = 0x0000000100000e10 (KCObjc`-[PYTeacher setAge:] at main.m:15)
}
我們看到方法列表中有4
個方法邦投,但是沒有
+ (void)study
方法,為什么呢擅笔?
仔細(xì)觀察我們會發(fā)現(xiàn)志衣,這4個方法,除了1個C++
的方法外猛们,其他3個都是對象方法
念脯,也就是對象方法存在類的方法列表里
,我們說萬物皆對象
阅懦,類
是元類
的對象
和二,難道說類對象
的”對象方法“
存在元類
的方法列表里?下面我們驗證一下:
(lldb) x/2gx PYTeacher.class
0x100002300: 0x00000001000022d8 0x0000000100334140
(lldb) po 0x00000001000022d8 & 0x00007ffffffffff8ULL
PYTeacher
// 打印 PYTeacher元類 的地址
(lldb) p/x 0x00000001000022d8 & 0x00007ffffffffff8ULL
(unsigned long long) $13 = 0x00000001000022d8
// 打印元類中的 bits
(lldb) p (class_data_bits_t *)(0x00000001000022d8 + 32)
(class_data_bits_t *) $14 = 0x00000001000022f8
// 獲取 class_rw_t 的地址
(lldb) p $14->data()
(class_rw_t *) $15 = 0x0000000100677d40
// 打印 class_rw_t 的內(nèi)容
(lldb) p *$15
(class_rw_t) $16 = {
flags = 2684878849
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = 4294975528
}
firstSubclass = 0x0000000100002328
nextSiblingClass = 0x00007fff8b968cd8
}
// 獲取 元類 的方法列表
(lldb) p $16.methods()
(const method_array_t) $17 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100002070
arrayAndFlag = 4294975600
}
}
}
// 打印方法列表的地址
(lldb) p $17.list
(method_list_t *const) $18 = 0x0000000100002070
// 打印方法列表信息
(lldb) p *$18
(method_list_t) $19 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 1
first = {
name = "study"
types = 0x0000000100000f74 "v16@0:8"
imp = 0x0000000100000dd0 (KCObjc`+[PYTeacher study] at main.m:28)
}
}
}
正如我們猜測的那樣耳胎,+ (void)study
方法存在元類
的方法列表
中惯吕。
Properties
我們再來看一下property
:
(lldb) p $3.properties()
(const property_array_t) $20 = {
list_array_tt<property_t, property_list_t> = {
= {
list = 0x0000000100002188
arrayAndFlag = 4294975880
}
}
}
// 打印屬性列表地址
(lldb) p $20.list
(property_list_t *const) $21 = 0x0000000100002188
// 打印屬性列表信息
(lldb) p *$21
(property_list_t) $22 = {
entsize_list_tt<property_t, property_list_t, 0> = {
entsizeAndFlags = 16
count = 1
first = (name = "age", attributes = "Ti,N,V_age")
}
}
這里我們看到了age
屬性,在class_rw_t
中怕午,我們卻沒找到成員變量
的存儲位置废登,但是class_rw_t
中,有獲取和設(shè)置class_ro_t
的方法郁惜,我們來看一下class_ro_t
結(jié)構(gòu)體堡距。
class_ro_t
class_ro_t
結(jié)構(gòu)體如下:
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;
省略。兆蕉。羽戒。
};
我們看到class_ro_t
中有const ivar_list_t * ivars
,我們來打印一下:
(lldb) p $3.ro()
(const class_ro_t *) $23 = 0x0000000100002090
// 打印 class_ro_t 內(nèi)容
(lldb) p *$23
(const class_ro_t) $24 = {
flags = 388
instanceStart = 8
instanceSize = 20
reserved = 0
ivarLayout = 0x0000000100000f28 "\x01"
name = 0x0000000100000f1e "PYTeacher"
baseMethodList = 0x00000001000020d8
baseProtocols = 0x0000000000000000
ivars = 0x0000000100002140
weakIvarLayout = 0x0000000000000000
baseProperties = 0x0000000100002188
_swiftMetadataInitializer_NEVER_USE = {}
}
// 打印 ivar_list_t 地址
(lldb) p $24->ivars
(const ivar_list_t *const) $25 = 0x0000000100002140
Fix-it applied, fixed expression was:
$24.ivars
// 打印 ivar_list_t 內(nèi)容
(lldb) p *$25
(const ivar_list_t) $26 = {
entsize_list_tt<ivar_t, ivar_list_t, 0> = {
entsizeAndFlags = 32
count = 2
first = {
offset = 0x00000001000022c8
name = 0x0000000100000f4a "name"
type = 0x0000000100000f7c "@\"NSString\""
alignment_raw = 3
size = 8
}
}
}
// 獲取成員變量
(lldb) p $26.get(0)
(ivar_t) $27 = {
offset = 0x00000001000022c8
name = 0x0000000100000f4a "name"
type = 0x0000000100000f7c "@\"NSString\""
alignment_raw = 3
size = 8
}
(lldb) p $26.get(1)
(ivar_t) $28 = {
offset = 0x00000001000022d0
name = 0x0000000100000f4f "_age"
type = 0x0000000100000f88 "i"
alignment_raw = 2
size = 4
}
ivar_list_t
中存儲這name
和_age
成員變量虎韵。
小結(jié)
- 對象的
成員變量
存儲在類
的class_ro_t
中易稠; -
對象方法
和屬性
存儲在類
的class_rw_t
中; -
類方法
存儲在元類
的class_rw_t
中包蓝。