本人2016年進入iOS開發(fā)行業(yè),時間一晃也工作了三四年了,寫過不少項目,代碼寫的也有幾萬行了,有的時候就會陷入思考,當我們寫完代碼編譯完成后怎么就運行到手機上成了一個App,這中間到底經(jīng)歷了什么過程?今天就把自己這幾年對于OC對象的了解分享一下,如有錯誤歡迎指正。
首先說明一下,為什么要了解和研究OC對象的本質(zhì)??
- 了解了對象的本質(zhì),也可以寫出更加優(yōu)秀,內(nèi)存占用更合理的代碼,提高系統(tǒng)的性能。
- 了解了本質(zhì),才可以在開發(fā)中游刃有余的玩轉(zhuǎn)各種黑科技,處理各種看似不可能完成的需求。
- 在面試中脫穎而出
查閱了很多資料最后得出此圖

搞過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)存圖


思考
- 創(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é)
圖示:

寫了這么多只寫到了成員變量那么屬性呢?請看以下代碼
@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é)了
