iOS objc_msgSend尾調用優(yōu)化詳解

這篇文章將認真徹底地分析 OC對objc_msgSend尾調用優(yōu)化。

Q1:什么是尾調用?

尾調用(Tail Call):某個函數(shù)的最后一步僅僅只是調用了一個函數(shù)(可以是自身,可以是另一個函數(shù))。

提醒:注意 “僅僅” 兩個字。

尾調用例子:
// 尾調用:
- (NSInteger)funcA:(NSInteger)num {

    /*  Some codes... */

    if (num == 0) {
        return [self funcA:num];//!< 尾調用->自身
    }    

    if (num > 0) {
        return [self funcB:num];//!< 尾調用->函數(shù)funcB
    }    

    return [self funcC:num];//!< 尾調用->函數(shù)funcC
}

正例解釋:funcA的最后一步僅僅調用了另一個函數(shù)。不論是調用funcA、funcB還是funcC都屬于尾調用。(不論調用函數(shù)的位置在哪,只要最后一步僅僅調用一個函數(shù)就行)

反例:不是尾調用的例子
// 不是尾調用1:
- (NSInteger)funcA:(NSInteger)num {

    NSInteger num = [self funcB:(num)];

    return num;//!< 不是尾調用->最后一步是返回一個值,而不是調用一個函數(shù)
}

反例解釋:不是尾調用。因為最后一步是返回一個值,而不是僅僅調用一個函數(shù)

// 不是尾調用2:
- (NSInteger)funcA:(NSInteger)num {

    return [self funcB:(num)] + 1;//!< 不是尾調用->原因:最后一步不僅調用了函數(shù)還有 +1 操作
}

反例解釋:不是尾調用。因為最后一步不僅調用了函數(shù)還有 +1 操作


Q2:OC的尾調用優(yōu)化體現(xiàn)在哪里?

小編準備了一個demo:通過“斷點”和“當前內存情況”查看有無尾調用優(yōu)化

場景一:無優(yōu)化 - 追加了.0不屬于尾調用

無優(yōu)化Demo效果圖:
無尾調用優(yōu)化

解釋:
這種場景下,每次函數(shù)調用一直在進棧,不斷申請??臻g,最后會棧溢出,最終導致崩潰。
空間復雜度O(n),時間復雜度O(n)。

下面請看圖解:

場景二:有尾調用優(yōu)化

優(yōu)化Demo效果圖:
尾調用優(yōu)化

解釋:
這種場景下,每次函數(shù)調用一直在重用棧幀,不申請??臻g。
空間復雜度O(1),時間復雜度O(n)。

下面請看圖解:

Q3:OC是如何實現(xiàn)尾調用優(yōu)化的?

這次討論起因于《Effective Objective-C 2.0》作者的原話:

如果某函數(shù)的最后一項操作是調用另外一個函數(shù),那么就可以運用“ 尾調用優(yōu)化 ”技術。編譯器會生成調轉至另一函數(shù)所需的指令碼,而且不會向調用堆棧中推入新的“棧幀”(frame stack)。只有當某函數(shù)的最后一個操作僅僅是調用其他函數(shù)而不會將其返回值另作他用時,才能執(zhí)行“ 尾調用優(yōu)化 ”
這項優(yōu)化對objc_msgSend非常關鍵,如果不這么做的話,那么每次調用Objective-C方法之前,都需要為調用objc_msgSend函數(shù)準備“棧幀”,大家在“棧蹤跡”(stack trace)中可以看到這種“棧幀”。此外,如果不優(yōu)化,還會過早地發(fā)生“棧溢出”(stack overflow)現(xiàn)象。

作者這一段概括的話,很精簡。而小編第一次看時,感覺很懵懂。在這里,QiShare對這段話進行了詳細的分析:

  1. 尾調用優(yōu)化的本質:很簡單,就是棧幀的復用。

  2. 尾調用優(yōu)化的條件有三點:

    • 尾調用函數(shù)不需要訪問當前棧幀中的變量。(變量可以作為形參,但是不能作為實參)
    • 尾調用返回后,函數(shù)沒有語句需要執(zhí)行。(最后一步僅僅只能執(zhí)行一個函數(shù))
    • 尾調用結果就是函數(shù)的返回值。(不能有別的“附加品”,最后一步僅僅只能是執(zhí)行一個函數(shù))
  3. 函數(shù)調用的過程:函數(shù)調用會在內存中申請一塊“棧幀”,保存調用的地址和內部變量等信息。如果函數(shù)A內部調用函數(shù)B,那么在函數(shù)A的棧幀上就會加上一個函數(shù)B的棧幀
    。如果函數(shù)B再調用了函數(shù)C,那么函數(shù)A的棧幀上就會有序加上函數(shù)B和函數(shù)C的棧幀。如果C運行結束了,返回到函數(shù)B,C的棧幀才會消失。

4. 尾調用優(yōu)化實現(xiàn)原理:當函數(shù)A的最后一步僅僅是調用另一個函數(shù)B時(或者調用自身函數(shù)A),這時,因為函數(shù)A的位置信息和內部變量已經不會再用到了,直接把函數(shù)A的棧幀交給函數(shù)B使用。

  1. 尾調用優(yōu)化關鍵圖解:

總結:
1. 尾調用:某個函數(shù)的最后一步僅僅調用了一個函數(shù)(可以是自身,可以是另一個函數(shù))。
2. OC的尾調用優(yōu)化的本質是:棧幀的復用
3. 尾調用優(yōu)化實現(xiàn)原理:當函數(shù)A的最后一步僅僅是調用另一個函數(shù)B時(或者調用自身函數(shù)A),這時,因為函數(shù)A的位置信息和內部變量已經不會再用到了,直接把函數(shù)A的棧幀交給函數(shù)B使用。

PS:尾調用優(yōu)化在Release模式下才會有,Debug模式下沒有。

本文Demo源碼地址

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容