本系列接《Effective Objective-C 2.0》一書中的系列文章。git
接下來,會有如下系列的主題的文章,以實踐探索Objective-C 2.0的一些特性,參考部分博文、官方文檔、以及MJ小碼哥的系列課程—很是推薦:框架
Objective-C(七)對象內存分析iphone
本文主要針對幾個類來窺探實例對象在內存中的存儲,咱們從成員變量和屬性入手,本文相關代碼在這兒。
咱們平時編寫的Objective-C代碼,底層實現其實都是C\C++代碼
因此Objective-C的面向對象都是基於C\C++的數據結構實現的
Objective-C的對象、類主要是基於C\C++的什麼數據結構實現的——結構體。
NSObject *obj = [[NSObject alloc] init];
// 得到NSObject實例對象的成員變量所佔用的大小 >> 8
NSLog(@"%zd", class_getInstanceSize([NSObject class]));
// 得到obj指針所指向內存的大小 >> 16
NSLog(@"%zu", malloc_size((__bridge const void *)obj));
複製代碼
上面有兩個函數:
建立一個實例對象,至少須要多少內存?
# import <objc/runtime.h>
class_getInstanceSize([NSObject class]);
建立一個實例對象,實際上分配了多少內存?
#import <malloc/malloc.h>
malloc_size((__bridge const void *)obj);
將Objective-C代碼轉換爲C\C++代碼
//若是須要連接其餘框架,使用-framework參數。好比-framework UIKit
// xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc OC源文件 -o 輸出的CPP文件
$ xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
複製代碼
其中,咱們能夠發現NSObject轉換爲C++的底層結構體爲:
//main.cpp
struct NSObject_IMPL {
Class isa; //typedef struct objc_class *Class;
};
複製代碼
咱們直接經過斷點調試也能夠發現,obj的確只有一個isa成員變量。
下面咱們經過查看obj對應的內存,來觀察:
從上圖中能夠看到,從0x1029000a0地址開始的8個字節,是有數據的,後面8個字節,都是0。
根據最開始打印的:
// 得到NSObject實例對象的成員變量所佔用的大小 >> 8
NSLog(@"%zd", class_getInstanceSize([NSObject class]));
// 得到obj指針所指向內存的大小 >> 16
NSLog(@"%zu", malloc_size((__bridge const void *)obj));
複製代碼
咱們猜想,前8個字節就是obj中isa
佔用的內存空間,後8個字節,是爲了內存對齊而分配的填充字節。
爲了驗證這個猜想,咱們將obj對象轉換爲對應的結構體:
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(@"%zu", malloc_size((__bridge const void *)(obj)));
struct NSObject_IMPL *objImpl = (__bridge struct NSObject_IMPL *)obj;
NSLog(@"obj address: %p", obj);
NSLog(@"objImpl address: %p, objImpl isa: %p", objImpl, objImpl->isa);
NSLog(@"-----");
}
return 0;
}
複製代碼
再次運行,輸出結果以下:
從上圖咱們能夠看出:
NSObject_IMPL
結構體,地址一致;isa
的值爲0x1dffffa4575141,與上一次運行一致,且只佔8個字節。isa
,8個字節爲內存對齊的填充字節。在這裏,爲何是16個字節,須要說明一下,iOS系統會給對象至少分配16*n字節的大小。
@interface BFPerson : NSObject
{
@public
int _age;
int _male;
}
@property (nonatomic, assign) double height;
@end
@implementation BFPerson
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
BFPerson *jack = [[BFPerson alloc] init];
jack->_age = 24;
jack->_male = 1;
jack.height = 185;
NSLog(@"jack age is %d, male: %d, height: %f", jack->_age, jack->_male, jack.height);
BFPerson *rose = [[BFPerson alloc] init];
rose->_age = 21;
rose->_male = 0;
rose.height = 165;
NSLog(@"rose age is %d, male: %d, height: %f", rose->_age, rose->_male, rose.height);
NSLog(@"%zd", class_getInstanceSize([BFPerson class]));
NSLog(@"%zd", malloc_size((__bridge const void *)jack));
}
return 0;
}
複製代碼
這一次,對象更復雜,並且繼承了NSObject,那麼其中實例對象中成員變量分配了多少字節,實際佔用了多少本身呢?
最後輸出:
經過將上面代碼轉換爲C++代碼,咱們能夠獲得BFPerson的結構:
其中,BFPerson_IMPL
包含了NSObject_IMPL
結構體,因此最後能夠規整爲:
能夠看到,第一個變量仍是isa指針,後面跟着咱們定義的兩個成員變量,及定義的一個屬性。
咱們知道屬性最後會轉換一個對應的成員變量,因此總共有三個成員變量。
其中isa,咱們知道是一個佔用8字節,因此獲得下面的各個成員變量的佔用字節數:
這和咱們打印該實例對象佔用的爲24個字節相符,可是系統仍是給它分配了32個字節。
咱們經過兩種方式來查看內存中的實例對象,是否是按咱們預想的方式存儲這些成員變量。
以下;
isa
值;isa
佔8個字節,但結構體最後佔用24字節,已是其整數倍。咱們經過前面轉換C++代碼結構體的分析,將jack轉換爲咱們自定義的結構體。
struct NSObject_IMPL {
Class isa;
};
struct BFPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
int _age;
int _male;
double _height;
};
struct BFPerson_IMPL *jackImpl = (__bridge struct BFPerson_IMPL *)jack;
NSLog(@"jack age is %d, male: %d, height: %f", jackImpl->_age, jackImpl->_male, jackImpl->_height);
複製代碼
輸出以下,能夠看到結果徹底一致,因此咱們的符合咱們的猜測。
繼承關係以下:
@interface BFPerson : NSObject
{
@public
int _age;
int _male;
}
@property (nonatomic, assign) double height;
@end
@implementation BFPerson
@end
@interface BFProgrammer : BFPerson
{
@public
char *company;
}
@end
@implementation BFProgrammer
@end
複製代碼
測試代碼以下;
BFPerson *jack = [[BFPerson alloc] init];
jack->_age = 24;
jack->_male = 1;
jack.height = 185;
NSLog(@"jack age is %d, male: %d, height: %f", jack->_age, jack->_male, jack.height);
NSLog(@"jack instance size: %zd", class_getInstanceSize([BFPerson class]));
NSLog(@"jack malloc size: %zd", malloc_size((__bridge const void *)jack));
BFProgrammer *tony = [[BFProgrammer alloc] init];
tony->_age = 28;
tony->_male = 1;
tony.height = 178;
tony->company = "Google";
NSLog(@"tony age is %d, male: %d, height: %f, company: %s", tony->_age, tony->_male, tony.height, tony->company);
NSLog(@"tony instance size:%zd", class_getInstanceSize([BFProgrammer class]));
NSLog(@"tony malloc size: %zd", malloc_size((__bridge const void *)tony));
複製代碼
對應的結果:
下面咱們分析下結果:
對於tony這個程序員:
其成員變量大小爲32字節,相對於jack這個BFPerson,多了8字節的內存變量。那麼這8個字節,用於存放char *company的指針。
咱們如今更進一步,從內存中直接讀取tony的公司名稱:Google。
Google是個C語言字符串常量,其存儲在內存中,採用ASCII字符編碼,其最後的結構爲:
從上面圖中,咱們發現tony所在地址爲0x103300700,根據其結構體,咱們能夠知道company所在地址爲:
compyan地址 = 0x103300700 + isa+ _age + _male + _height
= 0x103300700 + 8 + 4 + 4 + 8
= 0x103300718
複製代碼
對應的指令以下:
(lldb) x/2wx 0x103300718
0x103300718: 0x00000f52 0x00000001
(lldb) x/4wx 0x0000000100000f52
0x100000f52: 0x676f6f47 0x7400656c 0x20796e6f 0x20656761
(lldb) x/4wx 0x103300700
0x103300700: 0x00001391 0x001d8001 0x0000001c 0x00000001
複製代碼
Google
字符串,Google佔7個字節,注意是7個字節,最後一個字符爲'\0';下面咱們分析上面咱們經常使用的打印語句:
NSLog(@"%zd", class_getInstanceSize([NSObject class]));
NSLog(@"%zu", malloc_size((__bridge const void *)(obj)));
複製代碼
alloc
以及class_getInstanceSize
源碼z在Apple souce objc4庫中。
**[NSObject alloc]**代碼調用流程:
instanceSize
獲取的是alignedInstanceSize
。
alignedInstanceSize
小於16字節,會補齊爲16字節。
alignedInstanceSize
大於16字節,直接返回alignedInstanceSize
能夠看到,其中當上圖最後一步中的alignedInstanceSize,即通過結構體字節對齊後字節仍小於16字節,就會補齊爲16字節。
爲何須要補齊16字節呢?
代碼文檔有一行註釋:
CF requires all objects be at least 16 bytes.
或者咱們能夠理解爲OC對象爲了提升系統分配及查找地址的效率,而作的一個這樣的規定。
這也是上面第一個實例NSObject分析中,爲何實例對象實際佔用8字節,會分配16字節的緣由。alignedInstanceSize實際實際返回8字節,但calloc中的時候,size爲16字節。
size_t class_getInstanceSize(Class cls)
{
if (!cls) return 0;
return cls->alignedInstanceSize();
}
uint32_t alignedInstanceSize() {
return word_align(unalignedInstanceSize());
}
複製代碼
能夠看出,class_getInstanceSize
最後返回的就是上面所說的,通過字節對齊,可是在alloc
中calloc
以前的大小。
因此咱們看到其實際返回的是成員變量實際須要的空間大小。
calloc
代碼在另一個庫中——libmalloc。
其中源碼比較難以理解,因此直接給出結論。
calloc
返回的是系統實際分配的內存,最後返回的大小必定是16的倍數;
因此BFPerson實例中,成員變量佔24字節,但最後通過calloc返回的是32字節(16*2);
malloc_size返回的是對象指針所指向的大小,就是calloc實際分配的內存大小;
extern size_t malloc_size(const void *ptr);
/* Returns size of given ptr */
LLDB的使用請參考:待補--iOS調試(二)LLDB
Debug -> Debug Workfllow -> View Memory (Shift + Command + M)