OC對象的創(chuàng)建
我們經(jīng)常使用alloc,init,new 來創(chuàng)建對象,它們有什么區(qū)別呢
直接上代碼
LGPerson *p1 = [LGPerson alloc];
LGPerson *p2 = [p1 init];
LGPerson *p3 = [p1 init];
NSLog(@"p1---%@ - %p - %p", p1, p1, &p1);
NSLog(@"p2---%@ - %p - %p", p2, p2, &p2);
NSLog(@"p3---%@ - %p - %p", p3, p3, &p3);
打印結(jié)果如下:

首先解釋一下
-
%@打印的是對象 -
%p打印的指針地址 -
%p - &p打印的是指向?qū)ο髢?nèi)存的指針地址
由此可見 - 第一列:
p1,p2,p3相等,對象都是0x100b27a40 - 第二列:
p1,p2,p3相等,對象的指針地址都是0x100b27a40,指向同一內(nèi)存空間,說明了我們創(chuàng)建對象其實只用alloc就可以了,不用再init。 - 第三列:
&p1,&p2,&p3不相等, 打印的是指針地址,obj1、obj2、obj3三個不同指針, 地址不同
alloc探索
為什么創(chuàng)建對象只用alloc,不用再init,就可以創(chuàng)建一個對象?那我們就基于objc4-838(macOS 12.3 Xcode 13.2)進行探索一下,看系統(tǒng)具體是怎樣實現(xiàn)一個對象的創(chuàng)建的。
下載objc4-838之后打開工程,選擇SXObjcDebug工程

打開main文件,點擊[LGPerson alloc]方法

進入[NSObject alloc]方法

依次進去,并打上相應(yīng)流程斷點




好了,我們開始運行項目,需要注意的是運行項目之前需要把打得斷點取消掉,因為main函數(shù)之前系統(tǒng)也會創(chuàng)建很多的對象,我們只需要研究[LGPerson alloc]這個函數(shù)的具體實現(xiàn)就可以了。
首先定位到[LGPerson alloc]這行代碼,恢復(fù)剛打得斷點。

具體的調(diào)用流程依次是這樣的

我們發(fā)現(xiàn)先走了這段代碼callAlloc里面的
return ((id(*)(id, SEL))objc_msgSend)(cls, @selector(alloc));給這個class發(fā)了方法為alloc的一個消息,
打開函數(shù)調(diào)用棧,發(fā)現(xiàn)有一個objc_alloc函數(shù),很顯然是之前沒用調(diào)用[NSObject alloc]這個方法的

搜索源碼直接打上個斷點,再運行,發(fā)現(xiàn)首先運行了objc_alloc方法,然后依次執(zhí)行


alloc

_objc_rootAlloc

callAlloc -> _objc_rootAllocWithZone

_objc_rootAllocWithZone->_class_createInstanceFromZone

_class_createInstanceFromZone

這樣就總結(jié)出來了底層alloc的調(diào)用流程

使用符號斷點探索alloc
添加符號斷點,依次加入objc_alloc, callAlloc, [NSObject alloc],_objc_rootAlloc, _objc_rootAllocWithZone, _class_createInstanceFromZone等。注意main函數(shù)之前生成的對象也會走相應(yīng)的方法,要先把這些斷點disable,斷點到LGPerson *p1 = [LGPerson alloc];

需要注意的是alloc方法是一個類方法,加斷點的是需要把類名加上,如[NSObject alloc]

開跑,函數(shù)調(diào)用順序如下







可以看到_class_creatInstanceFromZone方法內(nèi)部是返回的是一個obj的也就是一個NSObject對象
init方法
打開源碼,init函數(shù)返回來_objc_rootInit,_objc_rootInit返回obj,看得出來,init方法沒有進行任何操作,這一點蘋果用到了工廠模式,當(dāng)我們需要自定義某個類時,可以通過重寫init方法來進行自定義操作。


編譯器優(yōu)化
編譯器優(yōu)化說明
- -O0 關(guān)閉所有優(yōu)化 代碼空間大,執(zhí)行效率低
- -O1 基本優(yōu)化等級 編譯器在不花費太多編譯時間基礎(chǔ)上,試圖生成更快、更小的代碼
- -O2 O1的升級版,推薦的優(yōu)化級別 編譯器試圖提高代碼性能,而不會增大體積和占用太多編譯時間
- -O3 最危險的優(yōu)化等級 會延長代碼編譯時間,生成更大體積、更耗內(nèi)存的二進制文件,大大增加編譯失敗的幾率和不可預(yù)知的程序行為,得不償失
- -Og O1基礎(chǔ)上,去掉了那些影響調(diào)試的優(yōu)化 如果最終是為了調(diào)試程序,可以使用這個參數(shù)。不過光有這個參數(shù)也是不行的,這個參數(shù)只是告訴編譯器,編譯后的代碼不要影響調(diào)試,但調(diào)試信息的生成還是靠 -g 參數(shù)的
- -Os O2基礎(chǔ)上,進一步優(yōu)化代碼尺寸 去掉了那些會導(dǎo)致最終可執(zhí)行程序增大的優(yōu)化,如果想要更小的可執(zhí)行程序,可選擇這個參數(shù)。
- -Ofast 優(yōu)化到破壞標(biāo)準(zhǔn)合規(guī)性的點(等效于-O3 -ffast-math ) 是在 -O3 的基礎(chǔ)上,添加了一些非常規(guī)優(yōu)化,這些優(yōu)化是通過打破一些國際標(biāo)準(zhǔn)(比如一些數(shù)學(xué)函數(shù)的實現(xiàn)標(biāo)準(zhǔn))來實現(xiàn)的,所以一般不推薦使用該參數(shù)。
舉個例子
int sum (int a , int b) {
return a + b;
}
在main函數(shù)里面調(diào)用該sum函數(shù),使用----分割sum函數(shù)調(diào)用前后。

運行打開匯編模式,我們會看到3和4的值

打開Build Setting在Debug模式下系統(tǒng)默認(rèn)是O0是不優(yōu)化的

修改Debug和Release模式的一樣的優(yōu)化等級Os

再運行一次,發(fā)現(xiàn)編譯器把sum函數(shù)也優(yōu)化掉了

對象的內(nèi)存對齊方式
alloc探索時 _class_createInstanceFromZone返回一個NSObject對象,我們看到這里面有一個instanceSize函數(shù),它是給對象計算需要的內(nèi)存空間的。

cmd+instanceSize點擊 依次進入


看到了這個內(nèi)存對其算法,在64位的架構(gòu)下WORD_MASK為7,在31位的架構(gòu)下WORD_MASK為3。
static inline uint32_t word_align(uint32_t x) {
return (x + WORD_MASK) & ~WORD_MASK;
}
在64位架構(gòu)下,假設(shè)一個對象實際內(nèi)存是10,即x=10,
x + WORD_MASK = 10 + 7 = 17 = 10001
~WORD_MASK = 17~7 = 11000
(x + WORD_MASK) & ~WORD_MASK = 10001 & 11000 = 10000 = 16
也可以
(x + WORD_MASK) >> 3 << 3 = 10001 >> 3 << 3 = 10 << 3 = 10000 = 16
所以這個對象實際需要的內(nèi)存大小就是16,蘋果是以8字節(jié)對齊來計算對象的內(nèi)存大小的。這樣就可以減少CPU的開銷,以空間換取時間。
結(jié)構(gòu)體內(nèi)存對齊
原則:
- 數(shù)據(jù)成員對?規(guī)則:結(jié)構(gòu)(struct)的第一個數(shù)據(jù)成員放
在offset為0的地方,以后每個數(shù)據(jù)成員存儲的起始位置要從
該成員大小或者成員的子成員大小的整數(shù)倍開始(比如int為4
字節(jié),則要從4的整數(shù)倍地址開始存儲)。 - 結(jié)構(gòu)體作為成員:如果一個結(jié)構(gòu)里有某些結(jié)構(gòu)體成員,則結(jié)
構(gòu)體成員要從其內(nèi)部最大元素大小的整數(shù)倍地址開始存
儲.(struct a里存有struct b,b里有char,int ,double
等元素,那b應(yīng)該從8的整數(shù)倍開始存儲)。 -
收尾工作:結(jié)構(gòu)體的總大小,也就是sizeof的結(jié)果必須是
其內(nèi)部最大成員的整數(shù)倍,不足的要補?。
數(shù)據(jù)類型占的字節(jié)數(shù):
數(shù)據(jù)類型占的字節(jié)數(shù).png
舉例:
double:8字節(jié) 從0位置開始需要8位,目前到7號位置
char:1字節(jié) 從8號位置開始存儲,需要1字節(jié),到8號位置
int:4字節(jié) 從9號位置開始存儲,需要4字節(jié),9不是4的倍數(shù),所以從12號位置開始存儲,到15位置
short:2字節(jié) 從16號位置開始存儲,需要2字節(jié),到17號位置
因為該結(jié)構(gòu)體內(nèi)的成員最大是double是字節(jié),17不是8的倍數(shù),所以需要32字節(jié)的內(nèi)存。
struct LGStruct1 {
double a; // 8字節(jié) [0 7] 從0到7
char b; // 1字節(jié) [8]
int c; // 4字節(jié) [12 13 14 15]
short d; // 2字節(jié) [16 17] 所以需要 24字節(jié)的內(nèi)存
}struct1;
驗證一下,打印結(jié)果是24,正確

調(diào)整一下結(jié)構(gòu)體內(nèi)部成員的順序
double:8字節(jié) 從0位置開始需要8位,目前到7號位置
int:4字節(jié) 從8號位置開始存儲,需要4字節(jié),到11位置
char:1字節(jié) 從12號位置開始存儲,需要1字節(jié),到12號位置
short:2字節(jié) 從13號位置開始存儲,需要2字節(jié),13不是2的倍數(shù),所以從14號位置開始存儲,到15位置
因為該結(jié)構(gòu)體內(nèi)的成員最大是double是字節(jié),15不是8的倍數(shù),所以需要16字節(jié)的內(nèi)存
struct LGStruct2 {
double a; // 8字節(jié) [0 7] 從0到7
int b; // 4字節(jié) [8 9 10 11]
char c; // 1字節(jié) [12]
short d; // 2字節(jié) [14 15] 所以需要 16字節(jié)的內(nèi)存
}struct2;
驗證一下,打印結(jié)果是16,正確

結(jié)構(gòu)體內(nèi)嵌結(jié)構(gòu)體,分析LGStruct3需要64字節(jié)
struct LGStruct3 {
double a; // 8字節(jié) [0 7] 從0到7
int b; // 4字節(jié) [8 9 10 11]
char c; // 1字節(jié) [12]
short d; // 2字節(jié) [14 15]
int e; // 4字節(jié) [16 17 18 19]
struct LGStruct1 struct1; // 24字節(jié) [24 47]
struct LGStruct2 struct2; // 16字節(jié) [48 63]
}struct3;
驗證一下,打印結(jié)果是64,perfect

