一個(gè)NSObject對(duì)象占用多少內(nèi)存

問(wèn)題提出:

\color{#FF0000}{一個(gè)NSObject對(duì)象占用多少內(nèi)存?(答案在最下面)}

解析過(guò)程

要知道NSObject對(duì)象占用多少內(nèi)存,需要知道創(chuàng)建一個(gè)NSObject對(duì)象的時(shí)候都做了什么。
比如下面這行代碼:

NSObject *object = [[NSObject alloc] init];

首先我們點(diǎn)進(jìn)去看看NSObject是怎么定義的

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

然后發(fā)現(xiàn)NSObject的聲明中只有一個(gè)class 類(lèi)型的isa指針成員變量,按住command鍵看一下class又是個(gè)什么東西呢。點(diǎn)進(jìn)去我們發(fā)現(xiàn),class是一個(gè)指向結(jié)構(gòu)體的指針,具體是什么這個(gè)結(jié)構(gòu)體是什么我們先不管。

typedef struct objc_class *Class;

另外我們知道平時(shí)編寫(xiě)的Objective-C代碼底層實(shí)現(xiàn)其實(shí)是C/C++代碼,然后轉(zhuǎn)成匯編語(yǔ)言,最后生成機(jī)器閱讀的機(jī)器語(yǔ)言也就是01010110這種。
也就是:


既然這樣,那我們就看看NSObject 轉(zhuǎn)成C/C++代碼是什么樣的。
通過(guò)下面任何一種方式都可以將OC語(yǔ)言轉(zhuǎn)成C/C++

方式一:clang -rewrite-objc main.m(main.m是要被轉(zhuǎn)C/C++的文件名字) -o main.cpp(main.cpp是要生成C/C++的文件名字)
方式二:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m(這里是要被轉(zhuǎn)C/C++的文件名字) -o main-arm64.cpp(main-arm64.cpp是要生成C/C++的文件名字)

方式一是將OC直接轉(zhuǎn)成C/C++ 語(yǔ)言,(注:-o main.cpp 這個(gè)是要生成C/C++的文件名,可寫(xiě)可不寫(xiě))
方式二是將OC轉(zhuǎn)成適應(yīng)于 64位真機(jī)下的 C/C++ 語(yǔ)言,-o main-arm64.cpp這行可寫(xiě)可不寫(xiě),默認(rèn)生成<原類(lèi)名.cpp>文件

在此我們也舉一個(gè)簡(jiǎn)單的例子,直接創(chuàng)建一個(gè)macOS項(xiàng)目就好(等下后面運(yùn)行不需啟動(dòng)模擬器)。

然后在main.m文件的main函數(shù)里簡(jiǎn)單寫(xiě)一行代碼

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSObject *obejct = [[NSObject alloc] init];
        
    }
    return 0;
}

接著打開(kāi)終端。cd到文件目錄下,然后選擇上面任何一種轉(zhuǎn)成C/C++的方式,這里我用方式二
即:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp

然后就會(huì)生成一個(gè) main-arm64.cpp 文件,我們打開(kāi)看一下。
直接搜索NSObject,就會(huì)發(fā)現(xiàn)有這么一個(gè)結(jié)構(gòu)體

struct NSObject_IMPL {
    Class isa;
};

此處我們也可以得之,OC的類(lèi)在C/C++中實(shí)現(xiàn)的方式就是一個(gè)結(jié)構(gòu)體,即OC類(lèi)是以結(jié)構(gòu)體的形式在內(nèi)存中存在的。而且NSObject內(nèi)部只有一個(gè)class 類(lèi)型的isa指針,既然是一個(gè)指針,那么我們知道,指針在64環(huán)境下就是占8個(gè)字節(jié)(在32位環(huán)境下就是4個(gè)字節(jié)。)既然這個(gè)指針占用8個(gè)字節(jié),而且NSObject這個(gè)結(jié)構(gòu)體內(nèi)部只有這么一個(gè)結(jié)構(gòu)體,很顯然,這個(gè)結(jié)構(gòu)體也就是占用8個(gè)字節(jié)。

所以,

NSObject *obejct = [[NSObject alloc] init];

這句代碼的過(guò)程就是:
[NSObject alloc]在內(nèi)存中分配地址,然后創(chuàng)建對(duì)象。接著將object這個(gè)指針指向剛剛創(chuàng)建的對(duì)象,最后將分配的對(duì)象地址賦值給object指針。也就是


那么這樣看來(lái),創(chuàng)建一個(gè)NSObject對(duì)象具體分配的大小 \color{red}{應(yīng)該} 就是8個(gè)字節(jié),因?yàn)榍懊嬖蹅円呀?jīng)分析出,NSObject底層實(shí)現(xiàn)方式其實(shí)就是一個(gè)結(jié)構(gòu)體,而這個(gè)結(jié)構(gòu)體內(nèi)部只有一個(gè)占8個(gè)字節(jié)的isa指針。到底是不是呢需要驗(yàn)證一下。
runtime里面有這么一個(gè)函數(shù),獲取類(lèi)的實(shí)例大小

class_getInstanceSize(Class _Nullable cls)

既然有這么個(gè)方法,那咱們就打印驗(yàn)證一下,看是不是8個(gè)字節(jié)呢,
注意要先引入
#import <objc/runtime.h>

NSLog(@"NSObject InstanceSize:%zd",class_getInstanceSize([NSObject class]));

結(jié)果:NSObject InstanceSize:8

經(jīng)過(guò)測(cè)試發(fā)現(xiàn),“果然”是8個(gè)字節(jié)。有眼尖的小伙伴可能會(huì)發(fā)現(xiàn)這里可能會(huì)有坑。為了確保安全,我們需要看看 class_getInstanceSize(Class _Nullable cls)

這里是OC源碼地址
https://opensource.apple.com/tarballs/

打開(kāi)鏈接往下翻或直接搜索 objc4,點(diǎn)進(jìn)去會(huì)發(fā)現(xiàn)好多版本,然后找到最大的那個(gè),也就是最新的下載、解壓。打開(kāi)解壓好的源碼,這是不能直接運(yùn)行的,但是我們可以大略看一下。
打開(kāi)之后直接搜索 class_getInstanceSize,找.mm文件看實(shí)現(xiàn)代碼

image.png

我們發(fā)現(xiàn),在調(diào)用class_getInstanceSize(Class _Nullable cls)函數(shù)的時(shí)候,如果傳入的類(lèi)對(duì)象為nil,就會(huì)直接返回0。否則就調(diào)用alignedInstanceSize函數(shù),也就是對(duì)齊后的實(shí)例大小,接著點(diǎn)擊去發(fā)現(xiàn)

image.png

調(diào)這個(gè)方法的意思是:返回unalignedInstanceSize(未對(duì)齊的實(shí)例大小)對(duì)齊后的大小
這也是遵循了內(nèi)存分配法則之一:內(nèi)存對(duì)齊原則。但這個(gè)函數(shù)的注釋是返回class的成員變量大小,注意,此處可有有蹊蹺了。既然這個(gè)函數(shù)式返回對(duì)象的成員變量的大小,那么這個(gè)大小是不是就是創(chuàng)建對(duì)象時(shí)分配的大小呢?為了證明我們也看看 NSObjectalloc方法是怎么實(shí)現(xiàn)的。對(duì)象調(diào)用alloc方法其實(shí)就是調(diào)用了allocwithzone,我們直接在剛剛下載的OC源碼里搜索allocwithzone,找到這個(gè)函數(shù)的實(shí)現(xiàn),發(fā)現(xiàn)在這個(gè)函數(shù)里調(diào)用了一個(gè)創(chuàng)建實(shí)例對(duì)象的函數(shù)

接著我們看看這個(gè)函數(shù)式怎么實(shí)現(xiàn)的,點(diǎn)進(jìn)去發(fā)現(xiàn)這里面有調(diào)用了一個(gè)獲取實(shí)例對(duì)象大小的函數(shù)
image.png

然后我們?cè)龠M(jìn)一步點(diǎn)進(jìn)去發(fā)現(xiàn)里面計(jì)算的size是調(diào)用了instanceSize(size_t)這個(gè)函數(shù)

image.png

我們接著點(diǎn)進(jìn)去,最后找到這個(gè),也就是實(shí)例對(duì)象的大小

image.png

我們發(fā)現(xiàn)這個(gè)函數(shù)的有一行注釋?zhuān)馑季褪荂oreFoundation框架規(guī)定所有的對(duì)象至少要16個(gè)字節(jié)。也就是如果這個(gè)對(duì)象不足16個(gè)字節(jié),也會(huì)分配給你16個(gè)字節(jié)。
雖然這么說(shuō),我們也得驗(yàn)證一下alloc在創(chuàng)建的時(shí)候是不是真的分配了16個(gè)字節(jié)。
首先我們?cè)谥白约簞?chuàng)建的文件里導(dǎo)入
#import <malloc/malloc.h>
然后會(huì)查看這個(gè)函數(shù)

extern size_t malloc_size(const void *ptr);
/* Returns size of given ptr */

malloc的全稱(chēng)是memory allocation,中文叫動(dòng)態(tài)內(nèi)存分配,用于申請(qǐng)一塊連續(xù)的指定大小的內(nèi)存塊區(qū)域以void*類(lèi)型返回分配的內(nèi)存區(qū)域地址。extern size_t malloc_size(const void *ptr) 函數(shù)的意思就是返回一個(gè)指針的占用內(nèi)存的大小,既然這樣我們就把創(chuàng)建的object傳進(jìn)去,看看大小到底是多少,

NSLog(@"obejct: %zd",malloc_size((__bridge const void *)(obejct)));

結(jié)果:obejct malloc_size: 16

打印結(jié)果的確是16個(gè)字節(jié)。
根據(jù)打印結(jié)果結(jié)合源碼解析我們就可以得知?jiǎng)?chuàng)建NSObject對(duì)象的時(shí)候系統(tǒng)會(huì)分配給其16個(gè)字節(jié),但是這個(gè)對(duì)象只使用了8個(gè)字節(jié),另外8個(gè)字節(jié)是空著的。

而且咱們也可以根據(jù)對(duì)象地址查看內(nèi)存空間地址,首先在創(chuàng)建NSObject的時(shí)候添加個(gè)斷點(diǎn),打印一下obejct對(duì)象的地址:0x10062f930,然后找Debug->Debug Workflow -> View Memory,接著將地址輸入,就可以查看。

查看內(nèi)存空間地址步驟

NSObject內(nèi)存空間地址

根據(jù)內(nèi)存空間地址,我們也可以發(fā)現(xiàn)。isa占用了前面8個(gè)字節(jié)(因?yàn)椴榭吹氖?6進(jìn)制,一位16進(jìn)制代表4位二進(jìn)制,所以?xún)晌?6進(jìn)制就是8位二進(jìn)制也就是一個(gè)字節(jié)),而后面八個(gè)字節(jié)都是00,再后面就是其他內(nèi)存了。

簡(jiǎn)單畫(huà)張圖表也就是頁(yè)面這個(gè)樣子


NSObject內(nèi)存分析

雖然弄明白了NSObject,但是開(kāi)發(fā)最多的還是用自定義的類(lèi)。下面就看一下自定義的類(lèi)所占的內(nèi)存空間大小。

在此為了方便查看,我就直接在main文件直接創(chuàng)建一個(gè)SSPerson類(lèi),類(lèi)里面有兩個(gè)成員變量

@interface SSPerson : NSObject{
    @public
    int _age;
    int _height;
}
@end

@implementation SSPerson
@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
    }
    return 0;
}

然后我們?cè)俅蝝ain.m文件轉(zhuǎn)為C/C++文件,看看自定義的SSPerson類(lèi)轉(zhuǎn)成C/C++是什么格式的,方法和前面一樣。
然后搜索SSPerson就會(huì)發(fā)現(xiàn),

struct SSPerson_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _age;
    int _height;
};

我們發(fā)現(xiàn)自定的SSPerson類(lèi)也是轉(zhuǎn)成了一個(gè)結(jié)構(gòu)體,內(nèi)部包含三個(gè)成員變量,下面兩個(gè)是咱們自己寫(xiě)的,NSObject_IVARS 是一個(gè)struct NSObject_IMPL結(jié)構(gòu)體類(lèi)型,前面我們說(shuō)過(guò),NSObject底層實(shí)現(xiàn)就是struct NSObject_IMPL,而其內(nèi)部就只有一個(gè)class類(lèi)型的 isa 成員,所以這里可以直接當(dāng)做是Class isa,

struct SSPerson_IMPL {
    Class isa;
    int _age;
    int _height;
};

那么根據(jù)之前分析(后面如果沒(méi)有特別說(shuō)明都是結(jié)合64位環(huán)境下計(jì)算),isa占8個(gè)字節(jié),而且int類(lèi)型占用4個(gè)字節(jié),這樣算來(lái),就是
一個(gè)isa 8個(gè)字節(jié) + int類(lèi)型 4個(gè)字節(jié) + int類(lèi)型 4個(gè)字節(jié) = 16個(gè)字節(jié)。
我們也可以進(jìn)一步查看SSPerson對(duì)象的內(nèi)存空間地

image.png

第一個(gè)是isa占用8個(gè)字節(jié),接著是_age占用4個(gè)字節(jié),然后是_height占用4個(gè)字節(jié)。

那么SSPerson是不是占16個(gè)字節(jié),我們打印一下便知。

NSLog(@"SSPerson InstanceSize:%zd",class_getInstanceSize([SSPerson class]));
NSLog(@"person malloc_size: %zd",malloc_size((__bridge const void *)(person)));

結(jié)果為:
SSPerson InstanceSize:16
person malloc_size: 16

由此可我們也可以得知,創(chuàng)建SSPerson對(duì)象的時(shí)候系統(tǒng)會(huì)分配給其16個(gè)字節(jié),而且其使用了16個(gè)字節(jié)。

如果感興趣,可以進(jìn)一步嘗試,多嘗試就會(huì)有多發(fā)現(xiàn)。

答案:

*系統(tǒng)分配了16個(gè)字節(jié)給NSObject對(duì)象(通過(guò)malloc_size函數(shù)獲得)
*但NSObject對(duì)象內(nèi)部只使用了8個(gè)字節(jié)的空間(64bit環(huán)境下,可以通過(guò)class_getInstanceSize函數(shù)獲得)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • iOS底層原理總結(jié) - 探尋OC對(duì)象的本質(zhì) 對(duì)小碼哥底層班視頻學(xué)習(xí)的總結(jié)與記錄。面試題部分,通過(guò)對(duì)面試題的分析探索...
    xx_cc閱讀 22,280評(píng)論 31 178
  • 面試題:一個(gè)NSObject對(duì)象占用多少內(nèi)存? 作為一個(gè)iOS開(kāi)發(fā)人員來(lái)說(shuō),iOS底層原理是必須要掌握的知識(shí)。雖然...
    朝夕向背閱讀 1,299評(píng)論 0 10
  • 1.oc對(duì)象的本質(zhì) 我們平時(shí)編寫(xiě)的Objective-C代碼,底層實(shí)現(xiàn)其實(shí)都是C\C++代碼。編譯過(guò)程 所以O(shè)bj...
    Coder_Cat閱讀 857評(píng)論 0 1
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,084評(píng)論 0 9
  • 熟悉的旋律又縈繞在耳畔 夏日的明凈里浮現(xiàn)出一絲絲秋涼般的善感 想那時(shí)光匆匆的流去 匆匆的流去 如泉水叮咚 又宛若夢(mèng)...
    runan777閱讀 207評(píng)論 0 0

友情鏈接更多精彩內(nèi)容