一個OC對象在內存中如何布局?以及一個NSObject對象占用多少內存?
我們知道OC的底層語言是c/c++我們平時編寫的OC代碼其實都會轉成c/c++的代碼.
舉個??
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc] init];
}
return 0;
}
在終端輸入
clang -rewrite-objc main.m -o main.cpp回車會生成C++,
但是該命令并沒有指定平臺(mac,windows,iOS),在不同的平臺會生成不同的代碼,OC代碼是一樣的.如果沒有指定平臺,生成的代碼文件會比較大.所以我們需要將代碼生成我們ios支持的C++代碼,更優(yōu)選擇是使用這條命令xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc mian.m -o main-arm64.cpp.
- 在終端
cd到mian.m的同級目錄. - 用于
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp回車. - 之后會在main.m的同級目錄看到新增了一個
main-arm64.cpp的文件 - 打開文件會看見大量的代碼,使用快捷鍵
command + ↓,會看到我們創(chuàng)建的NSObject代碼.
解析
xcrun -sdk iphoneos利用xcrun 指定sdk 支持編譯iphoneos平臺.clangode內置的llvm的編譯器前端.-arch arm64arch是架構的意思(指定架構),iOS架構分別是:模擬器(i386),真機分別是 32bit(armv7) 和b4bit(arm64) 從iPhone6 開始基本上是64的系統(tǒng).所以這里選擇 arm64.-rewrite-objc mian.m重寫main.m中的OC代碼.-o main-arm64.cpp, -o 代表output,以main-arm64.cpp文件的輸出.
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSObject *obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
}
return 0;
}
接著將生成的文件拖拽到項目中去.

之后可以編譯通過了.
在
cpp文件中搜索NSObject_IMPL.
實際上上述的NSObject_IMPL就是代碼中的一個NSObject對象的底層實現,也就是OC中的NSObject類的底層實現是C++的結構體支撐的.更為清楚的了解,我們可以Jump to Defintion

進入
NSObject.h文件中看OC是如何定義的.
可以看到OC的定義也是一個結構體.

在結構體中包含了一個 Class isa;的指針,該Class類型是typedef struct objc_class *Class;是一個指向結構體的指針,也即是Class是一個指針.
注意:指針類型在64位的操作系統(tǒng)是占8個字節(jié)數,在32為系統(tǒng)中占4個字節(jié)數.
總結:一個OC對象在內存中就是一個結構體,該結構體中只有一個isa成員,該isa指針占8個字節(jié)數.
思考:一個OC對象在內存中如何布局?
答:一個OC對象在底層中就是一個結構體.
思考:一個NSObject對象占用多少內存?
???(如果回答8個字節(jié)數的話,還是不夠深入.)
下面在代碼中可以引入系統(tǒng)框架來看下輸出的結果是什么.
#import <objc/runtime.h> // 通過runtime 可以獲取一個實例對象的大小
#import <malloc/malloc.h>// iOS中分配內存的框架
// NSObject Implementation
struct NSObject_IMPL {
Class isa;
};
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject *obj = [[NSObject alloc] init];
NSLog(@"---> %zd",class_getInstanceSize([NSObject class]));
NSLog(@"===> %zd", malloc_size((__bridge const void *)(obj)));
}
return 0;
}
輸出
2018-10-19 18:31:16.729754+0800 MLOC_NSObject[3518:154054] ---> 8
2018-10-19 18:31:16.732002+0800 MLOC_NSObject[3518:154054] ===> 16
結果是不一樣的,通過malloc_size可以得到實際的分配內存的大小,但是通過class_getInstanceSize根據名字可以猜測是獲取實例對象的大小,(那么就是NSObject的對象嗎?),接下來我們可以看下class_getInstanceSize蘋果官方是如何實現的(其中Runtime部分代碼是開源的).
蘋果開源代碼

下載最新代碼.打開源碼搜索
class_getInstanceSize
可以看到class_getInstanceSize的實現了內部調用了alignedInstanceSize點擊進入查看下

我們可以看到該方法返回的是一個實例成員變量的大小,并不是一個實例對象的大小,那么系統(tǒng)為什么會分配給我們16個字節(jié)數呢?
我們在初始化對象的時候
NSObject *obj = [[NSObject alloc] init];通過alloc來分配內存的,所有可以通過查看的內部實現是怎么樣.
一層層進入最終可以看到下面的方法.得出結論Core Foundation object 對象必須至少16的字節(jié)數
size_t instanceSize(size_t extraBytes) {
size_t size = alignedInstanceSize() + extraBytes;
// Core Foundation object 對象必須至少16的字節(jié)數
// CF requires all objects be at least 16 bytes.
if (size < 16) size = 16;
return size;
}
總結:一個NSObject對象分配了16個字節(jié)數,真正使用的只有8個字節(jié)數,
思考:一個NSObject對象占用多少內存?
正確答案:系統(tǒng)分配了16個字節(jié)給NSObject對象,(在我們的實際開發(fā)中直接通過
malloc_size函數獲得,不需要理會).
但是NSObject對象內部只使用了8個字節(jié)數(在64bit環(huán)境下可以通過class_getInstanceSize函數獲得)
小碼哥底層原理總結