-
isa指針
通過上一篇文章的分析我們已經(jīng)知道了實(shí)例對象,類對象,元類對象的結(jié)構(gòu)如上圖所示,每個對象中都有 isa 指針,isa 指針有什么作用?他們之間的關(guān)系是怎樣的呢?我們寫一個Person
類然后調(diào)用它的實(shí)例方法eat
,看一下底層源碼:
[person eat];
//本質(zhì)
objc_msgSend(person, sel_registerName("eat"));
[person eat]
本質(zhì)就是向person
對象發(fā)送一條eat
消息,通過上面的結(jié)構(gòu)圖我們知道,對象方法是存放在類對象中的,并不在實(shí)例對象中,那實(shí)例對象是怎么拿到類對象中的對象方法的呢?同樣,類方法也是如此怎么拿到存放在元類對象中的類方法的呢?.其實(shí)這就用到了isa
指針,大家記住:實(shí)例對象的isa
指針指向類對象,類對象的isa
指針指向元類對象,元類對象的isa
指針指向基類的元類對象
-
superClass
superClass 只存在類對象和元類對象中,實(shí)例對象中沒有superClass.類對象的 superClass 指向父類的類對象,父類的 superClass 指向基類的類對象,如果基類沒有父類,superClass 為nil;元類的 superClass 指向父類的元類對象,父類的元類對象的 superClass 指向基類的元類對象,基類的元類對象的 superClass 指向基類.
現(xiàn)在我們再添加一個Student
類繼承自Person
類,如下:
// Person
@interface Person : NSObject <NSCopying>
{
int _age;
int _no;
int _height;
}
-(void)personInstanceMethod;
+(void)personClassMethod;
@end
@implementation Person
- (void)personInstanceMethod{
}
+ (void)personClassMethod{
}
@end
// Student
@interface Student : Person <NSCopying>
{
int _sex;
}
-(void)studentInstanceMethod;
+(void)studentClassMethod;
@end
@implementation Student
- (void)studentInstanceMethod{
}
+ (void)studentClassMethod{
}
@end
然后再創(chuàng)建一個student
對象,讓student
對象去調(diào)用Person
的實(shí)例方法:
Student *student = [[Student alloc]init];
[student personInstanceMethod];
我們知道Person
的實(shí)例方法是存放在Person
的類對象中的,student
對象如何找到這個方法呢?我們畫一張圖:
首先
student
根據(jù)isa
找到Student class
對象,然后再通過Student class
對象的superClass
找到父類Person class
對象,再從Person class
對象中找到Person
的實(shí)例方法.如果父類Person class
中依然沒有,就依次往上找,直到NSObject
,如果NSObject
中還沒有,就會報(bào)一個很經(jīng)典的錯誤unrecognized selector sent to instance
.我們再改變一下,讓改一下代碼,讓
Student
去執(zhí)行Person
的類方法:
[Student personClassMethod];
這次方法執(zhí)行的查找順序是怎樣的呢?我就不畫圖了,其實(shí)跟[student personInstanceMethod]
差不多.首先Student
類對象先通過isa
找到Student 元類對象
,然后通過Student 元類對象
的superClass
找到父類的元類對象,也就是Person 元類對象
,然后在Person 元類對象
找到類方法執(zhí)行.
關(guān)于isa 和 superClass
的關(guān)系,網(wǎng)上有人總結(jié)了一張圖,可以很明顯的展示,虛線表示 isa , 實(shí)現(xiàn) 表示 superClass:
我們參照這張圖,對isa 和 superClass
做一個總結(jié):
- instance 的 isa 指向 class , class 的 isa 指向 meta-class , meta-class 的 isa 指向基類的 meta-class.
-
class 的 superClass 指向父類
如果沒有父類, superClass 為 nil. -
meta-class 的 superClass 指向父類的 meta-class
基類的 meta-class 的 superClass 指向基類的 class. -
instance 調(diào)用方法的軌跡
通過 isa 找到 class ,如果方法不存在就通過 superClass 找父類 -
class 調(diào)用類的方法軌跡
通過 isa 找到 meta-class,如果沒有,通過 superClass 找到父類的 meta-class.
注意上圖中有一根 superClass 線我用紅色的??標(biāo)識出來了,這根線特別特殊:基類的元類對象的 superClass 指向基類.下面我們通過代碼驗(yàn)證一下.
我們給NSObject
創(chuàng)建一個分類NSObject+test
,然后在分類的.m
方法中寫一個實(shí)例方法,打印輸出調(diào)用者:
- (void)test{
NSLog(@"NSObject 的對象方法test,調(diào)用者: %p",self);
}
然后在Person
類的頭文件中聲明一個+ (void)test;
方法,在讓Person
調(diào)用這個方法,如下:
NSLog(@"Student 地址: %p",[Student class]);
NSLog(@"Person 地址: %p",[Person class]);
[Student test];
我們來分析一下,Person
的頭文件中聲明了一個test
方法,并沒有在實(shí)現(xiàn)文件中實(shí)現(xiàn),而NSObject+test
的.m
文件中實(shí)現(xiàn)了一個對象方法test
,如果我們執(zhí)行[Student test];
會執(zhí)行成功么?
運(yùn)行一下看看:
2019-02-27 15:23:19.834089+0800 OC對象的分類_01[2112:300025] Student 地址: 0x100001478
2019-02-27 15:23:19.834469+0800 OC對象的分類_01[2112:300025] Person 地址: 0x100001428
2019-02-27 15:23:19.834505+0800 OC對象的分類_01[2112:300025] NSObject 的對象方法test,調(diào)用者: 0x100001478
會發(fā)現(xiàn)并沒有報(bào)unrecognized selector sent to instance
這個錯誤,居然調(diào)用成功了!很奇怪,為什么我們使用Student
調(diào)用?方法,最后卻執(zhí)行了NSObject
的?方法呢?這就是我在上圖中用紅??標(biāo)識的哪條線的作用.
我們分析一下它的方法調(diào)用軌跡:
1: 首先Student
通過isa
指針找到Student
的meta-class
,在Student
的meta-class
中查找test
方法,結(jié)果沒找到.
2: 又通過Student
的meta-class
中的superClass
找到他的父類的元類對象,也就是Person
的meta-class
,結(jié)果又沒找到
3: 繼續(xù)通過Person
的meta-class
中的superClass
找到NSObject
的meta-class
,結(jié)果還沒找到
4: 最后NSObject
的meta-class
的superClass
指向了基類NSObject
,它又去NSObject
中查找,結(jié)果找到了- (void)test
,就執(zhí)行了.
大家可能會疑惑,為什么調(diào)用的類方法,最后卻執(zhí)行了對象方法?
因?yàn)镺C調(diào)用方法的本質(zhì)就是發(fā)送消息,[Student test]
本質(zhì)就是objc_msgSend(objc_getClass("Student"), sel_registerName("test"));
它只知道去執(zhí)行test
,并不關(guān)心是加號方法還是減號方法.
我們一直在說實(shí)例對象的isa指針指向類對象,類對象的isa指針指向元類對象,也就是說實(shí)例對象的isa地址和類對象的地址相同,類對象的isa地址和元類的地址相同,我們來驗(yàn)證一下.
1: 分別創(chuàng)建Person
的實(shí)例對象,類對象,元類對象:
//實(shí)例對象
Person *person = [[Person alloc]init];
//類對象
Class personClass = [person class];
//元類對象
Class personMetaClass = object_getClass(personClass);
2: 通過命令行打印person->isa
和 personClass
地址
person->isa
地址是: 0x001d800100001429personClass
地址是: 0x0000000100001428咦~怎么不一樣呢?
這里需要注意一下,在 arm64 位之前,實(shí)例對象的 isa 地址和類對象的地址就是相同的,但是 arm64 位之后,實(shí)例對象的 isa 地址需要按位與
ISA_MASK
后才能得到類對象的地址,我們在object之isa指針詳解篇幅中詳細(xì)講解過,有興趣的同學(xué)可以看一下.那
ISA_MASK
是什么呢?打開 runtime 源碼搜索一下就能看到:
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
如果是 arm64 位系統(tǒng),ISA_MASK
就是0x0000000ffffffff8ULL
,如果是x86_64
系統(tǒng)ISA_MASK
就是0x00007ffffffffff8ULL
,因?yàn)槲覀兪窃趍ac 上運(yùn)行,所以我們就使用x86_64
的:
可以發(fā)現(xiàn),通過位運(yùn)算后得到的地址
0x0000000100001428
和personClass
地址:0x0000000100001428
就是同一個地址!下面驗(yàn)證類對象的 isa 指針指向元類對象
我們打印
personClass 的 isa
,會發(fā)現(xiàn)報(bào)錯,因?yàn)橄到y(tǒng)沒有暴露isa
這個成員:為了解決這個問題,我們定義一個和
class
結(jié)構(gòu)相同的hh_objc_class
:
struct objc_class {
Class _Nonnull isa;
Class _Nullable super_class
}
然后把Class personClass
類型轉(zhuǎn)換為struct hh_objc_class
類型:
struct hh_objc_class *hh_personClass = (__bridge struct hh_objc_class *)(personClass);
然后再打印:
p/x hh_personClass->isa
發(fā)現(xiàn)結(jié)果為:0x001d800100001401
p/x personMetaClass
的結(jié)果為:0x0000000100001400
同樣在按位或運(yùn)算:
按位或運(yùn)算后的結(jié)果和
p/x personMetaClass
的結(jié)果:0x0000000100001400
相同,也印證了我們之前的結(jié)論.
現(xiàn)在來驗(yàn)證一下類對象的 superClass 指向父類對象也就是說Student class
的superClass
地址和Person class
的地址相同.
還是之前代碼,我們直接打印p/x hh_studentClass->super_class
和p/x personClass
,結(jié)果如圖:
通過打印的結(jié)果可以看出,superClass
和isa
不同,superClass
并沒有& ISA_MASK
,而是直接相等.
我們一直在說屬性信息见咒、協(xié)議信息榜晦、成員變量信息获茬、對象方法存放在類對象中,類方法存放在元類對象中,但是一直沒有親眼看到,接下來我們就來驗(yàn)證一下,親眼看看.
首先我們知道,類對象和元類對象的結(jié)構(gòu)是一模一樣的,因?yàn)樗麄兌际?code>class類型.所以我們點(diǎn)擊進(jìn)入class
類型,發(fā)現(xiàn)它是這樣:
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
可以看到這個結(jié)構(gòu)體中的確是有method_list
,ivar_list
,protocol_list
等等,完全符合我們之前的說法,但是請注意:#if !__OBJC2__
,也就是說這段代碼的執(zhí)行有一個前提不是objc2.0
才會執(zhí)行,而現(xiàn)在我們用的都是OC2.0了,所以這段代碼是不會執(zhí)行的.也就是說這段代碼已經(jīng)過時了OBJC2_UNAVAILABLE
.所以我們需要從 runtime 的源碼中查看.
打開 runtime 源碼搜索struct objc_class
找到struct objc_class : objc_object {
這個結(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() {
return bits.data();
}
}
我們主要研究class_rw_t
,rw_t
是read_write_table
的縮寫,意思是可讀可寫的表.bits.data()
返回class_rw_t
.class_rw_t
里面存放的什么信息呢?我們點(diǎn)擊進(jìn)入class_rw_t
看看:
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;//方法信息
property_array_t properties;//屬性信息
protocol_array_t protocols;//協(xié)議信息
}
可以看到我們想看的方法,屬性,協(xié)議信息.只是還沒看到成員變量信息. class_rw_t
中有一個class_ro_t
,ro_t
是readOnly_table
的縮寫,意思是只讀的表.我們點(diǎn)擊進(jìn)入const class_ro_t
看看里面存放哪些信息:
struct class_ro_t {
const char * name;//類名
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;//成員變量
}
我畫了一張圖,能一目了然展示Class
的內(nèi)存結(jié)構(gòu):
這就是class
類型在 runtime 中的源碼,到這里我們已經(jīng)看到了所有我們想到看的信息,但是這只是在源碼上證明了結(jié)論,最好我們能把代碼跑起來,在內(nèi)存中看看是否是這樣,要想做到這種效果,就要借助李哥用 c++ 仿寫的一個class
類型:MJClassInfo.h
文件.
步驟:
1: 導(dǎo)入MJClassInfo.h
文件到我們工程中,這是編譯會出錯,我們把main.m
后綴改為main.mm
,讓編譯器編譯OC 和 C++ 文件.
2: 創(chuàng)建Student class
和Person class
然后轉(zhuǎn)換為mj_objc_class
類型
mj_objc_class *studentClass = (__bridge mj_objc_class *)[Student class];
mj_objc_class *personClass = (__bridge mj_objc_class *)[Person class];
3: 調(diào)用mj_objc_class
的data()
方法,返回class_rw_t
,這是李哥封裝的方法,在data()
內(nèi)部調(diào)用bits.data()
和runtime源碼實(shí)現(xiàn)一致.
class_rw_t *studentClass_rw_t_Data = studentClass->data();
class_rw_t *personClass_rw_t_Data = personClass->data();
4:打斷點(diǎn),查看class_rw_t
內(nèi)存數(shù)據(jù):
這樣我們也從內(nèi)存上查驗(yàn)了
class
的內(nèi)存結(jié)構(gòu),meta-class的查驗(yàn)方法和class
一樣,我就不寫步驟了,直接上圖大家擼一眼:大總結(jié):
1: 對象的 isa 指針指向哪里?
- instance 對象的 isa 指針指向 class 對象
- class 對象的 isa 指針指向 meta-class對象
- meta-class 對象的 isa 指向基類的 meta-class 對象
2: OC的類信息存放在哪里?
- 屬性信息,協(xié)議信息,成員變量信息,對象方法存放在類對象中.
- 類方法存放在 meta-class 對象中.
- 成員變量的具體值存放在 instance 對象.
3: 類對象和元類對象什么時候加載?什么時候釋放?
- 當(dāng)程序執(zhí)行
main
函數(shù)后就會加載類信息,此時加載到內(nèi)存; - 程序運(yùn)行過程中一直存在,直到程序退出時銷毀.
4: 實(shí)例對象中的成員變量和類對象中的成員變量有什么不同?
- 實(shí)例對象中存儲的成員變量的具體值
- 類對象中的存儲的成員變量的類型,名字等信息,只存儲一份