1. 程式人生 > >Objective-C 物件模型深入理解

Objective-C 物件模型深入理解

0x00序

本著加深對Objective-C 物件模型的理解和記憶目的,於是有了下文的簡單實踐操作。

0x01 疑問

在以下程式碼中,你能描述清楚以下問題嗎?

  1. TestClass的例項物件tcAtcB的記憶體結構是怎麼樣的
  2. 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"
; }

有什麼辦法可以直觀看到例項物件tcAtcB的記憶體結構嗎?

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被本身也具備的個例項變數:myIntnameage ,那麼我們可以猜測TestClass的例項物件的記憶體結構如下:

  1. 第一個8位元組58f25f05 01000000

    根據Little Endian 序(注2)規則”低地址存放低位,高地址存放高位“,那麼該位元組還原成十六進位制值是00000001055ff2580x1055ff258。與上述第4條日誌列印的TestCalss的地址相等。

  2. 第二個8位元組64000000 00000000

    同上,那麼該位元組還原成十六進位制值是000000 0000000064,十進位制為100。與tcA->myInt的值相等

  3. 第三個8位元組f8e15f05 01000000

    同上,那麼該位元組還原成十六進位制值是000000010055fe1f8,在gbd除錯環境下,通過輸出該地址的值,與tcA->name的值相等

    (lldb) po (NSString *)0x0000000100537218
    libai
    (lldb)
  4. 第四個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

  1. 如何確定指標大小

    int numb = 12;
    int *p = &numb;
    NSLog(@"指標p大小:%zu bytes", sizeof(p));//指標p大小:8 bytes
    
  2. 如何確定位元組序

    什麼是大端與小端(Little Endian 與 Big Endian)

    • Big Endian 是指低地址端 存放 高位位元組。
    • Little Endian 是指低地址端 存放 低位位元組。

    那麼,直接使用程式碼通過輸出直接來驗證是大端還是小端

    int a = 0x12345678;
    if( *((char*)&a) == 0x12)
      printf("Big Endian");
    else
      printf("Little Endian");

    輸出

    Little Endian