Objective-C內(nèi)存結(jié)構(gòu)源碼解讀

我們平時(shí)編寫的OC代碼帽驯,底層實(shí)現(xiàn)其實(shí)都是C\C++代碼,所以O(shè)C的對象和類主要是基于C和C++的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn)的书闸,這里的數(shù)據(jù)結(jié)構(gòu)就是結(jié)構(gòu)體尼变,下面請看詳細(xì)報(bào)道。

OC代碼轉(zhuǎn)換為C和C++

有兩種方式浆劲,可以用clang(xcode內(nèi)置的llvm編譯器)直接將.m輸出為.cpp嫌术,但是不能指定平臺(tái),一個(gè)主函數(shù)中只new一個(gè)對象的方法牌借,轉(zhuǎn)換出來的代碼會(huì)有近十萬行度气,命令:clang -rewrite-objc main.m -o main.cpp 。但實(shí)際上編譯器在編譯的時(shí)候會(huì)考慮平臺(tái)膨报,所以我們使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp命令磷籍,指定編譯平臺(tái)和架構(gòu)适荣,這樣轉(zhuǎn)換出來的代碼只有三分之一不到。這是轉(zhuǎn)換前后的代碼院领。因?yàn)檗D(zhuǎn)換后的代碼太長弛矛,所以只摘取了千百行。
在關(guān)于OC的本質(zhì)比然,通常需要來查看NSObject類的結(jié)構(gòu)丈氓,因?yàn)镹SObject是所有類的基類,我們從cpp文件中搜索NSObject强法,找到一個(gè)代碼万俗,就能看到OC類底層實(shí)現(xiàn)其實(shí)就是C的結(jié)構(gòu)體。

struct NSObject_IMPL {
    Class isa; // 因?yàn)閏lass是個(gè)指針拟烫,所以占8個(gè)字節(jié)(64位系統(tǒng))
};
typedef struct objc_class *Class;
// 這里要提一嘴该编,實(shí)際上NSObject的類初始化時(shí)候是占16字節(jié),
//使用malloc_size((__bridge const void *)obj)方法就能獲得obj指針?biāo)赶騼?nèi)存的大小>>16硕淑。
//class_getInstanceSize([NSObject class])是獲得NSObject實(shí)例對象的成員變量(isa)所占用的大小 >>8字節(jié)

// OC源碼课竣,可以看出申請內(nèi)存,不能小于16字節(jié)
size_t instanceSize(size_t extraBytes) {
    size_t size = alignedInstanceSize() + extraBytes;
    // CF requires all objects be at least 16 bytes.
    if (size < 16) size = 16;
    return size;
}

從上面代碼可以看出一個(gè)NSObject類申請占16字節(jié)置媳,通過lldb調(diào)試查看內(nèi)存空間于樟,前8個(gè)字節(jié)為成員變量isa的8個(gè)字節(jié),后8個(gè)字節(jié)為占位拇囊。

<NSObject: 0x100573f70>
(lldb) memory read 0x100573f70
0x100573f70: 41 d1 a9 8d ff ff 1d 00 00 00 00 00 00 00 00 00  A...............

下面我們new一個(gè)student類

@interface Student : NSObject
{
    @public
    int _no;
    int _age;
}
@end

通過上邊的描述迂曲,我們生成一個(gè).cpp文件,可以查看這個(gè)student的對象類型是一個(gè)結(jié)構(gòu)體指針寥袭,源碼描述為:

struct Student_IMPL {
//這個(gè)繼承關(guān)系的NSObject就是
//struct NSObject_IMPL {
//    Class isa; // 因?yàn)閏lass是個(gè)指針路捧,所以占8個(gè)字節(jié)(64位系統(tǒng))
//};
    struct NSObject_IMPL NSObject_VARS; 
    int _no;
    int _age;
};

通過源碼很容易理解,生成的對象大小其實(shí)就是16字節(jié)传黄,isa占8個(gè)字節(jié)杰扫,int占4個(gè)字節(jié)。最后我們通過代碼驗(yàn)證一下膘掰,具體結(jié)果可以使用lldb查看內(nèi)存空間占的值章姓,這里就不貼出來了:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Student *stu = [[Student alloc] init];
        stu->_no = 4;
        stu->_age = 5;
        
        NSLog(@"%zd", class_getInstanceSize([Student class])); //返回這個(gè)對象至少需要多少存儲(chǔ)空間
        NSLog(@"%zd", malloc_size((__bridge const void *)stu));
        struct Student_IMPL *stuImpl = (__bridge struct Student_IMPL *)stu;
        NSLog(@"no is %d, age is %d", stuImpl->_no, stuImpl->_age);
    }
    return 0;
}

內(nèi)存對齊:

這里我們要說一下內(nèi)存對齊,來更深入的探討OC對象的內(nèi)存分配识埋。
大學(xué)里我們學(xué)過內(nèi)存對齊在結(jié)構(gòu)體中的體現(xiàn)是結(jié)構(gòu)體的大小必須是最大成員的倍數(shù)凡伊。
iOS操作系統(tǒng)分配內(nèi)存的時(shí)候也會(huì)有內(nèi)存對齊,大小是16字節(jié)的倍數(shù)窒舟,那么為什么是16的倍數(shù)系忙,其實(shí)就是操作系統(tǒng)認(rèn)為16的倍數(shù)是對操作系統(tǒng)訪問最快的方式,我們看看代碼怎么說

//new一個(gè)person類辜纲,
@interface Person : NSObject
{
    int _age;
    int _height;
    int _no;
}
//通過xrun生成cpp代碼為
struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _age;
    int _height;
    int _no;
};
//打印內(nèi)存大小
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *p = [[Person alloc] init];
        // sizeof是編譯階段確認(rèn)類型占用內(nèi)存的大小笨觅,如果傳入的是p拦耐,這為8個(gè)字節(jié)耕腾,因?yàn)閜是一個(gè)指針類型见剩,在編譯階段就已經(jīng)確定。
        NSLog(@"%zd", sizeof(struct Person_IMPL)); // 24
        // class_getInstanceSize 是在類的創(chuàng)建時(shí)候至少需要多少存儲(chǔ)空間
        // malloc_size是最終分配了多少內(nèi)存空間
        NSLog(@"%zd %zd",
              class_getInstanceSize([Person class]), // 24
              malloc_size((__bridge const void *)(p))); // 32
    }
    return 0;
}

我們在看看malloc的源碼扫俺,去GNU查找最新的C語言源碼苍苞,有一個(gè)叫g(shù)libc的庫,可以看到malloc-alignment.h


這個(gè)函數(shù)就是C函數(shù)申請一個(gè)對齊后的內(nèi)存狼纬,這里在這里我們可以在iOS工程內(nèi)用sizeof計(jì)算出alignof_ (long double) 和 SIZE_SZ的值分別是16和8羹呵,所以在iOS的環(huán)境下,內(nèi)存對其的最小是16字節(jié)疗琉。

#define MALLOC_ALIGNMENT (2 * SIZE_SZ < __alignof__ (long double) \
              ? __alignof__ (long double) : 2 * SIZE_SZ)

OC對象的分類

Objective-C中的對象冈欢,簡稱OC對象,主要可以分為3種

instance對象(實(shí)例對象)

上邊我們描述的都是實(shí)類對象
instance對象就是通過類alloc出來的對象盈简,每次調(diào)用alloc都會(huì)產(chǎn)生新的instance對象
instance對象在內(nèi)存中存儲(chǔ)的信息包括
isa指針
其他成員變量

class對象(類對象)
        NSObject *object1 = [[NSObject alloc] init];
        NSObject *object2 = [[NSObject alloc] init];
        Class objectClass1 = [object1 class];
        Class objectClass2 = [object2 class];
        Class objectClass3 = object_getClass(object1);
        Class objectClass4 = object_getClass(object2);
        Class objectClass5 = [NSObject class];

objectClass1 ~ objectClass5都是NSObject的class對象(類對象)
它們是同一個(gè)對象凑耻。每個(gè)類在內(nèi)存中有且只有一個(gè)class對象

class對象在內(nèi)存中存儲(chǔ)的信息主要包括:

  • isa指針
  • superclass指針
  • 類的屬性信息(@property)、類的對象方法信息(instance method)
  • 類的協(xié)議信息(protocol)柠贤、類的成員變量信息(ivar)
meta-class對象(元類對象)
Class objectMetaClass = object_getClass([NSObject class];);

objectMetaClass是NSObject的meta-class對象(元類對象)
每個(gè)類在內(nèi)存中有且只有一個(gè)meta-class對象

meta-class對象和class對象的內(nèi)存結(jié)構(gòu)是一樣的香浩,但是用途不一樣,在內(nèi)存中存儲(chǔ)的信息主要包括:

  • isa指針
  • superclass指針
  • 類的類方法信息(class method)
  • ......
總結(jié)幾個(gè)方法
從打印結(jié)果看臼勉,對象方法的內(nèi)存不同
        NSObject *object1 = [[NSObject alloc] init];
        NSObject *object2 = [[NSObject alloc] init];
獲取類對象的打印相同
        Class objectClass1 = [object1 class];
        Class objectClass2 = [object2 class];
        Class objectClass3 = object_getClass(object1);
        Class objectClass4 = object_getClass(object2);
        Class objectClass5 = [NSObject class];
獲取元類的方法邻吭,傳遞的是類對象參數(shù)。
        Class objectMetaClass = object_getClass([NSObject class];);

        NSLog(@"%p %p",
              object1,
              object2);
        
        NSLog(@"%p %p %p %p %p",
              objectClass1,
              objectClass2,
              objectClass3,
              objectClass4,
              objectClass5);
打友绨浴:
2019-12-28 21:46:18.859724+0800 Interview02-class對象[5114:373716] 0x103a1e570 0x103a1f360
2019-12-28 21:46:18.860065+0800 Interview02-class對象[5114:373716] 0x7fff9cc7a118 0x7fff9cc7a118 0x7fff9cc7a118 0x7fff9cc7a118 0x7fff9cc7a118

源碼:
獲取類對象囱晴、元類對象的方法,在objc中實(shí)際就是返回isa指針瓢谢。
Class object_getClass(id obj)
{
    if (obj) return obj->getIsa();
    else return Nil;
}
這里不要跟這個(gè)方法混淆畸写,objc_getClass是傳遞了個(gè)類的名稱。最后通過map查找到對應(yīng)的class恩闻,下邊是精簡后的代碼艺糜。
Class objc_getClass(const char *aClassName)
{
    if (!aClassName) return Nil;

    // NO unconnected, YES class handler
    return look_up_class(aClassName, NO, YES);
}
Class 
look_up_class(const char *name, 
              bool includeUnconnected __attribute__((unused)), 
              bool includeClassHandler __attribute__((unused)))
{
 getClass(name);
}
static Class getClass(const char *name)
{
    Class result = getClass_impl(name);
}
static Class getClass_impl(const char *name)
{
    Class result = (Class)NXMapGet(gdb_objc_realized_classes, name);
}


調(diào)用流程

  • instance的isa指向class
  • class的isa指向meta-class
    meta-class的isa指向基類的meta-class
  • class的superclass指向父類的class
    如果沒有父類,superclass指針為nil
  • meta-class的superclass指向父類的meta-class
    基類的meta-class的superclass指向基類的class
  • instance調(diào)用對象方法的軌跡
    isa找到class幢尚,方法不存在破停,就通過superclass找父類
  • class調(diào)用類方法的軌跡
    isa找meta-class,方法不存在尉剩,就通過superclass找父類

isa指針

  • 從64bit開始真慢,isa需要進(jìn)行一次位運(yùn)算,才能計(jì)算出真實(shí)地址
// 因?yàn)镃lass的isa沒有設(shè)置成可以供外部訪問黑界,所以我們自己創(chuàng)建一個(gè)結(jié)構(gòu)體管嬉,跟Class一樣,方便我們下邊獲取isa的值朗鸠。
struct gkt_objc_class {
    Class isa;
    Class superclass;
};

int main(int argc, const char * argv[]) {

    GKTTest *test = [GKTTest new];
    Class testClass = [GKTTest class];
    Class mateClass = object_getClass(testClass);
    
    struct gkt_objc_class * testClass2 = (__bridge struct gkt_objc_class *)testClass;
    struct gkt_objc_class * mateClass2 = (__bridge struct gkt_objc_class *)mateClass;

    return 0;

}
(lldb) p/x test->isa
(Class) $0 = 0x001d800100001179 GKTTest
(lldb) p/x testClass
(Class) $1 = 0x0000000100001178 GKTTest
(lldb) p/x 0x001d800100001179 & 0x00007ffffffffff8
(long) $2 = 0x0000000100001178
(lldb) p/x testClass2->isa
(Class) $3 = 0x0000000100001150
(lldb) p/x mateClass2
(gkt_objc_class *) $4 = 0x0000000100001150
(lldb) p/x 0x0000000100001150 & 0x00007ffffffffff8
(long) $5 = 0x0000000100001150
(lldb) 


窺探struct objc_class的結(jié)構(gòu)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蚯撩,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子烛占,更是在濱河造成了極大的恐慌胎挎,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件忆家,死亡現(xiàn)場離奇詭異犹菇,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)芽卿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門揭芍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人卸例,你說我怎么就攤上這事称杨。” “怎么了币厕?”我有些...
    開封第一講書人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵列另,是天一觀的道長。 經(jīng)常有香客問我旦装,道長页衙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任阴绢,我火速辦了婚禮店乐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘呻袭。我一直安慰自己眨八,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開白布左电。 她就那樣靜靜地躺著廉侧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪篓足。 梳的紋絲不亂的頭發(fā)上段誊,一...
    開封第一講書人閱讀 51,698評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音栈拖,去河邊找鬼连舍。 笑死,一個(gè)胖子當(dāng)著我的面吹牛涩哟,可吹牛的內(nèi)容都是我干的索赏。 我是一名探鬼主播盼玄,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼潜腻!你這毒婦竟也來了埃儿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬榮一對情侶失蹤砾赔,失蹤者是張志新(化名)和其女友劉穎蝌箍,沒想到半個(gè)月后青灼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體暴心,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年杂拨,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了专普。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡弹沽,死狀恐怖檀夹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情策橘,我是刑警寧澤炸渡,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站丽已,受9級(jí)特大地震影響蚌堵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜沛婴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一吼畏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧嘁灯,春花似錦泻蚊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至羹奉,卻和暖如春秒旋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背尘奏。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來泰國打工滩褥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人炫加。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓瑰煎,卻偏偏與公主長得像铺然,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子酒甸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

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