Objective-C 物件模型深入理解
0x00序
本著加深對Objective-C 物件模型的理解和記憶目的,於是有了下文的簡單實踐操作。
0x01 疑問
在以下程式碼中,你能描述清楚以下問題嗎?
TestClass
的例項物件tcA
和tcB
的記憶體結構是怎麼樣的TestClass
的例項物件的大小
@interface TestClass : NSObject
{
@public
int myInt;
NSString *name;
}
@property (nonatomic, assign) NSInteger age; ///< 年齡
@end
@implementation TestClass
@end
int main(int argc, char * argv[]) {
@autoreleasepool {
//建立例項tcA
TestClass *tcA = [[TestClass alloc] init];
tcA->myInt = 100;
tcA.age = 7;
tcA->name = @"a";
//建立例項tcB
TestClass *tcB = [[TestClass alloc] init];;
tcB->myInt = 101;
tcB.age = 5;
tcB->name = @"A" ;
}
有什麼辦法可以直觀看到例項物件tcA
或tcB
的記憶體結構嗎?
0x02 直觀瞭解例項物件記憶體結構
通過NSData
的+ (instancetype)dataWithBytes:(nullable const void *)bytes length:(NSUInteger)length;
方法,可以我們直觀看到物件的記憶體結構!
那麼,修改main函式如下:
int main(int argc, char * argv[]) {
//建立例項tcA
TestClass *tcA = [[TestClass alloc] init];
tcA->myInt = 100 ;
tcA.age = 7;
tcA->name = @"libai";
//建立例項tcB
TestClass *tcB = [[TestClass alloc] init];;
tcB->myInt = 101;
tcB.age = 5;
tcB->name = @"gongben";
//新增程式碼,將物件轉換為位元組流Data
long tcSize = class_getInstanceSize([TestClass class]);
NSData *tcAData = [NSData dataWithBytes:(__bridge const void *)tcA length:tcSize];
NSData *tcBData = [NSData dataWithBytes:(__bridge const void *)tcB length:tcSize];
NSLog(@"TestClass object tcA contans %@", tcAData);
NSLog(@"TestClass object tcB contans %@", tcBData);
NSLog(@"tcSize size= %zu", tcSize);
NSLog(@"TestCalss memory address = %p", [TestClass class]);
}
輸出日誌如下:
2018-05-10 14:54:27.068685+0800 TestApp[79148:1558980] TestClass object tcA contans <58f25f05 01000000 64000000 00000000 f8e15f05 01000000 07000000 00000000>
2018-05-10 14:54:27.069407+0800 TestApp[79148:1558980] TestClass object tcB contans <58f25f05 01000000 65000000 00000000 18e25f05 01000000 05000000 00000000>
2018-05-10 14:54:27.069653+0800 TestApp[79148:1558980] tcSize size= 32
2018-05-10 14:54:27.069795+0800 TestApp[79148:1558980] TestCalss memory address = 0x1055ff258
0x03 位元組流分析
那麼下面這兩位元組流分別怎麼理解?
TestClass object tcA contans <58f25f05 01000000 64000000 00000000 f8e15f05 01000000 07000000 00000000>
TestClass object tcB contans <58f25f05 01000000 65000000 00000000 18e25f05 01000000 05000000 00000000>
首先我們知道TestClass
繼承於NSObject
, 而NSObject
的結構如下,它只有一個成員或者叫例項變數
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
那麼結合TestClass
被本身也具備的個例項變數:myInt
、name
、age
,那麼我們可以猜測TestClass
的例項物件的記憶體結構如下:
第一個8位元組
58f25f05 01000000
根據Little Endian 序(注2)規則”低地址存放低位,高地址存放高位“,那麼該位元組還原成十六進位制值是
00000001055ff258
即0x1055ff258
。與上述第4條日誌列印的TestCalss
的地址相等。第二個8位元組
64000000 00000000
同上,那麼該位元組還原成十六進位制值是
000000 0000000064
,十進位制為100
。與tcA->myInt
的值相等第三個8位元組
f8e15f05 01000000
同上,那麼該位元組還原成十六進位制值是
000000010055fe1f8
,在gbd除錯環境下,通過輸出該地址的值,與tcA->name
的值相等(lldb) po (NSString *)0x0000000100537218 libai (lldb)
第四個8位元組
07000000 00000000
同上,那麼該位元組還原成十六進位制值是
000000 0000000007
,十進位制為7
。與tcA.age
的值相等
由此可見,例項物件的記憶體結構正如上圖那樣分佈!
0x04 例項物件大小
從第三條日誌得到,tcA或者tcB的記憶體大小都是32位元組
:
2018-05-10 14:54:27.069653+0800 TestApp[79148:1558980] tcSize size= 32
通過計算各個成員變數的值,得到的結果是28位元組
:
sizeof(tcA->myInt) + sizeof(tcA->name) + sizeof(tcA.age) + sizeof(tcA->isa) = 4 + 8 + 8 + 8 = 28
也就是說,sizeof(物件)的大小,並不一定等於各個資料成員的大小之和!這涉及到”記憶體位元組對齊”
struct x_ {
char a; // 1 byte
int b; // 4 bytes
short c; // 2 bytes
char d; // 1 byte
} MyStruct1;
struct y_ {
int b; // 4 bytes
char a; // 1 byte
char d; // 1 byte
short c; // 2 bytes
} MyStruct2;
NSLog(@"%lu,%lu", sizeof(MyStruct1), sizeof(MyStruct2));
上述程式碼打印出來的結果為:12,8
注
如何確定指標大小
int numb = 12; int *p = &numb; NSLog(@"指標p大小:%zu bytes", sizeof(p));//指標p大小:8 bytes
如何確定位元組序
什麼是大端與小端(Little Endian 與 Big Endian)
- Big Endian 是指低地址端 存放 高位位元組。
- Little Endian 是指低地址端 存放 低位位元組。
那麼,直接使用程式碼通過輸出直接來驗證是大端還是小端
int a = 0x12345678; if( *((char*)&a) == 0x12) printf("Big Endian"); else printf("Little Endian");
輸出
Little Endian