iOS底層 - 類的結構分析

iOS開發底層探究之路

類的結構分析

類分析初探

首先我們先創建一個類LGPerson,再創建一個繼承于LGPerson的子類LcrTeacher

@interface LGPerson : NSObject
{
    NSString *hobby;
}
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, strong) NSString *name;
-(void)sayHello;
+(void)say88;
@end
#import "LGPerson.h"
@implementation LGPerson
-(void)sayHello{    
}
+(void)say88{   
}
@end

#import "LGPerson.h"
@interface LcrTeacher : LGPerson
@end
#import "LcrTeacher.h"
@implementation LcrTeacher
@end

在main.m文件中我們創建兩個對象:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        //ISA_MASK  0x00007ffffffffff8ULL
        LGPerson *person = [LGPerson alloc];
        LcrTeacher *teacher = [LcrTeacher alloc];
        
        NSLog(@"%@ ----  %@",person,teacher);
    }
    return 0;
}

利用lldb調試我們打印出下面圖片中一些信息:

類結構初探

如上圖所示,12處的打印結果都為LGPerson1處是因為person對象的指針指向類,所以當此對象的指針地址&ISA_MASK時,所得到的就是LGPerson類地址,打印即為LGPerson。2 處打印的地址是類的isa&ISA_MASK后得到的元類的信息,那么這個元類到底是什么?

元類

對象的isa指向的是所對應的類的信息,類其實也是一個對象,它的內存信息中保存的位域信息所指向的就是由蘋果定義的元類
1.元類是由系統定義創建的,這個過程是在編譯期間完成的。
2.元類的類對象,元類中保存著類的相關類方法信息
3.元類是沒有自己的名字,由于與當前類相關聯,所以蘋果直接拿當前類名命名元類。

由此,上面的12處打印的都為LGPerson就是元類導致的。
通過lldb可以調試元類信息鏈,即isa走向,對象--->類--->元類--->根元類--->根類(NSObject):

isa走位圖分析

上圖可見:

  • 對象的isa指向
  • 的isa指向元類
  • 元類的isa指向根元類
  • 根元類的isa指向自己

著名isa及Superclass走位圖

isa流程圖.png

結合上面的工程,將我們自定義的類信息放上去,isaSuperclass走向一目了然:

  • isa走向:

    • teacher走位鏈:teacher對象) ---> LcrTeacher) --->LcrTeacher元類) --->NSObject根元類)--->NSObject自己) 。
    • person 走位鏈:person對象)---> LGPerson) ---> LGPerson元類) --->NSObject根元類)--->NSObject自己) 。
  • Superclass走向:

    • 的走向鏈:Lcrteacher(子類)---> LGPerson(父類) ---> NSObject(根類) --->nil
    • 元類的走向鏈:LcrTeacher(子類元類) ---> LGPerson(父類元類) ---> NSObject(根元類) ---> NSObject(根類) ---> nil

objc_object & objc_class

在繼續下面類的結構分析之前,我們先來了解objc_object 和 objc_class兩個概念,以及它們之間是怎樣的關系?

objc_class:
在上篇文章iOS底層 - isa與類關聯的原理 中,通過clang編譯過的main.cpp文件中可以看到類的底層結構:

struct NSObject_IMPL {
    Class isa;
};
typedef struct objc_class *Class;

NSObject 被編譯成了一個NSObject_IMPL結構體,里面有一個Class類型的isa,類型Class 是由objc_class 定義的。
在源碼objc4-781源碼中我們搜索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();
    }
    void setData(class_rw_t *newData) {
        bits.setData(newData);
    }

    void setInfo(uint32_t set) {
        ASSERT(isFuture()  ||  isRealized());
        data()->setFlags(set);
    }

    void clearInfo(uint32_t clear) {
        ASSERT(isFuture()  ||  isRealized());
        data()->clearFlags(clear);
    }
//略
}

objc_class的源碼中看出objc_class 是繼承自objc_object的。

objc_object:
查看源碼,在objc.h及objc_private.h兩個文件中分別能找到objc_object的源碼實現:

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
struct objc_object {
private:
    isa_t isa;

public:

    // ISA() assumes this is NOT a tagged pointer object
    Class ISA();
 //略
}

那么objc_classobjc_object 以及 NSObject對象到底是什么關系?

  • objc_class 繼承自objc_object,所以objc_class中有一個繼承過來的isa屬性。
  • 通過clang編譯的main.cpp 中看出NSObject_IMPL 中有一個Class類型的isa,而Class是由objc_class定義的,而objc_class 又繼承自objc_object,所以可知所有以NSObject所創建的對象都有一個isa屬性。
  • objc_object是一個對象創造的根類,所有對象創建之后都帶有objc_object的特性,帶有一個isa屬性。

總結

  • 所有的對象++元類都有isa
  • 所有的對象都是由objc_object繼承來的。
  • 簡單概括就是萬物皆對象,萬物皆來源于objc_object,有以下兩點結論:
    • 所有以 objc_object為模板 創建的對象,都有isa屬性
    • 所有以objc_class為模板,創建的類,都有isa屬性
  • 在結構層面可以通俗的理解為上層OC底層的對接:
    • 下層是通過 結構體 定義的 模板,例如objc_classobjc_object
    • 上層 是通過底層的模板創建的 一些類型,例如LGPerson
      可以用下圖表示出:
      objc_object & objc_class & isa

類的結構分析

上面分析可知類是以objc_class為模版創建的,那么我們就從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_object,默認帶有一個isa,大小為8字節。
  • 父類superclass,objc_class定義的一個Class類型的結構體指針,大小也為8字節。
  • cache 類的緩存信息,結構體cache_t類型,我們在下面會計算cache屬性大小。(cache是一個結構體,而不是結構體指針,不能簡單地認為大小8字節)
  • bits 存放類的一些信息,class_data_bits_t 結構體類型,接下來我們就針對這個bits里存放的信息進行一步步探究。

cache大小

struct cache_t {
#if CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_OUTLINED
    explicit_atomic<struct bucket_t *> _buckets;  //8
    explicit_atomic<mask_t> _mask;    //4
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_HIGH_16
    explicit_atomic<uintptr_t> _maskAndBuckets; // 8
    mask_t _mask_unused; // 4
//過濾一些靜態的,因為靜態不計入結構體的大小
#elif CACHE_MASK_STORAGE == CACHE_MASK_STORAGE_LOW_4
    // _maskAndBuckets stores the mask shift in the low 4 bits, and
    // the buckets pointer in the remainder of the value. The mask
    // shift is the value where (0xffff >> shift) produces the correct
    // mask. This is equal to 16 - log2(cache_size).
    explicit_atomic<uintptr_t> _maskAndBuckets;
    mask_t _mask_unused;

    static constexpr uintptr_t maskBits = 4;
    static constexpr uintptr_t maskMask = (1 << maskBits) - 1;
    static constexpr uintptr_t bucketsMask = ~maskMask;
#else
#error Unknown cache mask storage type.
#endif
    
#if __LP64__
    uint16_t _flags;          //2
#endif
    uint16_t _occupied;   //2
//省略下面的方法,結構體大小只和屬性有關
}

有上面代碼可得,cache大小為16,所以8 + 8 + 16 ,想獲得bts里的信息的話,我們可利用指針偏移32來獲取類信息中的bits,這樣就可以訪問類相關的一些信息了。

lldb調試查看類信息bits

其中data()方法獲取信息,是objc_class提供的方法:

class_rw_t *data() const {
        return bits.data();
    }

目前已經拿到了bits信息,但是還不夠,我們暫時還看不到一些具體的類的信息,所以我們繼續往下探索:

bits 查看屬性列表 property_list

由于上方獲取到的bits是class_rw_t類型結構體, 查看class_rw_t 源碼,我們可以找到類的一些相關信息:


class_rw_t相關類的信息

可以看到,方法列表methods、屬性列表properties、協議列表protocols等類的信息。
既然有這些信息,我們不妨試著去獲取它們,并且將它們打印:

bits的屬性列表獲取

所以,通過點方法.properties(),就可以獲取到類的屬性列表,再通過p *$6 打印$6里的數據,可以獲得property_list_t類型的屬性列表,進而打印.get()方法,獲取每一個屬性。.get()一直獲取屬性的話,會造成數組越界的結果,因為類的屬性個數是固定的,文章頭部的LGPerson有兩個屬性(nickNamename),所以此處只能.get()到這兩個屬性.

bits 查看屬性列表 method_list

@interface LGPerson : NSObject
{
    NSString *hobby;
}
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, strong) NSString *name;

-(void)sayHello;
+(void)say88;

@end

下面再利用lldb調試,是否也能像屬性列表一樣方式去查看類的方法列表:

bits獲取方法列表

由上圖可知,可以利用.methods()獲取bits信息中的方法列表,獲取到的也是一個method_list_t類型的方法列表信息,然后利用.get()方法一個個獲取到方法。LGPerson 共有6個方法,實例方法sayHello.cxx_destructname屬性的setgetnickName屬性的setget

最后留下兩個問題:成員變量hobby在哪里?以及類方法+say88又在哪里?下篇文章我們一一揭曉。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,892評論 2 372