iOS底層原理-OC對(duì)象的本質(zhì)

面試題:一個(gè)NSObject對(duì)象占用多少內(nèi)存?

作為一個(gè)iOS開(kāi)發(fā)人員來(lái)說(shuō),iOS底層原理是必須要掌握的知識(shí)。雖然iOS底層原理更多的是在面試中被問(wèn)到餐济,但是在實(shí)際工作中,掌握iOS底層原理更有助于我們提高編寫(xiě)代碼的質(zhì)量胆剧、速度以及更方便的解決bug絮姆。

想要弄清楚iOS底層原理的本質(zhì),首先要清楚以下兩點(diǎn)

1秩霍、OC對(duì)象在內(nèi)存中是怎么布局的篙悯?
2、OC對(duì)象中包含了哪些內(nèi)容铃绒?

一鸽照、NSObject對(duì)象在內(nèi)存中的布局

我們平時(shí)編寫(xiě)的Objective-C代碼,底層都是C/C++語(yǔ)言來(lái)支持的匿垄。
iOS代碼運(yùn)行流程如下:


OC代碼的轉(zhuǎn)化過(guò)程

接下來(lái)通過(guò)創(chuàng)建OC項(xiàng)目移宅,并將OC相應(yīng)文件轉(zhuǎn)化為C++文件來(lái)探尋OC對(duì)象的本質(zhì)。

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSObject *isa_obj= [[NSObject alloc] init];
}

@end

將OC的ViewController.m文件轉(zhuǎn)化為c++文件椿疗,可以通過(guò)以下兩種命令行來(lái)執(zhí)行漏峰。

第一種方式是不指定架構(gòu)來(lái)轉(zhuǎn)化為C++。
clang -rewrite-objc main.m -o main.cpp
其中cpp代表C++(c plus plus)

第二種方式是指定架構(gòu)模式(現(xiàn)在iOS是arm64架構(gòu)) 來(lái)轉(zhuǎn)化為C++届榄。
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc ViewController.m -o ViewController-arm64.cpp

ViewController-arm64.cpp文件中全局搜索NSObjcet_IMPL浅乔,可以找到NSObject的實(shí)現(xiàn)代碼

    struct NSObject_IMPL {
         Class isa;
    };

    點(diǎn)擊Class,我們發(fā)現(xiàn)铝条,Class是一個(gè)指向結(jié)構(gòu)體的指針靖苇,如下:
    typedef struct objc_class *Class;

從上面可以看出,NSObject對(duì)象的底層是基于C++的數(shù)據(jù)類(lèi)型實(shí)現(xiàn)班缰。這個(gè)數(shù)據(jù)類(lèi)型是結(jié)構(gòu)體類(lèi)型贤壁。

我們打印下NSObject內(nèi)存,來(lái)看下NSObject對(duì)象在內(nèi)存中的占用大小

#import "ViewController.h"
#import <objc/runtime.h>
#import <malloc/malloc.h>

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSObject *isa_obj= [[NSObject alloc] init];
    //獲得NSObject類(lèi)的實(shí)例對(duì)象的成員變量所占用的大小
    NSLog(@"%zd",class_getInstanceSize([NSObject class]));
    //獲得isa_objl指針?biāo)赶騼?nèi)存的大小
    NSLog(@"%zd",malloc_size((__bridge const void *)isa_obj));    
}

輸出結(jié)果

2018-10-10 15:42:53.776715+0800 NSObject本質(zhì)-01[5132:224858] 8
2018-10-10 15:42:53.776875+0800 NSObject本質(zhì)-01[5132:224858] 16

根據(jù)打印結(jié)果埠忘,NSObject對(duì)象的指針占用了8個(gè)字節(jié)脾拆,但是NSObject對(duì)象確占用了16個(gè)字節(jié)。其實(shí)class_getInstanceSize返回的是成員變量的大小莹妒,上例中這個(gè)成員變量只是isa指針名船。malloc_size返回的才是NSObject對(duì)象的在內(nèi)存中的大小。

回到開(kāi)頭的面試題

面試題:一個(gè)NSObject對(duì)象占用多少內(nèi)存旨怠?
NSObject的面向?qū)ο笫且訡/C++的數(shù)據(jù)類(lèi)型實(shí)現(xiàn)的渠驼,這種數(shù)據(jù)類(lèi)型是結(jié)構(gòu)體。在NSObject頭文件中鉴腻,Class是一個(gè)指向結(jié)構(gòu)體的指針迷扇。在64位環(huán)境下,指針占用8個(gè)字節(jié)拘哨。但實(shí)際上系統(tǒng)給NSObject對(duì)象分配了16個(gè)字節(jié)谋梭。但是NSObject對(duì)象內(nèi)部占用了8個(gè)字節(jié)。

在NSObject的初始化中倦青,系統(tǒng)為NSObject對(duì)象分配16個(gè)字節(jié)的內(nèi)存空間瓮床,其中8個(gè)字節(jié)用來(lái)存放一個(gè)成員isa指針。那么isa指針這個(gè)變量的地址就是結(jié)構(gòu)體的地址产镐,也就是NSObjcet對(duì)象的地址隘庄。
假設(shè)isa的地址為0x100400110,那么系統(tǒng)分配存儲(chǔ)空間給NSObject對(duì)象癣亚,然后將存儲(chǔ)空間的地址賦值給objc指針丑掺。objc存儲(chǔ)的就是isa的地址。objc指向內(nèi)存中NSObject對(duì)象地址述雾,即指向內(nèi)存中的結(jié)構(gòu)體街州,也就是isa的位置兼丰。

我們通過(guò)自定義的類(lèi)來(lái)說(shuō)明內(nèi)部布局
#import <Foundation/Foundation.h>

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

@implementation Student


@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Student *student = [[Student alloc] init];
        student -> _no = 1;
        student -> _age = 18;
    }
    return 0;
}

按照以上c++生成步驟生成文件,并查找Student唆缴,C++文件中如下:

struct Student_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _no;
    int _age;
};

通過(guò)搜索NSObject_IMPL鳍征,我們查找到NSObject_IMPL實(shí)現(xiàn)方式

struct NSObject_IMPL {
    Class isa;
};

所以Student_IMPL的結(jié)構(gòu)體就相當(dāng)于

struct Student_IMPL {
    Class *isa;
    int _no;
    int _age;
};

因此,此結(jié)構(gòu)體占用多少存儲(chǔ)空間面徽,Student對(duì)象就占用多少存儲(chǔ)空間艳丛。結(jié)構(gòu)體占用的存儲(chǔ)空間為:
isa指針(8個(gè)字節(jié))+int _no(4個(gè)字節(jié))+int _age(4個(gè)字節(jié))= 16個(gè)字節(jié)

我們用另外一種方式驗(yàn)證Student內(nèi)存分布

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>

struct Student_IMPL {
    Class isa;
    int _no;
    int _age;
};

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

@implementation Student

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {  
        Student *stu = [[Student alloc] init];
        stu->_no = 1;
        stu->_age = 18;
        
        struct Student_IMPL *stuImpl = (__bridge struct Student_IMPL *)stu;
        NSLog(@"_no = %d, _age = %d", stuImpl->_no, stuImpl->_age);
    }
    return 0;
}

首先給Student的成員變量_no,_age賦值。用結(jié)構(gòu)體指針stuImpl訪(fǎng)問(wèn)Student的成員變量趟紊,輸出成員變量值氮双。這說(shuō)明,stu這個(gè)指針指向的就是Student_IMPL的結(jié)構(gòu)體霎匈。

當(dāng)存在繼承關(guān)系的時(shí)候戴差,對(duì)象在內(nèi)存中是如何分布的呢?我們來(lái)看一個(gè)例子

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <malloc/malloc.h>

@interface Person : NSObject
{
    int _no;
}
@end

@implementation Person

@end

@interface Student : Person
{
    int _age;
}
@end

@implementation Student

@end

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

    }
    return 0;
}

上面代碼中Person對(duì)象铛嘱,Student對(duì)象分別占用多少內(nèi)存空間造挽?

通過(guò)將OC文件轉(zhuǎn)化為C++文件,我們查找Student_IMPL弄痹,發(fā)現(xiàn)Student對(duì)象在內(nèi)存中的分布

struct Student_IMPL {
    struct Person_IMPL Person_IVARS;
    int _age;
};

Person對(duì)象在內(nèi)存中的分布如下

struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _no;
};

在看上面的面試題饭入,不難回答,Student對(duì)象和Person對(duì)象都是占用16個(gè)字節(jié)的內(nèi)存肛真。雖然Person對(duì)象占用了16個(gè)字節(jié)的內(nèi)存空間谐丢,但是Person的地址指針和成員變量只占用了12個(gè)字節(jié),空出來(lái)4個(gè)字節(jié)的內(nèi)存蚓让。這4個(gè)字節(jié)乾忱,就被Student的成員變量占據(jù)。

二历极、OC對(duì)象中包含的內(nèi)容

Objective_C中的對(duì)象窄瘟,簡(jiǎn)稱(chēng)OC對(duì)象,主要可以分為3種對(duì)象

1趟卸、instance對(duì)象(實(shí)例對(duì)象)
2蹄葱、class對(duì)象(類(lèi)對(duì)象)
3、meta-class對(duì)象(元類(lèi)對(duì)象)

1锄列、instance對(duì)象(實(shí)例對(duì)象)

instance對(duì)象是通過(guò)類(lèi)alloc出來(lái)的對(duì)象图云,每次調(diào)用alloc都會(huì)產(chǎn)生新的實(shí)例對(duì)象。
instance對(duì)象在內(nèi)存中存儲(chǔ)的信息包括成員以下內(nèi)容:
isa指針邻邮;
成員變量值竣况。

  • 注意instance對(duì)象 不存儲(chǔ)方法。
  • isa指針也是一種成員變量值筒严。

2丹泉、class對(duì)象(類(lèi)對(duì)象)

每個(gè)類(lèi)在內(nèi)存中有且只有一個(gè)class對(duì)象
class對(duì)象在內(nèi)存中存儲(chǔ):
isa指針;
superclass指針;
類(lèi)的屬性信息(@property);
類(lèi)的對(duì)象方法信息(instance method);

類(lèi)的協(xié)議信息(protocol);
類(lèi)的成員變量信息(ivar)情萤。

3、meta-class對(duì)象(元類(lèi)對(duì)象)

每個(gè)類(lèi)中有且只有一個(gè)meta-class對(duì)象
meta-class對(duì)象在內(nèi)存中存儲(chǔ):
isa指針;
superclass指針;
類(lèi)方法信息摹恨。

  • 獲取類(lèi)對(duì)象紫岩、元類(lèi)對(duì)象的方法

a) Class objc_getClass(const char *aClassName)
1>傳入類(lèi)名的字符串
2>返回對(duì)應(yīng)的類(lèi)對(duì)象

b) Class object_getClass(id obj)
1> 傳入的obj可能是instance、class對(duì)象睬塌、meta-class對(duì)象
2>返回值
如果傳入的obj是instance對(duì)象,返回class對(duì)象
如果傳入的obj是class對(duì)象歇万,返回meta-class對(duì)象
如果傳入的obj是meta-class對(duì)象揩晴,返回NSObject(基類(lèi))的meta-class對(duì)象

c) 判斷是否為元類(lèi)對(duì)象
class_isMetaClass(Class cls)

isa指針的指向
isa指針的指向
  • instance的isa指向class
    當(dāng)調(diào)用對(duì)象方法時(shí),通過(guò)instance對(duì)象的isa指針找到class贪磺,最后找到對(duì)象方法的實(shí)現(xiàn)進(jìn)行調(diào)用硫兰。
  • class的isa指針指向meta-class
    當(dāng)調(diào)用類(lèi)方法時(shí),通過(guò)class的isa找到meta-class寒锚,最后找到類(lèi)方法的實(shí)現(xiàn)進(jìn)行調(diào)用劫映。
class對(duì)象的superclass指針
class對(duì)象的superclass指針
  • 當(dāng)Student的instance對(duì)象要調(diào)用Person的對(duì)象方法時(shí),會(huì)先通過(guò)isa找到Student的superclass刹前,然后通過(guò)superclass找到Person的class泳赋,最后找到對(duì)象方法的實(shí)現(xiàn)進(jìn)行調(diào)用
meta-class對(duì)象的superclass指針
meta-class對(duì)象的superclass指針
  • 當(dāng)Student的class要調(diào)用Person的類(lèi)方法時(shí),會(huì)先通過(guò)isa找到Student的meta-class喇喉,然后通過(guò)superclass找到person的meta-class祖今,最后找到類(lèi)方法的實(shí)現(xiàn)進(jìn)行調(diào)用。
isa拣技、superclass的總結(jié)
isa和superclass指向流程
  • instance的isa指向class
  • class的isa指向meta-class
  • meta-class的isa指向基類(lèi)的meta-class
  • class的superclass指向父類(lèi)的class
    如果沒(méi)有父類(lèi)千诬,superclass的指針為nil
  • meta-class的superclass指向父類(lèi)的meta-class
    基類(lèi)的meta-class的superclass指向基類(lèi)的class
  • instance調(diào)用對(duì)象方法的軌跡
    isa找到class,方法不存在膏斤,就通過(guò)superclass找父類(lèi)
  • class調(diào)用類(lèi)方法的軌跡
    isa找meta-class徐绑,方法不存在,就通過(guò)superclass找父類(lèi)
isa地址值的計(jì)算
isa值的指向

64bit之前莫辨,instance的isa存的值為class的地址值傲茄。從64bit開(kāi)始,isa需要進(jìn)行一次位運(yùn)算沮榜,才能計(jì)算出真實(shí)地址烫幕。


ISA_MASK值

如果平臺(tái)是arm64,isa & ISA_MASK 的值是class的isa
如果平臺(tái)是x86敞映,isa & ISA_MASK的值是class的isa

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末较曼,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子振愿,更是在濱河造成了極大的恐慌捷犹,老刑警劉巖弛饭,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異萍歉,居然都是意外死亡侣颂,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)枪孩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)憔晒,“玉大人,你說(shuō)我怎么就攤上這事蔑舞【艿#” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵攻询,是天一觀(guān)的道長(zhǎng)从撼。 經(jīng)常有香客問(wèn)我,道長(zhǎng)钧栖,這世上最難降的妖魔是什么低零? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮拯杠,結(jié)果婚禮上掏婶,老公的妹妹穿的比我還像新娘。我一直安慰自己潭陪,他們只是感情好气堕,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著畔咧,像睡著了一般茎芭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上誓沸,一...
    開(kāi)封第一講書(shū)人閱讀 48,970評(píng)論 1 284
  • 那天梅桩,我揣著相機(jī)與錄音,去河邊找鬼拜隧。 笑死宿百,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的洪添。 我是一名探鬼主播垦页,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼干奢!你這毒婦竟也來(lái)了痊焊?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎薄啥,沒(méi)想到半個(gè)月后辕羽,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡垄惧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年刁愿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片到逊。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡铣口,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出觉壶,到底是詐尸還是另有隱情脑题,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布掰曾,位于F島的核電站,受9級(jí)特大地震影響停团,放射性物質(zhì)發(fā)生泄漏旷坦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一佑稠、第九天 我趴在偏房一處隱蔽的房頂上張望秒梅。 院中可真熱鬧,春花似錦舌胶、人聲如沸捆蜀。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)辆它。三九已至,卻和暖如春履恩,著一層夾襖步出監(jiān)牢的瞬間锰茉,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工切心, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留飒筑,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓绽昏,卻偏偏與公主長(zhǎng)得像协屡,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子全谤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

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