上一篇文章,我們介紹了OC對象的分類,它們內(nèi)存中存放的信息如下圖:
但是這個isa和superclass有什么用呢?
首先我們創(chuàng)建兩個類,如下:
// MJPerson
@interface MJPerson : NSObject <NSCopying>
{
@public
int _age;
}
@property (nonatomic, assign) int no;
- (void)personInstanceMethod;
+ (void)personClassMethod;
@end
@implementation MJPerson
- (void)test
{
}
- (void)personInstanceMethod
{
}
+ (void)personClassMethod
{
}
- (id)copyWithZone:(NSZone *)zone
{
return nil;
}
@end
// MJStudent
@interface MJStudent : MJPerson <NSCoding>
{
@public
int _weight;
}
@property (nonatomic, assign) int height;
- (void)studentInstanceMethod;
+ (void)studentClassMethod;
@end
@implementation MJStudent
- (void)test
{
}
- (void)studentInstanceMethod
{
}
+ (void)studentClassMethod
{
}
- (id)initWithCoder:(NSCoder *)aDecoder
{
return nil;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
}
@end
其中MJStudent繼承于MJPerson
一. isa指針的作用
調(diào)用方法:
MJPerson *person = [[MJPerson alloc] init];
person->_age = 10;
[person personInstanceMethod];
[MJPerson personClassMethod];
我們都知道方法調(diào)用的本質(zhì)是發(fā)送消息,所以上面的方法調(diào)用的本質(zhì)就是給person對象發(fā)送personInstanceMethod消息,給MJPerson類對象發(fā)送personClassMethod消息
他們底層是這樣的:(也可以通過NSObject的本質(zhì)介紹的指令重寫為c++文件查看)
調(diào)用對象方法本質(zhì)
objc_msgSend(person, @selector(personInstanceMethod))
調(diào)用類方法的本質(zhì) (objc_msgSend內(nèi)部是通過匯編實現(xiàn)的,半開源)
objc_msgSend([MJPerson class], @selector(personClassMethod))
但是現(xiàn)在有一個問題, personInstanceMethod對象方法不在person實例對象里面, personInstanceMethod類方法也不在MJPerson類對象里面,怎么辦?
其實他們是通過isa指針聯(lián)系起來的:
- instance的isa指向class
當調(diào)用對象方法時踱稍,通過instance的isa找到class伦意,最后找到對象方法的實現(xiàn)進行調(diào)用 - class的isa指向meta-class
當調(diào)用類方法時,通過class的isa找到meta-class潭千,最后找到類方法的實現(xiàn)進行調(diào)用
現(xiàn)在我們就明白了:
[person personInstanceMethod]方法調(diào)用的流程:
person實例對象先通過自己的isa找到MJPerson類對象,然后在MJPerson類對象里面找到personInstanceMethod方法進行調(diào)用
[MJPerson personClassMethod]方法的調(diào)用流程:
MJPerson類對象先通過自己的isa找到MJPerson的元類對象,然后在元類對象里面找到personInstanceMethod方法進行調(diào)用
如下圖:
二. superclass指針的作用
先看結論:
1. 解釋上圖結論
關于isa指針:
- instance的isa指向class
- class的isa指向meta-class
- meta-class的isa指向基類(NSObject)的meta-class
- 基類(NSObject)的meta-class的isa指向它自己
關于superclass指針:
- class的superclass指向父類的class
- 如果沒有父類洞渔,superclass指針為nil
(最后一直找不到方法會報錯:unrecognized selector sent to instance/class) - meta-class的superclass指向父類的meta-class
- 基類的meta-class的superclass指向基類的class
instance調(diào)用對象方法的軌跡:
實例對象的isa找到class殴蓬,方法不存在柏蘑,就通過superclass找父類
class調(diào)用類方法的軌跡:
類對象的isa找meta-class,方法不存在空镜,就通過superclass找父類
2. 方法調(diào)用軌跡分析
① [student personInstanceMethod]子類實例對象調(diào)用父類的對象方法
當Student的instance對象要調(diào)用Person的對象方法時浩淘,會先通過實例對象的isa找到Student的class,然后通過它的superclass找到Person的class吴攒,最后找到對象方法的實現(xiàn)進行調(diào)用
② [MJStudent personClassMethod]子類類對象調(diào)用父類的類方法
當Student的class要調(diào)用Person的類方法時张抄,會先通過Student的class的isa找到Student的meta-class,然后通過它的superclass找到Person的meta-class洼怔,最后找到類方法的實現(xiàn)進行調(diào)用
補充:
如果MJStudent和MJPerson里面都有一個test對象方法署惯,調(diào)用方法:[student test],如果按照面向?qū)ο蟮倪壿嬚{(diào)用的是student的test镣隶,當明白isa和superclass就知道為什么調(diào)用的是student的test了极谊。
小任務:
- (instancetype)init方法在NSObject的類里面诡右,想一下調(diào)用過程
[student init];
+ (void)load在NSObject的元類里面,想一下調(diào)用過程
[MJStudent load];
三. 驗證:基類的meta-class的superclass指向基類的class
下面我們驗證一條最特殊的實線:基類的meta-class的superclass指向基類的class
給NSObject添加分類轻猖,只實現(xiàn)一個test對象方法帆吻,MJPerson不實現(xiàn)任何方法。
- (void)test
{
NSLog(@"-[NSObject test] - %p", self);
}
執(zhí)行以下代碼
NSLog(@"[MJPerson class] - %p", [MJPerson class]);
NSLog(@"[NSObject class] - %p", [NSObject class]);
[MJPerson test];
[NSObject test]
打印結果:
[MJPerson class] - 0x1000011e0
[NSObject class] - 0x7fff97e99140
-[NSObject test] - 0x1000011e0
-[NSObject test] - 0x7fff97e99140
可以發(fā)現(xiàn)咙边,無論是MJPerson還是NSObject調(diào)用test方法后猜煮,最后都會調(diào)用NSObject的對象方法
過程:MJPerson類對象調(diào)用test方法的時候會通過MJPerson的isa去NSObject元類對象里面找test方法(元類對象里面放的都是類方法),但是沒找到败许,然后就去NSObject元類對象的類對象里面找test方法王带,然后就找到了- (void)test方法,然后就調(diào)用了檐束。
可以發(fā)現(xiàn)辫秧,不管是類對象還是實例對象束倍,調(diào)用流程都是:
isa -> superclass -> suerpclass -> superclass -> .... superclass -> nil
總結:
所以被丧,對象方法調(diào)用軌跡為:
類方法調(diào)用軌跡為:
四. 關于ISA_MASK
上面我們說了實例對象的isa指向類對象,類對象的isa指向元類對象绪妹,其實在64位之前的確是這樣的甥桂,但是64位之后,實例對象的isa & ISA_MASK = 類對象邮旷,類對象的isa & ISA_MASK = 元類對象(就是需要做個位運算)黄选。
下面我們驗證一下,首先在objc4源碼中搜索ISA_MASK我們會發(fā)現(xiàn)以下代碼:
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
上面代碼的意思是婶肩,如果是arm64平臺用第一個办陷,x86平臺用第二個,由于我們代碼是命令行的律歼,所以用第二個民镜,驗證代碼如下:
struct mj_objc_class {
Class isa;
Class superclass;
};
MJPerson *person = [[MJPerson alloc] init];
Class personClass = [MJPerson class];
struct mj_objc_class *personClass2 = (__bridge struct mj_objc_class *)(personClass);
Class personMetaClass = object_getClass(personClass);
NSLog(@"%p %p %p", person, personClass, personMetaClass);
為什么要定義mj_objc_class結構體?
因為Class的雖然有isa险毁,但是沒有顯露出來制圈,如果我們直接訪問會報錯
(lldb) p/x (long)personClass->isa
error: member reference base type 'Class' is not a structure or union
我們重新?lián)Q個結構體,指向personClass就能獲取內(nèi)部的isa指針畔况。
上面代碼鲸鹦,打印結果為:
實例對象:0x100603ac0 類對象:0x1000014d0 元類對象:0x1000014a8
打斷點,打印如下:
其中跷跪,p/x是按照16進制打印馋嗜,person->isa是直接訪問指針指向的地址。
除了打印還可以直接在點擊enter吵瞻,即可出現(xiàn)地址嵌戈,如上圖
觀察上圖覆积,驗證了我們所說的,實例變量的isa和ISA_MASK做個位運算才是類對象的地址值熟呛,類對象的isa和ISA_MASK做個位運算才是元類對象的地址值宽档。
那么superclass是不是也需要做位運算呢?
測試代碼如下:
struct mj_objc_class *personClass = (__bridge struct mj_objc_class *)([MJPerson class]);
struct mj_objc_class *studentClass = (__bridge struct mj_objc_class *)([MJStudent class]);
NSLog(@"1111");
打斷點,結果如下:
可以發(fā)現(xiàn)庵朝,子類的supreclass指針里面存的直接是父類的地址吗冤,并不需要做位運算。
五. 窺探Class結構
類對象和元類對象都是Class類型的九府,所以他們在內(nèi)存中的結構也是一樣的椎瘟,只不過存放的信息不一樣,下面我們就窺探Class的結構侄旬。
點擊Class肺蔚,進去之后我們發(fā)現(xiàn),Class的定義是這樣的儡羔,是個結構體宣羊。
// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
再點擊objc_class,進去
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 *` */
上面有我們想要的信息,但是上面的結構體在最新的OBJC2已經(jīng)過時了(OBJC2_UNAVAILABLE)汰蜘,所以只能我們自己查看源碼了仇冯。
在objc4中,搜索struct objc_class族操,找到如下代碼:
//class和metra-class對象的本質(zhì)結構
//objc_class : objc_object 這是c++結構體的繼承(c++結構體和類幾乎沒區(qū)別)
struct objc_class : objc_object {
// Class ISA; // isa
Class superclass; //superclass
cache_t cache; // 方法緩存
class_data_bits_t bits; // 用于獲取具體的類信息
class_rw_t *data() {
return bits.data();
}
......
}
再次搜索"struct objc_object {",可以找到父類
struct objc_object {
private:
isa_t isa;
......
};
可以發(fā)現(xiàn)父類里面就一個isa苛坚,其他的都是方法,現(xiàn)在isa和superclass我們都找到了色难。
再次查看objc_class結構體泼舱,發(fā)現(xiàn)可以通過bits獲取data信息,點擊它的類型class_rw_t(r是read,w是write,t是table,整體是可讀可寫的表的意思),進去
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_ro_t(只讀表),進去
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; //instance對象占用的空間
#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;
method_list_t *baseMethods() const {
return baseMethodList;
}
};
這里我們找到了成員變量列表
至此涯贞,isa、superclass危喉、屬性宋渔、成員變量、協(xié)議辜限、對象方法我們都找到了皇拣。
看圖總結一下結構:
總結:
現(xiàn)在我們明白了,無論是類對象還是元類對象,他們在內(nèi)存中的結構都是objc_class這種結構體類型的氧急,但是他們存儲的信息不一樣颗胡,比如對于元類對象,只有isa吩坝、superclass毒姨、類方法有值,其他的都是空钉寝。
六. 驗證Class內(nèi)存結構
上面我們是在源碼中分析了Class和meta-Class內(nèi)存結構弧呐,但是實際上是不是這樣我們還需要在代碼中進行驗證。
如何在代碼中驗證嵌纲?就像上面我們想查看isa一樣俘枫,模仿系統(tǒng)自定義了一個結構體。在這里我們也是模仿系統(tǒng)的結構體自定義我們自己的結構體逮走,然后把系統(tǒng)的結構體指向我們自己的結構體鸠蚪,如果他們內(nèi)存結構一樣,那么強制轉(zhuǎn)換類型以后數(shù)據(jù)肯定都可以對的上师溅。代碼如下:
//#import "MJClassInfo.h"是c++文件
//如果文件名是main.m編譯的時候只能認識OC和C代碼,如果改成main.mm才認識c++文件
int main(int argc, const char * argv[]) {
@autoreleasepool {
MJStudent *stu = [[MJStudent alloc] init];
stu->_weight = 10;
mj_objc_class *studentClass = (__bridge mj_objc_class *)([MJStudent class]);
mj_objc_class *personClass = (__bridge mj_objc_class *)([MJPerson class]);
class_rw_t *studentClassData = studentClass->data();
class_rw_t *personClassData = personClass->data();
class_rw_t *studentMetaClassData = studentClass->metaClass()->data();
class_rw_t *personMetaClassData = personClass->metaClass()->data();
NSLog(@"1111");
}
return 0;
}
打斷點茅信,查看類對象結構如下:
元類對象結構如下:
結果正如所料,下面我們就能回答面試題了险胰。
問題二. 對象的isa指針指向哪里汹押?
- instance的isa指向class
- class的isa指向meta-class
- meta-class的isa指向基類(NSObject)的meta-class
- 基類(NSObject)的meta-class的isa指向它自己
問題三. superclass指針指向哪里矿筝?
- class的superclass指向父類的class
- 如果沒有父類起便,superclass指針為nil
(最后一直找不到方法會報錯:unrecognized selector sent to instance/class) - meta-class的superclass指向父類的meta-class
- 基類的meta-class的superclass指向基類的class
問題四. OC的類信息存放在哪里?
- 屬性窖维、成員變量榆综、協(xié)議、對象方法铸史,存放在class對象中
- 類方法鼻疮,存放在meta-class對象中
- 成員變量的具體值,存放在instance對象中
Demo地址: isa和superclass