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

這篇文章將認(rèn)真徹底地分析 OC對(duì)objc_msgSend尾調(diào)用優(yōu)化

Q1:什么是尾調(diào)用慰于?

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

提醒:注意 “僅僅” 兩個(gè)字东囚。

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

    /*  Some codes... */

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

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

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

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

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

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

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

反例解釋:不是尾調(diào)用份帐。因?yàn)樽詈笠徊绞欠祷匾粋€(gè)值璃吧,而不是僅僅調(diào)用一個(gè)函數(shù)

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

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

反例解釋:不是尾調(diào)用。因?yàn)樽詈笠徊讲粌H調(diào)用了函數(shù)還有 +1 操作


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

小編準(zhǔn)備了一個(gè)demo:通過“斷點(diǎn)”和“當(dāng)前內(nèi)存情況”查看有無尾調(diào)用優(yōu)化

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

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

解釋:
這種場景下畜挨,每次函數(shù)調(diào)用一直在進(jìn)棧,不斷申請椮迹空間巴元,最后會(huì)棧溢出,最終導(dǎo)致崩潰驮宴。
空間復(fù)雜度O(n)逮刨,時(shí)間復(fù)雜度O(n)。

下面請看圖解:

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

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

解釋:
這種場景下堵泽,每次函數(shù)調(diào)用一直在重用棧幀修己,不申請椈肿埽空間。
空間復(fù)雜度O(1)睬愤,時(shí)間復(fù)雜度O(n)片仿。

下面請看圖解:

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

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

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

作者這一段概括的話辜昵,很精簡荸镊。而小編第一次看時(shí),感覺很懵懂堪置。在這里躬存,QiShare對(duì)這段話進(jìn)行了詳細(xì)的分析:

  1. 尾調(diào)用優(yōu)化的本質(zhì):很簡單,就是棧幀的復(fù)用舀锨。

  2. 尾調(diào)用優(yōu)化的條件有三點(diǎn):

    • 尾調(diào)用函數(shù)不需要訪問當(dāng)前棧幀中的變量岭洲。(變量可以作為形參,但是不能作為實(shí)參)
    • 尾調(diào)用返回后坎匿,函數(shù)沒有語句需要執(zhí)行盾剩。(最后一步僅僅只能執(zhí)行一個(gè)函數(shù))
    • 尾調(diào)用結(jié)果就是函數(shù)的返回值。(不能有別的“附加品”替蔬,最后一步僅僅只能是執(zhí)行一個(gè)函數(shù))
  3. 函數(shù)調(diào)用的過程:函數(shù)調(diào)用會(huì)在內(nèi)存中申請一塊“棧幀”告私,保存調(diào)用的地址和內(nèi)部變量等信息。如果函數(shù)A內(nèi)部調(diào)用函數(shù)B承桥,那么在函數(shù)A的棧幀上就會(huì)加上一個(gè)函數(shù)B的棧幀
    驻粟。如果函數(shù)B再調(diào)用了函數(shù)C,那么函數(shù)A的棧幀上就會(huì)有序加上函數(shù)B和函數(shù)C的棧幀快毛。如果C運(yùn)行結(jié)束了格嗅,返回到函數(shù)B番挺,C的棧幀才會(huì)消失。

4. 尾調(diào)用優(yōu)化實(shí)現(xiàn)原理:當(dāng)函數(shù)A的最后一步僅僅是調(diào)用另一個(gè)函數(shù)B時(shí)(或者調(diào)用自身函數(shù)A)屯掖,這時(shí)玄柏,因?yàn)楹瘮?shù)A的位置信息和內(nèi)部變量已經(jīng)不會(huì)再用到了,直接把函數(shù)A的棧幀交給函數(shù)B使用贴铜。

  1. 尾調(diào)用優(yōu)化關(guān)鍵圖解:

總結(jié):
1. 尾調(diào)用:某個(gè)函數(shù)的最后一步僅僅調(diào)用了一個(gè)函數(shù)(可以是自身粪摘,可以是另一個(gè)函數(shù))。
2. OC的尾調(diào)用優(yōu)化的本質(zhì)是:棧幀的復(fù)用
3. 尾調(diào)用優(yōu)化實(shí)現(xiàn)原理:當(dāng)函數(shù)A的最后一步僅僅是調(diào)用另一個(gè)函數(shù)B時(shí)(或者調(diào)用自身函數(shù)A)绍坝,這時(shí)徘意,因?yàn)楹瘮?shù)A的位置信息和內(nèi)部變量已經(jīng)不會(huì)再用到了,直接把函數(shù)A的棧幀交給函數(shù)B使用轩褐。

PS:尾調(diào)用優(yōu)化在Release模式下才會(huì)有椎咧,Debug模式下沒有。

本文Demo源碼地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末把介,一起剝皮案震驚了整個(gè)濱河市勤讽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌拗踢,老刑警劉巖脚牍,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異巢墅,居然都是意外死亡诸狭,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門君纫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來驯遇,“玉大人,你說我怎么就攤上這事庵芭∶美粒” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵双吆,是天一觀的道長眨唬。 經(jīng)常有香客問我,道長好乐,這世上最難降的妖魔是什么匾竿? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮蔚万,結(jié)果婚禮上岭妖,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好昵慌,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布假夺。 她就那樣靜靜地躺著,像睡著了一般斋攀。 火紅的嫁衣襯著肌膚如雪已卷。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天淳蔼,我揣著相機(jī)與錄音侧蘸,去河邊找鬼。 笑死鹉梨,一個(gè)胖子當(dāng)著我的面吹牛讳癌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播存皂,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼晌坤,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了旦袋?” 一聲冷哼從身側(cè)響起泡仗,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎猜憎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體搔课,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡胰柑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了爬泥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片柬讨。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖袍啡,靈堂內(nèi)的尸體忽然破棺而出踩官,到底是詐尸還是另有隱情,我是刑警寧澤境输,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布蔗牡,位于F島的核電站,受9級(jí)特大地震影響嗅剖,放射性物質(zhì)發(fā)生泄漏辩越。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一信粮、第九天 我趴在偏房一處隱蔽的房頂上張望黔攒。 院中可真熱鬧,春花似錦、人聲如沸督惰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赏胚。三九已至访娶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間栅哀,已是汗流浹背震肮。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留留拾,地道東北人戳晌。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像痴柔,于是被迫代替她去往敵國和親沦偎。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345