iOS底層原理探究- NSObject 所佔記憶體

極客學偉發表於2018-06-02

iOS底層原理探究- NSObject 所佔記憶體

物件導向的Objective-C

我們平時寫的 OC 程式碼底層實現為 C/C++ 程式碼,因為 RuntimeOC 具備了物件導向的特點,而後底層的 C/C++ 會轉換成底層的 彙編 程式碼,最終被被解析成計算機能識別的 機器語言 。而 OC 中的類,正是正是基於 C/C++ 的結構體實現的。我們可以通過 clang 命令將我們平時所寫的 OC 程式碼轉換為 C/C++ 程式碼。這是轉換程式碼:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 原始檔名 -o 目標檔名
複製程式碼

如果需要連結其他框架,使用 -framework引數 比如 -framework UIKit

如: 我們進入原始檔所在的目錄,執行 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp 會在當前目錄生成一個main.cpp的檔案,這就是一個最簡單的OC檔案的 C++ 實現。

通過轉換之後我們很容易找到 NSObject 類的真正實現:

struct NSObject_IMPL {
	Class isa;
};
複製程式碼

只有一個 名為 isaClass 例項。繼續探尋 Class 的宣告:

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
複製程式碼

發現 Class 實際上是一個指向 objc_class 的結構體指標。

也就是說 NSObject 最終宣告為一個 指向結構體 objc_class 的名為 isa 的結構體指標。既然是指標,在32位系統中佔 4個 位元組,在64位系統中佔 8 個位元組。如何檢視自己的Mac是多少位呢?可以在終端 輸入 uname -a 若末尾顯示 x86_64 則代表是64位系統,如果末尾顯示 i686 則代表是32 系統。目前我們所使用的大多都是 64 位。

使用 Runtime 中 class_getInstanceSize 輸出一個類的例項物件的成員變數的大小
        NSObject *obj = [[NSObject alloc] init];
        NSLog(@"%zd",class_getInstanceSize([obj class]));
複製程式碼

輸出:

Snip20180529_3

由此可見,一個 NSObject 只有一個成員變數 即 isa,它所佔 8 個位元組的大小。 我們可通過 objc4原始碼得出此結論。

Snip20180529_4

Snip20180529_5

Class's ivar size rounded up to a pointer-size boundary. 一個類的所有成員變數所佔用的空間。

那在 OC 中一個 NSObject 物件佔用8個位元組嗎?答案是否定的,我們繼續分析。

使用 malloc_size(const void *ptr) 輸出一個類實際分配的記憶體大小
        NSObject *obj = [[NSObject alloc] init];
        NSLog(@"%zd",malloc_size((__bridge const void *)obj));
複製程式碼

輸出:

2018-05-29 07:40:24.270554+0800 XWTest0[25108:1196172] 16
複製程式碼
此外我們也可以用這種方式來證明一個NSObject物件佔 16 個位元組

我們知道在 OC 的物件例項中, 真正給物件分配記憶體的方式是

+ (instancetype)allocWithZone:(struct _NSZone *)zone OBJC_SWIFT_UNAVAILABLE("use object initializers instead");
複製程式碼

我們可以探究 allocWithZone 方法的底層實現, 同樣檢視Apple開源的OC原始碼 objc4原始碼

Snip20180602_1

Snip20180602_2

Snip20180602_3

Snip20180602_4

所有的OC物件至少為16位元組。進一步證明 NSObject類實際所佔用的記憶體空間為 16 個位元組

相關文章