窺探iOS底層實現--OC對象的本質(一)

窺探iOS底層實現--OC對象的本質(一) - 掘金

窺探iOS底層實現--OC對象的本質(二) - 掘金

窺探iOS底層實現--OC對象的分類:instance、class、meta-calss對象的isa和superclass - 掘金

...

問題: 一個NSObject對象占用多少個內存?

int main(int argc, char * argv[]) {
    @autoreleasepool {
        NSObject *obj = [[NSObject alloc]init];
        /// obj對象占用了多少內存?
    }
}
return 0;

Objective-c的本質:

平時我們編寫的Objective-c的代碼,底層的實現其實都是C/C++的代碼。


OC_層次結構圖.png

所以Objective-c 的面向對象都是基于C/C++的數據結構實現的。

思考問題: Objective-C的對象、類主要是基于C\C++的什么數據結構實現的?

///> Student類
@interface Student: NSObject{
 @public
    int _no;
    int _age;
}
@end
@implementation Student

@end

int main(int argc, char * argv[]) {
    @autoreleasepool {
        NSObject *obj = [[NSObject alloc]init];
        /// obj對象占用了多少內存?
    }
}
return 0;

如果Objective-c的對象轉成C/C++的代碼實際上最重轉成了C
/C++的機構體。

那么我們怎么把OC的代碼轉換成C/C++的代碼呢?

  1. 終端進入到程序的目錄下:


    OC_終端目錄.png
  1. 輸入命令行

    xcrun  -sdk  iphoneos  clang  -arch  arm64  -rewrite-objc  OC源文件  -o  輸出的CPP文件
    eg:
    xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 
    
  2. 然后機會生成一個main-arm64.cpp的文件 這里面就是我們的C/C++的實現。

  3. 如果需要鏈接其他框架,使用-framework參數。比如-framework UIKit

  4. 在生成main-arm64.cpp 內搜索 NSObject_IMPL

    • NSObject_IMPL: NSObject Implementation
    • NSObject在內存中其實就是
///> NSObject_IMPL
struct NSObject_IMPL {
    Class isa; /// isa
};

NSObject的底層實現結構圖

OC_NSObject_底層實現結構圖.png

上圖實際上NSObject對象中存在一個isa指針,isa指針在64位系統中占用8個字節,在32位的系統中占用4個字節,目前用的是64位系統,所以在我們NSObject中isa指針會占用8個字節。CLass isa的內部實現為結構體。

  /// class 其實就是一個指針  指向一個結構體的指針
  typedef struct objc_class *Class 
  
///   創建并分配存儲空間
NSObject *obj = [[NSObject alloc]init];

假設我們NSObject對象分配了一塊存儲空間,假設之后8個字節,在這8個字節中我們只放了isa指針,假設我們的isa的地址為0x100400110,這個isa的地址就是結構體的地址。所以說obj的地址就是0x100400110。

NSObject占用的內存

#import <malloc/malloc.h>
#import <OBJC/runtime.h>
///> main
int main(int argc, char * argv[]) {
    @autoreleasepool {
        NSObject *obj = [[NSObject alloc]init];
        ///> 獲得NSObject類的實例對象的大小
        NSLog(@"%zd", class_getInstanceSize([NSObject class]));
        ///> 獲取obj指針指向內存的大小
        NSLog(@"%zd", malloc_size((__bridge const void *)obj));        
        
        
        /**輸出結果
        8    
        16   
        */ 
    }
    return 0;
}
  • 首先我們用的Runtime的 class_getInstanceSize()方法去查看 NSObject類的實例對象的大小

    • 傳入 類 class
    • 注意: Instance 實例,返回一個類的實例大小占用了內存空間的大小為8
  • 然后我們用malloc_size的方法去查看obj指針指向內存的大小 為16;

    • 傳入obj的指針(會有錯誤提示 然后寫上橋接就好了(__bridge const void *) )

malloc_size 為什么是16接下來我們可以去查看源碼去解決問題:
源碼地址:Source Browser:OBJective-c源碼
找到objc4,下載版本號最大的就是最新的源碼去查看

OC_源碼_objc4.png

alloc本質調用的是allocWithZone。 在源碼中搜索allocWithZone

OC_malloc_size_01.png

在底層代碼中間我們找到allocWithZone的底層方法。發現obj是由class_cerateInstance(cls,0)創建出來的。

OC_malloc_size_02.png

然后我們在進入_class_createInstanceFromZone(cls, extraBytes, nil);

OC_malloc_size_03.png

進入后會看到calloc(1,size)的alloc的實現代碼,傳入了一個size,size是instanceSize(extraBytes)得到的,我們再次進入

OC_malloc_size_04.png

規定對象的字節至少是16個字節,
當我們的分配的size值小于16是 會把size設置為16
我們size傳進來的就是alignedInstanceSize() 就是我們傳進來的實例變量的大小 為8 所以當小于16的時候底層代碼中返回的就是16 , 所以分配的內存大小至少是16。

  • 一個NSObject對象占用多少內存?
    • 系統分配了16個字節給NSObject對象(通過malloc_size函數獲得)
    • 但NSObject對象內部只使用了8個字節的空間(64bit環境下,可以通過class_getInstanceSize函數獲得

自定義NSObject 占用的內存

#import <malloc/malloc.h>
#import <OBJC/runtime.h>
///> Student類
@interface Student: NSObject{
    @public
    int _no;
    int _age;
}

///> 實際底層的結構體 結構
//struct Student_IMPL{
//    Class isa,
//    int _no,
//    int _age;
//}
@end

@implementation Student
@end

///> main
int main(int argc, char * argv[]) {
    @autoreleasepool {
        NSObject *obj = [[NSObject alloc]init];
        Student *stu = [[Student alloc]init];
        stu->_no = 4;
        stu->_age = 5;
        NSLog(@"no:%d, age:%d",stu->_no, stu->_age);
        NSLog(@"%zd", class_getInstanceSize([Student class]));
        NSLog(@"%zd", malloc_size((__bridge const void *)stu));        
        
        /**輸出結果
        no:4, age:5
        16
        16
        */ 
    }
    return 0;
}
  • 當我們自定義一個NSObject的時候實際底層會有三個成員變量,isa指針占用8個字節,_no占用4個字節 _age真用4個字節,所以我們最后的結果為16,16

思考: 如果我的Student有三個成員變量 那么會占用對少個字節? (class_getInstanceSize([Student class]) 的輸出是多少? malloc_size((__bridge const void *)stu的輸出是多少? )

#import <malloc/malloc.h>
#import <OBJC/runtime.h>
///> Student類
@interface Student: NSObject{
    @public
    int _no;
    int _age;
    int _gender;
}

///> 實際底層的結構體 結構
//struct Student_IMPL{
//    Class isa,
//    int _no,
//    int _age;
//    int _gender;

//}
@end

@implementation Student
@end

///> main
int main(int argc, char * argv[]) {
    @autoreleasepool {
        NSObject *obj = [[NSObject alloc]init];
        Student *stu = [[Student alloc]init];
        stu->_no = 4;
        stu->_age = 5;
        stu->_gender = 0;
        NSLog(@"%zd", class_getInstanceSize([Student class]));
        NSLog(@"%zd", malloc_size((__bridge const void *)stu));        
        
        /**輸出結果
        24
        32
        */ 
    }
    return 0;
}
  • 最重的輸出結果為:
    • class_getInstanceSize: 24
    • malloc_size: 32

isa:占用8個字節,_no:占用4個字節,_age:占用4個字節, _gender:占用4個字節, 不應該是一共占用了20個自己嗎?為什么是24個呢?

為什么會是24和32呢????

接下來我們就要對內存對齊的知識點作為一個了解 將在下一篇文章中講解

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。