iOS-底層-isa指針和superclass指針+窺探Class

上一篇文章,我們介紹了OC對象的分類,它們內(nèi)存中存放的信息如下圖:

信息.png

但是這個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)系起來的:

  1. instance的isa指向class
    當調(diào)用對象方法時踱稍,通過instance的isa找到class伦意,最后找到對象方法的實現(xiàn)進行調(diào)用
  2. 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)用

如下圖:

isa指針.png

二. superclass指針的作用

先看結論:

isa和superclass.png

1. 解釋上圖結論

關于isa指針:
  1. instance的isa指向class
  2. class的isa指向meta-class
  3. meta-class的isa指向基類(NSObject)的meta-class
  4. 基類(NSObject)的meta-class的isa指向它自己
關于superclass指針:
  1. class的superclass指向父類的class
  2. 如果沒有父類洞渔,superclass指針為nil
    (最后一直找不到方法會報錯:unrecognized selector sent to instance/class)
  3. meta-class的superclass指向父類的meta-class
  4. 基類的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)用

子類實例對象調(diào)用父類的對象方法.png

② [MJStudent personClassMethod]子類類對象調(diào)用父類的類方法

當Student的class要調(diào)用Person的類方法時张抄,會先通過Student的class的isa找到Student的meta-class,然后通過它的superclass找到Person的meta-class洼怔,最后找到類方法的實現(xiàn)進行調(diào)用

子類類對象調(diào)用父類的類方法.png

補充:

如果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)用軌跡.png.png

類方法調(diào)用軌跡為:

類方法調(diào)用軌跡.png.png

四. 關于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做個位運算才是元類對象的地址值宽档。

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");

打斷點,結果如下:

superclass

可以發(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é)議辜限、對象方法我們都找到了皇拣。

看圖總結一下結構:

窺探struct objc_class的結構.png
對象內(nèi)存結構.png

總結:

現(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;
}

打斷點茅信,查看類對象結構如下:

類對象的內(nèi)存結構.png

元類對象結構如下:

元類對象的內(nèi)存結構.png

結果正如所料,下面我們就能回答面試題了险胰。

問題二. 對象的isa指針指向哪里汹押?

  1. instance的isa指向class
  2. class的isa指向meta-class
  3. meta-class的isa指向基類(NSObject)的meta-class
  4. 基類(NSObject)的meta-class的isa指向它自己

問題三. superclass指針指向哪里矿筝?

  1. class的superclass指向父類的class
  2. 如果沒有父類起便,superclass指針為nil
    (最后一直找不到方法會報錯:unrecognized selector sent to instance/class)
  3. meta-class的superclass指向父類的meta-class
  4. 基類的meta-class的superclass指向基類的class

問題四. OC的類信息存放在哪里?

  1. 屬性窖维、成員變量榆综、協(xié)議、對象方法铸史,存放在class對象中
  2. 類方法鼻疮,存放在meta-class對象中
  3. 成員變量的具體值,存放在instance對象中

Demo地址: isa和superclass

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末琳轿,一起剝皮案震驚了整個濱河市判沟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌崭篡,老刑警劉巖挪哄,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異琉闪,居然都是意外死亡迹炼,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來斯入,“玉大人砂碉,你說我怎么就攤上這事】塘剑” “怎么了增蹭?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長磅摹。 經(jīng)常有香客問我沪铭,道長,這世上最難降的妖魔是什么偏瓤? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任杀怠,我火速辦了婚禮,結果婚禮上厅克,老公的妹妹穿的比我還像新娘赔退。我一直安慰自己,他們只是感情好证舟,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布硕旗。 她就那樣靜靜地躺著,像睡著了一般女责。 火紅的嫁衣襯著肌膚如雪漆枚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天抵知,我揣著相機與錄音墙基,去河邊找鬼。 笑死刷喜,一個胖子當著我的面吹牛残制,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播掖疮,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼初茶,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了浊闪?” 一聲冷哼從身側(cè)響起恼布,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎搁宾,沒想到半個月后折汞,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡猛铅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年字支,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡堕伪,死狀恐怖揖庄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情欠雌,我是刑警寧澤蹄梢,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站富俄,受9級特大地震影響禁炒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜霍比,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一幕袱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧悠瞬,春花似錦们豌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至凌外,卻和暖如春辩尊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背康辑。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工摄欲, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人晾捏。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓蒿涎,卻偏偏與公主長得像哀托,于是被迫代替她去往敵國和親惦辛。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容