上一節(jié)我們了解了isa
的內(nèi)部結(jié)構(gòu)
蠕搜,了解了結(jié)構(gòu)
和類
的關(guān)系。
- 現(xiàn)在收壕,我們用代碼來(lái)探究下
isa
的指針指向與類
的關(guān)系
1. 類與isa指針的關(guān)系
在objc4
源碼中蜜宪,加入測(cè)試代碼。在NSLog
打印出加上斷點(diǎn)
int main(int argc, const char * argv[]) {
@autoreleasepool {
HTPerson * person = [[HTPerson alloc]init];
NSLog(@"%@", person);
}
return 0;
}
- 準(zhǔn)備好
isa
位運(yùn)算的MASK(遮罩)。
p person
打印鸡捐,x/4gx
打印箍镜,p/x
&與上ISA_MASK
、打印獲取到的isa中的shiftcls
地址。成功找到HTPerson
類
如果看不到写隶,需要回看上一節(jié)。了解
isa內(nèi)部構(gòu)造
和獲取類地址的方法。
- 我們猜想,既然拿到了
HTPerson
類的isa地址
,那HTPerson
類的isa
指針指向哪里呢详拙?
我們發(fā)現(xiàn)脯爪,0x0000000100002630
和0x0000000100002608
都打印出來(lái)是HTPerosn
的冷冗。2個(gè)地址不一樣為什么打印出來(lái)結(jié)果一樣俺叭?
類地址不應(yīng)該是唯一的嗎晋南?
這個(gè)問(wèn)題我們保留。下面再一起回答羔砾。
現(xiàn)在负间,我想繼續(xù)順著這根藤(isa指針?lè)较颍┡佳纯梢悦侥膫€(gè)類去。
我們發(fā)現(xiàn)政溃,一直順著摸趾访,摸到NSObject
后,內(nèi)存地址不再發(fā)生變化玩祟。
為了解答上面2個(gè)不同地址
都是打印了HTPerson
的問(wèn)題腹缩。我們需要先了解一個(gè)新東西: ??
2. 元類(Meta)
元類的定義和創(chuàng)建都由系統(tǒng)控制,由編譯器自動(dòng)完成空扎,不受我們管理
對(duì)象的isa
來(lái)自于類
,類
也是對(duì)象
润讥。那類
的isa
指向哪里呢转锈?
- 答案: 元類 。類的歸屬來(lái)自于元類楚殿。
類既然是對(duì)象撮慨,就需要管理方法
、屬性
的存儲(chǔ)和歸屬脆粥。而這個(gè)管理者砌溺,就是元類(Meta)
上面2個(gè)
不同地址
都打印HTPerson
的問(wèn)題,實(shí)際上打印路徑是: HTPerosn -> HTPerson元類 -> NSObject
問(wèn)題: 既然你說(shuō)打印到了元類
变隔, 那HTPerson元類
到NSObject
之后规伐,為什么就結(jié)束了? 不應(yīng)該再打印一次NSObject
元類嗎匣缘?
- 我們順著上面代碼猖闪。打印一次
p/x [NSObject class]
:
發(fā)現(xiàn)[NSObject class]
打印的地址與之前打印的地址不一致
!
- 因?yàn)?
[NSObject class]
打印的是NSObject
本類肌厨,而HTPerson元類
的父類是NSObject元類
培慌。所以地址不一樣。 - 我們驗(yàn)證一下柑爸。
果然吵护,NSObject
的isa
指針指向了NSObject元類
。 地址和HTPerson元類
的isa
指針地址一致表鳍。
那么馅而,類在內(nèi)存中會(huì)存在多份(多地址)嗎?
#import <objc/runtime.h>
#import "HTPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
Class class1 = [HTPerson class];
Class class2 = [HTPerson alloc].class;
Class class3 = object_getClass([HTPerson alloc]);
Class class4 = [HTPerson alloc].class;
NSLog(@"%p", class1);
NSLog(@"%p", class2);
NSLog(@"%p", class3);
NSLog(@"%p", class4);
}
return 0;
}
我們多種方式
讀取類进胯,通過(guò)打印可以發(fā)現(xiàn)用爪,所有地址都一樣。
- 類的信息在
內(nèi)存
中永遠(yuǎn)只存在一份
問(wèn)題: 不直接繼承NSObject類胁镐,Isa是如何指向的呢偎血?
@interface HTMan : HTPerson
@end
@implementation HTMan
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
HTMan * man = [[HTMan alloc]init];
NSLog(@"%p", man);
}
return 0;
}
代碼中HTMan
繼承自HTPerson
诸衔,而HTPerson
繼承自NSObject
但是isa
的指向,卻是HTMan
->HTMan元類
->NSObject元類
總結(jié)
- 類的信息在
內(nèi)存
中永遠(yuǎn)只存在一份
- 類的
isa
指針首先指向自己的元類
颇玷,再直接指向NSObject元類
(自己類->自己元類->NSObject元類) -
NSObject元類
的isa也指向NSObject元類
-
NSObject元類
是所有元類的始祖笨农。所以也叫根元類
誤區(qū):
- 上述是isa的
指針指向
,并非類的繼承關(guān)系帖渠。類
有繼承
關(guān)系谒亦,實(shí)例對(duì)象無(wú)繼承
關(guān)系。NSObject
沒(méi)有父類
(父類為Null)空郊。
所以我們類的繼承份招,溯源只需要找到NSObject。
OC
語(yǔ)言中:NSObject是對(duì)象的始祖狞甚。萬(wàn)物皆對(duì)象锁摔。- NSObject
根元類
的isa指針
是直接指向NSObject根元類
3. OC對(duì)象的本質(zhì)
首先了解2個(gè)結(jié)構(gòu)體: objc_object
(根對(duì)象)和 objc_class
(根類)
我們打開(kāi)objc4
源碼,搜索struct objc_object
搜索struct objc_class
:
源碼搜索時(shí)哼审,注意看結(jié)構(gòu)體
尾部
的聲明谐腰。UNAVAILABLE
已廢棄的不要耗費(fèi)精力了。
我們發(fā)現(xiàn)涩盾,objc_class
繼承自objc_object
十气。
object_object
擁有isa
屬性,所以objc_class
也擁有isa
春霍。萬(wàn)物皆對(duì)象(object)砸西。
使用方法:
-
struct objc_object * Object
以objc_object為模板,定義一個(gè)對(duì)象 -
struct objc_class * Class
以objc_class為模板终畅,定義一個(gè)類
3. 類的結(jié)構(gòu)分析
老規(guī)矩籍胯,先從案例下手,看懂了再總結(jié)
#import <Foundation/Foundation.h>
#import "HTPerson.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
HTPerson * person = [[HTPerson alloc]init];
NSLog(@"%p", person);
}
return 0;
}
-
p/x
打印HTPerson
類地址离福。 ->x/4gx 打印內(nèi)存信息
請(qǐng)問(wèn):
1. 為什么第一個(gè)地址是
HTPeroson
杖狼?第二個(gè)是NSObject
?
objc_class
第一個(gè)地址存放的是isa
地址妖爷,第二個(gè)存放的是superclass
類地址
(參考上面objc_object部分代碼
和objc_class部分代碼
圖蝶涩。)2. 第二個(gè)地址打印的是
NSObject
類還是元類?
NSObject.class
打印的地址與第二個(gè)地址
打印的一致
。 與接著打印的NSObject
元類地址不一致
絮识。 說(shuō)明第二個(gè)地址
打印的是NSObjetc
自身類绿聘。
3. 內(nèi)存偏移
老規(guī)矩,先從案例開(kāi)始次舌,在main.m
文件中加入測(cè)試代碼:
int main(int argc, const char * argv[]) {
@autoreleasepool {
int f[4] = {10,20,30,40};
NSLog(@"%p", &f);
NSLog(@"%p", &f[0]);
NSLog(@"%p", &f[1]);
NSLog(@"%p", &f[2]);
NSLog(@"%p", &f[3]);
}
return 0;
}
在打印的尾部加入斷點(diǎn)
我們發(fā)現(xiàn):
-
&f
和&f[0]
內(nèi)存地址一樣熄攘。 證明對(duì)象
的內(nèi)存地址
就是使用內(nèi)部首元素
的地址
-
-
f
是int
類型的數(shù)組,內(nèi)部元素每位占用4字節(jié)
彼念。 所以每個(gè)元素內(nèi)存偏移值
為4
挪圾。
-
是不是瞬間有個(gè)小想法:
我們是否可以根據(jù)數(shù)據(jù)類型
浅萧,確定內(nèi)存占用空間的大小
,通過(guò)內(nèi)存值偏移
哲思,就可定位
到下一元素
的指針地址洼畅。然后直接取出
這個(gè)指針地址指向的值
。
小拓展: 如何通過(guò)
地址
取出對(duì)應(yīng)的值
:
知識(shí)點(diǎn): 指針(地址)的指針
地址
強(qiáng)轉(zhuǎn)
為指定類型(int) -> 加*
讀取對(duì)象(指針)的指針
-> 打印目標(biāo)值
快速讀扰锱狻:
p *((int *)0x7ffeefbff5b0)
我們通過(guò)首地址偏移
帝簇,果然:
完美??
但前提是我們必須知道偏移值
是多少,也就是存儲(chǔ)
的是什么類型
靠益。
- 上面我們使用的是
地址偏移
丧肴,所以必須加入偏移值
。 - 我們也可以使用
指針偏移
胧后。直接在屬性層在進(jìn)行操作闪湾。
至此。我們已經(jīng)掌握了地址偏移
和指針偏移
绩卤。
- 只有知道
屬性類型
,占用空間大小
江醇,才能使用地址偏移
和指針偏移
濒憋,讀取類的所有信息。
現(xiàn)在陶夜,我們來(lái)分析類的結(jié)構(gòu)
4. 分析類結(jié)構(gòu)
在objc4
源碼中凛驮,搜索objc_class
。
-
剔除
不會(huì)占用類空間的const
条辟、void
黔夭、static
和函數(shù)
。
struct objc_class : objc_object {
// Class ISA; 羽嫡。 // 指針 - 占用8字節(jié)
Class superclass; // 指針 - 占用8字節(jié)
cache_t cache; // 本姥?
class_data_bits_t bits;
};
如果我們要獲取bits
的首地址位置。只有cache_t
類型不知道空間大小杭棵。 點(diǎn)進(jìn)去看看:
-
剔除
不會(huì)占用類空間的const
婚惫、void
、static
和函數(shù)
魂爪。
struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
explicit_atomic<struct bucket_t *> _buckets;
explicit_atomic<mask_t> _mask;
#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;
#else
#error Unknown cache mask storage type.
#endif
#if __LP64__
uint16_t _flags;
#endif
uint16_t _occupied;
};
- 我們進(jìn)入
explicit_atomic
查看:
- 發(fā)現(xiàn)返回的類型就是傳入的泛型
T
先舷。 所以explicit_atomic
的類型只跟他傳入
的類型相關(guān)。
- 我們進(jìn)入
bucket_t
結(jié)構(gòu)體:
這里的
_sel
和_imp
是不是很眼熟滓侍。 ?? 后續(xù)我們會(huì)詳細(xì)介紹
進(jìn)入uintptr_t
:
- 發(fā)現(xiàn)
uintptr_t
實(shí)際就是unsigned long
蒋川,64位操作操作系統(tǒng)下占用8
個(gè)字節(jié)
所以我們_buckets
實(shí)際大小就是8字節(jié)
接下來(lái)我們看mask_t
。
-
點(diǎn)進(jìn)去
發(fā)現(xiàn)它就是uint32_t
類型撩笆。占用4
字節(jié)
匯總一下:
現(xiàn)在系統(tǒng)都是64位系統(tǒng)捺球。
所以
cache_t
內(nèi)存大小為: 12 + 2 + 2 =16 字節(jié)
回頭繼續(xù)分析object_class
的內(nèi)存大小
struct objc_class : objc_object {
// Class ISA; 缸浦。 // 指針 - 占用8字節(jié)
Class superclass; // 指針 - 占用8字節(jié)
cache_t cache; // 占用16字節(jié)
class_data_bits_t bits;
};
所以我們得出結(jié)論。
-
object_class內(nèi)部的找到bits,需要偏移8 + 8 + 16 =
32
位
現(xiàn)在懒构,我們來(lái)讀取bits
5. 讀取bits
-
x/4gx HTPerson.class
打印內(nèi)存地址 -
p/x 0x0000000100002390 + 32
獲取bits地址 -
p (class_data_bits_t *)$12
強(qiáng)轉(zhuǎn)為class_data_bits_t
類型
在objc_class
結(jié)構(gòu)中餐济,我們發(fā)現(xiàn)bits.data()
返回的是class_rw_t
類型
我們打印bits->data()
檢驗(yàn)一下
成功拿到,打印HTPerson
的class_rw_t信息:
- 我們想要尋找這個(gè)類的
方法
和屬性
,但是發(fā)現(xiàn)只有一個(gè)ro_or_rw_ext
痘番。
我們進(jìn)入class_rw_t
內(nèi)部, 折疊所有函數(shù)侥祭。
Xcode折疊所有函數(shù):
command + shift + ←
6. 讀取methods、properties和protocols
main.m
加入測(cè)試代碼:
@interface HTPerson : NSObject {
NSString * hobby;
}
@property(nonatomic, copy) NSString * name;
@property(nonatomic, copy) NSString * nickname;
+(void) ClassFunction;
-(void) objectFunction;
@end
@implementation HTPerson
+(void) ClassFunction {
NSLog(@"類方法");
}
-(void) objectFunction{
NSLog(@"對(duì)象方法");
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
HTPerson * person = [[HTPerson alloc]init];
NSLog(@"%p", person);
}
return 0;
}
NSLog
處加入斷點(diǎn)
篙悯,運(yùn)行
代碼×迦蓿快速找到data()
對(duì)象的位置:
p/x HTPerson.class
- ->
p/x (class_data_bits_t *)(0x0000000100002258 + 32)
- ->
p $1->data()
methods
利用$2
對(duì)象打印p $2->methods()
:
我從圖中看到了list
鸽照。繼續(xù)打印p *$3.list
:
- 看到
count
有6個(gè)值,我們使用get
打印一下:
properties
利用$2
對(duì)象打印p $2->properties()
:
接下來(lái)打印p * properties()
屬性方法颠悬。
protocols
利用$2
對(duì)象打印p $2->protocols()
:
成員變量hobby
去哪了矮燎?
打印ro
打印p *$23.ivars
打印每一個(gè)成員變量p $25.get(0)
- 我們發(fā)現(xiàn),除了
hobby
變量赔癌。所有property
屬性诞外,都自動(dòng)生成了帶下劃線
的成員變量。
為什么案例中的ClassFunction
類方法沒(méi)出現(xiàn)在Methods中灾票?
對(duì)象的isa
指向自己的類峡谊,所以對(duì)象方法存放到了HTPerosn
中,難道類方法存放在它的isa
上一級(jí)刊苍?HTPerson元類中
既们?
實(shí)踐出真知:
-
p HTPerson.class
->x/4gx $0
->p/x (class_data_bits_t *)(0x0000000100002230 + 32)
->p $1->data()
->p $2->methods()
->p $3.list
->p *$4
->p $5.get(0)
果然。 對(duì)象
的方法存在類
中正什,類
的方法存在元類
中
下一章