Swift逃逸閉包、非逃逸閉包(@escaping & @noescape)

前言

很多剛開始寫Swift的同學(xué)或許已經(jīng)把閉包應(yīng)用在很多地方了,也總是會把閉包跟OC中的block劃等號,的確Swift中的的閉包跟OC中的block有很多相似之處,但是Swift畢竟是一門新的語言,出于性能、內(nèi)存優(yōu)化等原因的考慮,Swift比起OC而言在很多地方是有區(qū)別。以閉包來說,Swift 的閉包分為 逃逸非逃逸 兩種。

逃逸閉包

概念:一個接受閉包作為參數(shù)的函數(shù),逃逸閉包(可能)會在函數(shù)返回之后才被調(diào)用,也就是說閉包逃離了函數(shù)的作用域
場景舉例:網(wǎng)絡(luò)請求請求結(jié)束后才調(diào)用的閉包,因為發(fā)起請求后過了一段時間后這個閉包才執(zhí)行,并不一定是在函數(shù)作用域內(nèi)執(zhí)行

func requestData() -> () {
        CCNetWorkTool.sharedInstance.request(method: .POST, URLString: API_TEST parameters: nil) {[weak self] (response, isSuccess) in
            // 請求失敗
            if isSuccess == false {
                // 失敗處理
                return
            }
            
            // 請求成功
            // ......
    }
非逃逸閉包

概念:一個接受閉包作為參數(shù)的函數(shù), 閉包是在這個函數(shù)結(jié)束前內(nèi)被調(diào)用
注意:關(guān)于非逃逸的閉包有一個默認規(guī)則:除了作為函數(shù)的即時參數(shù)傳入的閉包是非逃逸的,其他類型的都是逃逸的。

場景舉例:我們常用的masonry或者snapkit的添加約束的方法就是非逃逸的,創(chuàng)建完子控件,然后添加到父視圖,緊接著就是需要把約束設(shè)置好,這種情況不需要再等什么條件滿足后再來回調(diào),所以就要求閉包馬上執(zhí)行。

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let orangeView = UIView()
        orangeView.backgroundColor = UIColor.orange
        view.addSubview(orangeView)
        
        orangeView.snp.makeConstraints { (make) in
            make.topMargin.equalTo(40)
            make.leftMargin.equalTo(10)
            make.rightMargin.equalTo(10)
            make.bottomMargin.equalTo(40)
            make.center.equalTo(view.center)
        }
    }
}

閉包為什么要分為逃逸和非逃逸

其實是為了管理內(nèi)存。閉包會強引用它捕獲的所有對象,比如你在閉包中訪問了當前控制器的屬性、函數(shù),編譯器會要求你在閉包中顯示 self 的引用(其實當前對象的屬性、函數(shù)隱形帶了一個self參數(shù),一旦調(diào)用他們,隱形中就使用了self),這樣閉包會持有當前對象,容易導(dǎo)致循環(huán)引用。

非逃逸閉包不會產(chǎn)生循環(huán)引用,它會在函數(shù)作用域內(nèi)釋放,編譯器可以保證在函數(shù)結(jié)束時閉包會釋放它捕獲的所有對象;使用非逃逸閉包的另一個好處是編譯器可以應(yīng)用更多強有力的性能優(yōu)化,例如,當明確了一個閉包的生命周期的話,就可以省去一些保留(retain)和釋放(release)的調(diào)用;此外非逃逸閉包它的上下文的內(nèi)存可以保存在棧上而不是堆上。

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

Swift3.0之后怎么允許一個閉包參數(shù)逃逸

Swift3.0之前閉包作為即時函數(shù)的參數(shù)默認為逃逸的(@escaping),但是很多開發(fā)者在開發(fā)中總是忽略去判斷閉包是否為逃逸,這樣就都被當做了逃逸閉包處理,對閉包的內(nèi)存管理優(yōu)化不太友好。Swift3.0中做出調(diào)整,所有作為即時函數(shù)的參數(shù)的閉包默認是非逃逸( @noescape)。如果開發(fā)者想使閉包具有逃逸性,需要用@escaping修飾閉包,@escaping 標識符還有警示開發(fā)者的作用,警示開發(fā)者這是一個逃逸閉包,注意循環(huán)引用問題,比如下面的代碼


逃逸閉包1.png

以下是幾個GCD的API用到了逃逸閉包

public func __dispatch_after(_ when: dispatch_time_t, _ queue: DispatchQueue, _ block: @escaping () -> Swift.Void)

public func __dispatch_barrier_async(_ queue: DispatchQueue, _ block: @escaping () -> Swift.Void)
注意

可選型的閉包總是逃逸的:更令人驚訝的是,即便閉包被用作參數(shù),但是當閉包被包裹在其他類型(例如元組、枚舉的 case 以及可選型)中的時候,閉包仍舊是逃逸的。由于在這種情況下閉包不再是即時的參數(shù),它會自動變成逃逸閉包。

參考資料:
http://www.itdecent.cn/p/120069d493f5
http://swift.gg/2016/11/15/optional-non-escaping-closures/

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

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容