這篇文章将認真徹底地分析 OC對 objc_msgSend 尾調用優化
的
。
Q1:什麼是尾調用?
尾調用(
Tail Call
):某個函數的最後一步僅僅隻是調用了一個函數(可以是自身,可以是另一個函數)。
提醒:注意 “僅僅” 兩個字。
尾調用例子:
// 尾調用:
- (NSInteger)funcA:(NSInteger)num {
/* Some codes... */
if (num == 0) {
return [self funcA:num];// 尾調用->自身
}
if (num > 0) {
return [self funcB:num];// 尾調用->函數funcB
}
return [self funcC:num];// 尾調用->函數funcC
}
正例解釋:funcA的最後一步僅僅調用了另一個函數。不論是調用funcA、funcB還是funcC都屬于尾調用。~(不論調用函數的位置在哪,隻要最後一步僅僅調用一個函數就行)~
反例:不是尾調用的例子
// 不是尾調用1:
- (NSInteger)funcA:(NSInteger)num {
NSInteger num = [self funcB:(num)];
return num;// 不是尾調用->最後一步是傳回一個值,而不是調用一個函數
}
反例解釋:不是尾調用。因為最後一步是傳回一個值,而不是僅僅調用一個函數
// 不是尾調用2:
- (NSInteger)funcA:(NSInteger)num {
return [self funcB:(num)] + 1;// 不是尾調用->原因:最後一步不僅調用了函數還有 +1 操作
}
反例解釋:不是尾調用。因為最後一步不僅調用了函數還有 +1 操作
Q2:OC的尾調用優化展現在哪裡?
小編準備了一個demo:通過“斷點”和“目前記憶體情況”檢視有無尾調用優化
場景一:無優化 - 追加了.0不屬于尾調用
無優化Demo效果圖:
**解釋:
這種場景下,每次函數調用一直在進棧,不斷申請棧空間,最後會棧溢出,最終導緻崩潰。
空間複雜度O(n),時間複雜度O(n)。**
下面請看圖解:
場景二:有尾調用優化
優化Demo效果圖:
這種場景下,每次函數調用一直在重用棧幀,不申請棧空間。
空間複雜度O(1),時間複雜度O(n)。**
Q3:OC是如何實作尾調用優化的?
這次讨論起因于《Effective Objective-C 2.0》作者的原話:
如果某函數的最後一項操作是調用另外一個函數,那麼就可以運用
技術。編譯器會生成調轉至另一函數所需的指令碼,而且不會向調用堆棧中推入新的
“ 尾調用優化 ”
(frame stack)。隻有當某函數的最後一個操作僅僅是調用其他函數而
“棧幀”
時,才能執行
不會将其傳回值另作他用
這項優化對
“ 尾調用優化 ”
非常關鍵,如果不這麼做的話,那麼每次調用Objective-C方法之前,都需要為調用objc_msgSend函數準備“棧幀”,大家在“棧蹤迹”(stack trace)中可以看到這種“棧幀”。此外,如果不優化,還會過早地發生“棧溢出”(stack overflow)現象。
objc_msgSend
作者這一段概括的話,很精簡。而小編第一次看時,感覺很懵懂。在這裡,QiShare對這段話進行了詳細的分析:
- 尾調用優化的本質:很簡單,就是 棧幀 的複用。
- 尾調用優化的條件有三點:
- 尾調用函數不需要通路目前棧幀中的變量。(變量可以作為形參,但是不能作為實參)
- 尾調用傳回後,函數沒有語句需要執行。(最後一步僅僅隻能執行一個函數)
- 尾調用結果就是函數的傳回值。(不能有别的“附加品”,最後一步僅僅隻能是執行一個函數)
-
函數調用的過程:函數調用會在記憶體中申請一塊“棧幀”,儲存調用的位址和内部變量等資訊。如果函數A内部調用函數B,那麼在函數A的棧幀上就會加上一個函數B的棧幀
。如果函數B再調用了函數C,那麼函數A的棧幀上就會有序加上函數B和函數C的棧幀。如果C運作結束了,傳回到函數B,C的棧幀才會消失。
4. 尾調用優化實作原理:當函數A的最後一步僅僅是調用另一個函數B時(或者調用自身函數A),這時,因為函數A的位置資訊和内部變量已經不會再用到了,直接把函數A的棧幀交給函數B使用。
- 尾調用優化關鍵圖解:
總結:1. 尾調用:某個函數的最後一步**僅僅**調用了一個函數(可以是自身,可以是另一個函數)。 2. OC的尾調用優化的本質是:[棧幀](https://baike.so.com/doc/9968763-10316382.html)的複用 3. 尾調用優化實作**原理**:當函數A的最後一步僅僅是調用另一個函數B時(或者調用自身函數A),這時,因為函數A的位置資訊和内部變量已經不會再用到了,直接把函數A的棧幀交給函數B使用。
PS:尾調用優化在Release模式下才會有,Debug模式下沒有。
本文Demo源碼位址