Swift-進(jìn)階 :閉包(二)逃逸閉包 & 非逃逸閉包

本文主要分析逃逸閉包 请梢、非逃逸閉包专执、自動(dòng)閉包

逃逸閉包 & 非逃逸閉包

逃逸閉包定義

當(dāng)閉包作為一個(gè)實(shí)際參數(shù)傳遞給一個(gè)函數(shù)時(shí)途事,并且是在函數(shù)返回之后調(diào)用布卡,我們就說這個(gè)閉包逃逸了血崭。當(dāng)聲明一個(gè)接受閉包作為形式參數(shù)的函數(shù)時(shí)卧惜,可以在形式參數(shù)前寫@escaping明確閉包是允許逃逸

  • 如果用@escaping修飾閉包后,我們必須顯示的在閉包中使用self

  • swift3.0之后夹纫,系統(tǒng)默認(rèn)閉包參數(shù)就是被@nonescaping咽瓷,可以通過SIL來驗(yàn)證

    • 1、執(zhí)行時(shí)機(jī):在函數(shù)體內(nèi)執(zhí)行

    • 2舰讹、閉包生命周期:函數(shù)執(zhí)行完之后茅姜,閉包也就消失了

逃逸閉包的兩種調(diào)用情況

  • 1、延遲調(diào)用

  • 2月匣、作為屬性存儲(chǔ)钻洒,在后面進(jìn)行調(diào)用

1、作為屬性

當(dāng)閉包作為存儲(chǔ)屬性時(shí)锄开,主要有以下幾點(diǎn)說明:

  • 1素标、定義一個(gè)閉包屬性

  • 2、在方法中對(duì)閉包屬性進(jìn)行賦值

  • 3萍悴、在合適的時(shí)機(jī)調(diào)用(與業(yè)務(wù)邏輯相關(guān))

如下所示头遭,當(dāng)前的complitionHandler作為CJLTeacher的屬性寓免,是在方法makeIncrementer調(diào)用完成后才會(huì)調(diào)用,這時(shí)计维,閉包的生命周期要比當(dāng)前方法的生命周期長

//*********1袜香、閉包作為屬性
class CJLTeacher {
    //定義一個(gè)閉包屬性
    var complitionHandler: ((Int)->Void)?
    //函數(shù)參數(shù)使用@escaping修飾,表示允許函數(shù)返回之后調(diào)用
    func makeIncrementer(amount: Int, handler: @escaping (Int)->Void){
        var runningTotal = 0
        runningTotal += amount
        //賦值給屬性
        self.complitionHandler = handler
    }

    func doSomething(){
        self.makeIncrementer(amount: 10) {
            print($0)
        }
    }

    deinit {
        print("CJLTeacher deinit")
    }
}
//使用
var t = CJLTeacher()
t.doSomething()
t.complitionHandler?(10)

<!--打印結(jié)果-->
10

2享潜、延遲調(diào)用

  • 【延遲方法中使用】 1困鸥、在延遲方法中調(diào)用逃逸閉包
class CJLTeacher {
    //定義一個(gè)閉包屬性
    var complitionHandler: ((Int)->Void)?
    //函數(shù)參數(shù)使用@escaping修飾,表示允許函數(shù)返回之后調(diào)用
    func makeIncrementer(amount: Int, handler: @escaping (Int)->Void){
        var runningTotal = 0
        runningTotal += amount
        //賦值給屬性
        self.complitionHandler = handler

        //延遲調(diào)用
        DispatchQueue.global().asyncAfter(deadline: .now()+0.1) {
            print("逃逸閉包延遲執(zhí)行")
            handler(runningTotal)
        }
        print("函數(shù)執(zhí)行完了")
    }

    func doSomething(){
        self.makeIncrementer(amount: 10) {
            print($0)
        }
    }

    deinit {
        print("CJLTeacher deinit")
    }
}
//使用
var t = CJLTeacher()
t.doSomething()

<!--打印結(jié)果-->
函數(shù)執(zhí)行完了
逃逸閉包延遲執(zhí)行
10

當(dāng)前方法執(zhí)行的過程中不會(huì)等待閉包執(zhí)行完成后再執(zhí)行剑按,而是直接返回疾就,所以當(dāng)前閉包的生命周期要比方法長

逃逸閉包 vs 非逃逸閉包 區(qū)別

  • 非逃逸閉包:一個(gè)接受閉包作為參數(shù)的函數(shù),閉包是在這個(gè)函數(shù)結(jié)束前內(nèi)被調(diào)用艺蝴,即可以理解為閉包是在函數(shù)作用域結(jié)束前被調(diào)用

    • 1猬腰、不會(huì)產(chǎn)生循環(huán)引用,因?yàn)殚]包的作用域在函數(shù)作用域內(nèi)猜敢,在函數(shù)執(zhí)行完成后姑荷,就會(huì)釋放閉包捕獲的所有對(duì)象

    • 2、針對(duì)非逃逸閉包缩擂,編譯器會(huì)做優(yōu)化:省略內(nèi)存管理調(diào)用

    • 3鼠冕、非逃逸閉包捕獲的上下文保存在棧上,而不是堆上(官方文檔說明)胯盯。注:針對(duì)這點(diǎn)目前沒有驗(yàn)證出來懈费,有驗(yàn)證出來的童鞋歡迎留言解惑

  • 逃逸閉包:一個(gè)接受閉包作為參數(shù)的函數(shù),逃逸閉包可能會(huì)在函數(shù)返回之后才被調(diào)用博脑,即閉包逃離了函數(shù)的作用域

    • 1憎乙、可能會(huì)產(chǎn)生循環(huán)引用,因?yàn)樘右蓍]包中需要顯式的引用self(猜測其原因是為了提醒開發(fā)者叉趣,這里可能會(huì)出現(xiàn)循環(huán)引用了)泞边,而self可能是持有閉包變量的(與OC中block的的循環(huán)引用類似)

    • 2、一般用于異步函數(shù)的返回疗杉,例如網(wǎng)絡(luò)請(qǐng)求

  • 使用建議:如果沒有特別需要阵谚,開發(fā)中使用非逃逸閉包是有利于內(nèi)存優(yōu)化的,所以蘋果把閉包區(qū)分為兩種烟具,特殊情況時(shí)再使用逃逸閉包

自動(dòng)閉包

有下面一個(gè)例子椭蹄,當(dāng)conditiontrue時(shí),會(huì)打印錯(cuò)誤信息净赴,即如果是false绳矩,當(dāng)前條件不會(huì)執(zhí)行

//1、condition為false時(shí)玖翅,當(dāng)前條件不會(huì)執(zhí)行
func debugOutPrint(_ condition: Bool, _ message: String){
    if condition {
        print("cjl_debug: \(message)")
    }
}
debugOutPrint(true, "Application Error Occured")

  • 如果字符串是在某個(gè)業(yè)務(wù)邏輯中獲取的翼馆,會(huì)出現(xiàn)什么情況割以?
func debugOutPrint(_ condition: Bool, _ message: String){
    if condition {
        print("cjl_debug: \(message)")
    }
}
func doSomething() -> String{
    print("doSomething")
    return "Network Error Occured"
}

<!--如果傳入true-->
debugOutPrint(true, doSomething())
//打印結(jié)果
doSomething
cjl_debug: Network Error Occured

<!--如果傳入false-->
debugOutPrint(false, doSomething())
//打印結(jié)果
doSomething

通過結(jié)果發(fā)現(xiàn),無論是傳入true還是false应媚,當(dāng)前的方法都會(huì)執(zhí)行严沥,如果這個(gè)方法是一個(gè)非常耗時(shí)的操作,這里就會(huì)造成一定的資源浪費(fèi)中姜。所以為了避免這種情況消玄,需要將當(dāng)前參數(shù)修改為一個(gè)閉包

  • 【修改】:將message參數(shù)修改成一個(gè)閉包,需要傳入的是一個(gè)函數(shù)
//3丢胚、為了避免資源浪費(fèi)翩瓜,將當(dāng)前參數(shù)修改成一個(gè)閉包
func debugOutPrint(_ condition: Bool, _ message: () -> String){
    if condition {
        print("cjl_debug: \(message())")
    }
}
func doSomething() -> String{
    print("doSomething")
    return "Network Error Occured"
}
debugOutPrint(true, doSomething)

修改后運(yùn)行結(jié)果如下

  • 如果此時(shí)傳入一個(gè)string,又需要如何處理呢携龟?
    可以通過@autoclosure將當(dāng)前的閉包聲明成一個(gè)自動(dòng)閉包兔跌,不接收任何參數(shù),返回值是當(dāng)前內(nèi)部表達(dá)式的值峡蟋。所以當(dāng)傳入一個(gè)String時(shí)坟桅,其實(shí)就是將String放入一個(gè)閉包表達(dá)式中,在調(diào)用的時(shí)候返回

//4蕊蝗、將當(dāng)前參數(shù)修改成一個(gè)閉包仅乓,并使用@autoclosure聲明成一個(gè)自動(dòng)閉包
func debugOutPrint(_ condition: Bool, _ message: @autoclosure() -> String){
    if condition {
        print("cjl_debug: \(message())")
    }
}
func doSomething() -> String{
    print("doSomething")
    return "Network Error Occured"
}
<!--使用1:傳入函數(shù)-->
debugOutPrint(true, doSomething())

<!--使用2:傳入字符串-->
debugOutPrint(true, "Application Error Occured")

<!--打印結(jié)果-->
doSomething
cjl_debug: Network Error Occured

cjl_debug: Application Error Occured

自動(dòng)閉包就相當(dāng)于

debugOutPrint(true, "Application Error Occured")

相當(dāng)于用{}包裹傳入的對(duì)象,然后返回{}內(nèi)的值
{
    //表達(dá)式里的值
    return "Network Error Occured"
}

總結(jié)

  • 逃逸閉包:一個(gè)接受閉包作為參數(shù)的函數(shù)蓬戚,逃逸閉包可能會(huì)在函數(shù)返回之后才被調(diào)用方灾,即閉包逃離了函數(shù)的作用域,例如網(wǎng)絡(luò)請(qǐng)求碌更,需要在形參前面使用@escaping來明確閉包是允許逃逸的。

    • 一般用于異步函數(shù)的回調(diào)洞慎,比如網(wǎng)絡(luò)請(qǐng)求

    • 如果標(biāo)記為了@escaping痛单,必須在閉包中顯式的引用self

  • 非逃逸閉包:一個(gè)接受閉包作為參數(shù)的函數(shù),閉包是在這個(gè)函數(shù)結(jié)束前內(nèi)被調(diào)用劲腿,即可以理解為閉包是在函數(shù)作用域結(jié)束前被調(diào)用

  • 為什么要區(qū)分@escaping 和 @nonescaping旭绒?

    • 1、為了內(nèi)存管理焦人,閉包會(huì)強(qiáng)引用它捕獲的所有對(duì)象挥吵,這樣閉包會(huì)持有當(dāng)前對(duì)象,容易導(dǎo)致循環(huán)引用

    • 2花椭、非逃逸閉包不會(huì)產(chǎn)生循環(huán)引用忽匈,它會(huì)在函數(shù)作用域內(nèi)使用,編譯器可以保證在函數(shù)結(jié)束時(shí)閉包會(huì)釋放它捕獲的所有對(duì)象

    • 3矿辽、使用非逃逸閉包可以使編譯器應(yīng)用更多強(qiáng)有力的性能優(yōu)化丹允,例如郭厌,當(dāng)明確了一個(gè)閉包的生命周期的話,就可以省去一些保留(retain)和釋放(release)的調(diào)用

    • 4雕蔽、非逃逸閉包它的上下文的內(nèi)存可以保存在棧上而不是堆上

  • 總結(jié):如果沒有特別需要折柠,開發(fā)中使用非逃逸閉包是有利于內(nèi)存優(yōu)化的,所以蘋果把閉包區(qū)分為兩種批狐,特殊情況時(shí)再使用逃逸閉包

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末扇售,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子嚣艇,更是在濱河造成了極大的恐慌承冰,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件髓废,死亡現(xiàn)場離奇詭異巷懈,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)慌洪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門顶燕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人冈爹,你說我怎么就攤上這事涌攻。” “怎么了频伤?”我有些...
    開封第一講書人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵恳谎,是天一觀的道長。 經(jīng)常有香客問我憋肖,道長因痛,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任岸更,我火速辦了婚禮鸵膏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘怎炊。我一直安慰自己谭企,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開白布评肆。 她就那樣靜靜地躺著债查,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瓜挽。 梳的紋絲不亂的頭發(fā)上盹廷,一...
    開封第一講書人閱讀 51,698評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音久橙,去河邊找鬼速和。 笑死歹垫,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的颠放。 我是一名探鬼主播排惨,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼碰凶!你這毒婦竟也來了暮芭?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤欲低,失蹤者是張志新(化名)和其女友劉穎辕宏,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體砾莱,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瑞筐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了腊瑟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片聚假。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖闰非,靈堂內(nèi)的尸體忽然破棺而出膘格,到底是詐尸還是另有隱情,我是刑警寧澤财松,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布瘪贱,位于F島的核電站,受9級(jí)特大地震影響辆毡,放射性物質(zhì)發(fā)生泄漏菜秦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一舶掖、第九天 我趴在偏房一處隱蔽的房頂上張望球昨。 院中可真熱鬧,春花似錦访锻、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至避诽,卻和暖如春龟虎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背沙庐。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來泰國打工鲤妥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留佳吞,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓棉安,卻偏偏與公主長得像底扳,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子贡耽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容