iOS OC對象的本質(zhì)窺探(一)

本人2016年進入iOS開發(fā)行業(yè),時間一晃也工作了三四年了,寫過不少項目,代碼寫的也有幾萬行了,有的時候就會陷入思考,當我們寫完代碼編譯完成后怎么就運行到手機上成了一個App,這中間到底經(jīng)歷了什么過程?今天就把自己這幾年對于OC對象的了解分享一下,如有錯誤歡迎指正。
首先說明一下,為什么要了解和研究OC對象的本質(zhì)??
  • 了解了對象的本質(zhì),也可以寫出更加優(yōu)秀,內(nèi)存占用更合理的代碼,提高系統(tǒng)的性能。
  • 了解了本質(zhì),才可以在開發(fā)中游刃有余的玩轉(zhuǎn)各種黑科技,處理各種看似不可能完成的需求。
  • 在面試中脫穎而出
查閱了很多資料最后得出此圖
1577366532813.jpg
搞過iOS的同學對此并不陌生,大家都知道OC會編譯成C++,那么OC在內(nèi)存中是怎么存儲的?是以什么方式存儲的呢?今天就來看看。
  • 新建一個工程寫這么一行代碼
NSObject *obj = [[NSObject alloc] init];
  • 按住command點進去可以看到蘋果官方對NSObject的聲明
@interface NSObject <NSObject> {
    Class isa  OBJC_ISA_AVAILABILITY;
}
  • 將OC轉(zhuǎn)化成C++
    具體的怎么轉(zhuǎn)化請移步簡書OC轉(zhuǎn)化C++
  • 拿到轉(zhuǎn)化后的C++代碼,我們發(fā)現(xiàn)NSObject在C++中成了這個
//NSObject底層實現(xiàn)
struct NSObject_IMPL {
    Class isa; 
};

struct what?這不是結(jié)構(gòu)體嗎?對,是的,OC對象轉(zhuǎn)化成C++代碼后就是通過結(jié)構(gòu)體的形式存儲在內(nèi)存中的。一個對象可能多個不同類型的屬性,所以以結(jié)構(gòu)體的形式存儲。

通過上面的探索我們得出OC對象在編譯的過程中轉(zhuǎn)化成了C++代碼,并且以結(jié)構(gòu)體的形式在內(nèi)存中存儲。并且由一個指針指向這塊內(nèi)存空間,那么問題來了一個NSObject對象在沒有成員自定義成員變量的情況下在內(nèi)存中占了多大的內(nèi)存空間。
//NSObject底層實現(xiàn)
struct NSObject_IMPL {
    Class isa; 
};
這行代碼中的isa是個指針,那么一個指針在64bit環(huán)境下占8個字節(jié),在32bit環(huán)境下占4個字節(jié)。那個NSObject只擁有一個Class isa成員變量是不是占有8個字節(jié)呢?
驗證
// 獲得NSObject實例對象的成員變量所占用的大小 >> 8
NSLog(@"%zd", class_getInstanceSize([NSObject class]));
輸出 8

// 獲得obj指針所指向內(nèi)存的大小 >> 16
 NSLog(@"%zd", malloc_size((__bridge const void *)obj));
輸出 16

一個 NSObject 對象在創(chuàng)建后系統(tǒng)分配了16個字節(jié),只是自己的成員變量占用了8個字節(jié)
或者可以說成
系統(tǒng)分配了16個字節(jié)給NSObject對象(通過malloc_size函數(shù)獲得) 但NSObject對象內(nèi)部只使用了8個字節(jié)的空間(64bit環(huán)境下,可以通過class_getInstanceSize函數(shù)獲得)

原因:蘋果的規(guī)定吧,通過malloc_size方法的底層實現(xiàn)我們可以看到,當系統(tǒng)對一個對象分配的內(nèi)存<16字節(jié)時,系統(tǒng)會強制分配16字節(jié),也就是說對于一個對象系統(tǒng)最少分配16字節(jié)。

進一步驗證
  • 創(chuàng)建一個Student類,寫兩個成員變量
@interface Student : NSObject
{
    @public
    int _no;
    int _age;
}
@end

@implementation Student

@end
  • 拿到C++代碼,我們發(fā)現(xiàn)長成了這個樣子
struct Student_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _no;
    int _age;
};

通過查找發(fā)現(xiàn)NSObject_IMPL 是這個樣子

struct NSObject_IMPL {
    Class isa;
};

匯總: Student對象轉(zhuǎn)化成C++后最終結(jié)果

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

但我們在執(zhí)行這段代碼的時候

Student *stu = [[Student alloc] init];
stu->_no = 4;
stu->_age = 5;
        
NSLog(@"%zd", class_getInstanceSize([Student class]));
NSLog(@"%zd", malloc_size((__bridge const void *)stu));

實際是stu 指針指向了struct Student_IMPL這個結(jié)構(gòu)體所對應(yīng)的內(nèi)存空間,這個結(jié)構(gòu)體的內(nèi)存空間地址,就是該結(jié)構(gòu)體第一個元素的內(nèi)存地址 Class isa;也就是isa指針指向的地址

  • 驗證
struct Student_IMPL *stuImpl = (__bridge struct Student_IMPL *)stu;
NSLog(@"no is %d, age is %d", stuImpl->_no, stuImpl->_age);
輸出 4 5
  • 那個這個Student對象占據(jù)內(nèi)存空間大???系統(tǒng)給該對象分配內(nèi)存大小呢?

分析: 一個指針占8個字節(jié),一個int類型占4個字節(jié),兩個那就是8個字節(jié),合計16個字節(jié)

NSLog(@"%zd", class_getInstanceSize([Student class]));
輸出 16
NSLog(@"%zd", malloc_size((__bridge const void *)stu));
輸出 16

NSObject對象和Student對象所對應(yīng)的內(nèi)存圖

NSObject
]
Student

思考
  • 創(chuàng)建一個Person類,寫一個成員變量
@interface Person : NSObject
{
    @public
    int _no;
}
@end

@implementation Person

@end
  • 再創(chuàng)建一個Student類繼承與Person類,寫一個成員變量
@interface Student : Person
{
    @public
    int _age;
}
@end

@implementation Student

@end
那么Person 和 Student 分別占用多大的內(nèi)存呢?

答案:16,16
分析:通過上面的驗證可以知道Person 轉(zhuǎn)化成C++

struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS; //8字節(jié)
    int _no; //四字節(jié)
};

那么這兩個字節(jié)相加應(yīng)該是12字節(jié)?為什么是16字節(jié)呢,因為上面提到過,蘋果為一個實例對象開辟的最小內(nèi)存空間為16字節(jié)(這是規(guī)定),還有就是內(nèi)存對齊原則,分配的總內(nèi)存數(shù)是最大成員變量的整數(shù)倍,其中最大的成員變量是Class isa指針,8字節(jié),整數(shù)倍也就是16字節(jié)

分析:通過上面的驗證也可以知道Student 轉(zhuǎn)化成C++

struct Student_IMPL {
    struct Person_IMPL Person_IVARS; //8字節(jié)
    int _no; //四字節(jié)
    int _age//四字節(jié)
};

三個成員變量相加正好16字節(jié),又是8的整數(shù)倍,所以分配16字節(jié)

圖示:
關(guān)系圖.jpg
寫了這么多只寫到了成員變量那么屬性呢?請看以下代碼
@interface Student : NSObject
{
    @public
    int _age;
}
@property (copy,assign) int weight;
@end

@implementation Student

@end
那么創(chuàng)建一個這樣的Student對象占多大的內(nèi)存呢?

答案: 16個 屬性weight 會自動生成_weight 成員變量,當然也還有g(shù)et和set方法,那么兩個成員變量8字節(jié),一個指針8字節(jié),相加16字節(jié)。

總結(jié):一個實例對象在創(chuàng)建時至少分配16字節(jié),每個對象都會有一個isa指針,再加上成員變量所占空間就是分配的總空間。
補充:指針類型成員變量如NSString *str, double number, long height 占8字節(jié)在64bit機器下,int float 占4字節(jié),OC對象分配內(nèi)存遵循內(nèi)存對齊法則。

蘋果內(nèi)存對齊法則:分配的總內(nèi)存數(shù)是最大成員變量的整數(shù)倍
那么真的是這樣嗎?還有什么其他的嗎?

下面一探究竟:
  • 創(chuàng)建一個Person對象
@interface Person : NSObject
{
    int _age;
    int _height;
    int _no;
}
@end

@implementation Person

@end

  • 執(zhí)行代碼
MJPerson *p = [[MJPerson alloc] init];
NSLog(@"%zd %zd",
              class_getInstanceSize([MJPerson class]), // 24
              malloc_size((__bridge const void *)(p))); // 32
輸出 24 32
  • 分析:
    一個Person對象,有_age,_height,_no,isa,這四個成員變量三個int類型,12字節(jié),一個isa 指針8字節(jié),總共20字節(jié)。很容易理解內(nèi)存對齊 法則:成員變量所占內(nèi)存最大成員變量的整數(shù)倍,所以占用了24字節(jié)。malloc_size 這個方法返回32字節(jié),說明操作系統(tǒng)實際為這個對象分配了32字節(jié),這個為什么?
    通過查看源碼我們發(fā)現(xiàn):
    1578315925798.jpg

    操作系統(tǒng)為對象分配的內(nèi)存空間都是16的整數(shù)倍,所以也就有了malloc_size 這個方法返回32字節(jié)了
思考: 通過對實例對象的研究,發(fā)現(xiàn)在實例對象中存儲的只有成員變量,那么一個類的對象方法還有類方法存儲在那里呢?

請看下篇iOS OC對象的本質(zhì)窺探(對象分類)(二)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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