從OC開始底層學習(2)
一、物件的記憶體分佈和影響物件記憶體的因素
我們先建立一個類,併為其分配屬性。
@interface LGPerson : NSObject @property (nonatomic ,copy) NSString *name; @property (nonatomic ,copy) NSString *hobby; @property (nonatomic ,assign) int age; @property (nonatomic ,assign) double hight; @property (nonatomic ,assign) short number; @end
然後我們為其賦值,並列印系統為其分配的記憶體,輸出結果為48,那如果我們為LGPerson新增方法呢?物件的記憶體是否會發現變化?顯然會不會的,因為物件的記憶體中儲存的是isa+成員變數的值,除此之外的其他不會對物件的記憶體產生影響。
LGPerson *p = [LGPerson new]; p.name = @"三夏"; p.hobby = @"girl"; p.hight = 1.80; p.age = 18; p.number = 123; NSLog(@"%lu",malloc_size((__bridge const void *)(p)));
那麼在物件的記憶體中成員變數儲存的值是按照什麼順序儲存的呢?有人想可能是按照物件的賦值順序,也有可能是按照屬性的初始化順序?其實物件中的成員變數儲存的順序是蘋果自動重排的,系統這這麼做的目的是為了優化記憶體,就比如這裡,int(4),char(1),short(2),他們完全可以在同一個8位元組的記憶體中儲存來達到優化記憶體的目的。那如果類被繼承,他的屬性的排序又是如何的呢?
@interface OSTestObject : NSObject { @public int count;(1) NSObject *objc1; NSObject *objc2; int count;(2) } @end @interface OSTestObject1 : OSTestObject { @public int _count2; } @end
NSLog(@"objc1實際佔用的記憶體空間為%zd",class_getInstanceSize([OSTestObject1 class]));// 40 48 NSLog(@"系統為objc1開闢的記憶體空間為%zd",malloc_size((__bridge const void *)objc1));//32 32
當我們更改count宣告的位置就會發現,就會發現objc1的記憶體大小發生了變化,這說明父類的成員變數的順序會對子類建立物件的記憶體大小產生影響,父子類的成員變數並沒有被系統自動重排。這是因為,子類在繼承父類時,父類的儲存空間就已經是連續的了,子類沒有辦法修改父類的資料結構,系統在重排時只是基於一個類來考慮的,並不會把父類和子類的成員變數一起重排。
二、聯合體和位域
2.1 聯合體
union LGTeacher2 { char *name; int age; int height; }t2; union LGTeacher3 { char a[7]; //7 int b; // 4 }t3; // 8 int main(int argc, char * argv[]) { NSString * appDelegateClassName; @autoreleasepool { // Setup code that might create autoreleased objects goes here. appDelegateClassName = NSStringFromClass([AppDelegate class]); t2.name = "三夏"; t2.age = 18; t2.height = 2.2; NSLog(@"%p %p %p",&t2,&t2.age,&t2.height); NSLog(@"%lu",sizeof(t2)); NSLog(@"%lu",sizeof(t3)); } }
在這裡,t2和t3都是聯合體的表達形式,聯合體又叫共用體,union就是在記憶體中劃了一個足夠用的空間(這塊空間是共用的),系統都不管這塊空間裡是怎麼儲存的,聯合體的成員變數就相當於為這塊記憶體空間開闢了幾個訪問途徑,他們共享這一塊記憶體。
- 聯合體的成員共用一塊記憶體空間,一次只能使用一個成員
- 聯合體可以定義多個成員,每個成員都是訪問這個聯合體的一條路徑
- 對某一個成員賦值,會覆蓋其他成員的值
- 可以節省一定的記憶體空間
這塊記憶體是如何分配的?
- 聯合體大小必須能容納聯合體中最大的成員變數
-
通過上面計算出的聯合體大小必須是聯合體中佔記憶體大小(最大的基本資料型別)大小的整數倍,比如char a[7] 佔了7個位元組,但是他的基本資料型別就是char,是一個位元組。
2.2 聯合體 和 結構體之間的區別
- 結構體(struct)中所有變數是“共存”的,而聯合體(union)中是各變數是“互斥”的,只能存在一個
- (結構體全分配,聯合體每次僅分配一個)struct記憶體空間的分配是粗放的,不管用不用,全部分配。這樣帶來的一個壞處就是對於記憶體的消耗要大一些。但是結構體裡面的資料是完整的。 而聯合體裡面的資料只能存在一個,但優點是記憶體使用更為精細靈活,也節省了記憶體空間。
2.3 位域
struct LGStruct2 { // a: 位域名 32:位域長度 int a : 32; char b : 2; char c : 7; char d : 2; }struct2;
這個是位域的寫法,可以節省記憶體空間,關於位域有幾點說明:
- 位域的型別說明符是 位域名:位域長度
- 位域的長度不能超過資料型別的最大長度
- 一個位域是儲存在同一個位元組當中的,如果這一個位元組所剩的空間不夠去存放另一個位域的時候, 另一個位域就會從下一個位元組開始存放
struct LGStruct2 { // a: 位域名 7:位域長度 char a : 7;//佔7位 char b : 2;//從上個位元組所剩空間位1位,不夠存放,則需要從下一個位元組開始存放 char c : 7;//從下一個位元組開始存放 char d : 2;//從下一個位元組開始存放 }struct2;
三、nonPointerIsa
在上次的alloc探索中,我們看到了_class_createInstanceFromZone方法內的initInstanceIsa方法,這個方法是把建立的物件通過物件內的isa指標來關聯到相應的類,isa指標內包含了物件所屬的類物件的記憶體地址。現在我們接著往下看:
我們發現在initInstanceIsa方法呼叫了initIsa方法,我們追根溯源發現了其內部就是對物件的isa指標進行初始化,同時我們發現了isa_t的資料型別。
isa_t就是一個聯合體,目的是為了相容之前的版本,現在系統使用的isa是nonPointIsa,
nonPointerIsa是記憶體優化的一種手段。isa是一個Class型別的結構體指標,佔8個位元組,主要是用 來存記憶體地址的。但是8個位元組意味著它就有8*8=64位。儲存地址根本不需要這麼多的記憶體空間。 而且每個物件都有個isa指標,這樣就浪費了記憶體。所以蘋果就把和物件一些息息相關的東⻄,存 在了這塊記憶體空間裡面。這種isa指標就叫nonPointerIsa。
四 isa的資料結構
對其中一些符號的解釋說明如下:
五 補充知識
p : <值型別> 引用 值 po:值 p/x : 以16位元組輸出 p/o:以8位元組輸出 p/t :以2位元組輸出 p/f:以浮點輸出 x:輸出記憶體地址 x/4gx:以16進位制的形式列印4個地址