前言
很多剛開始寫Swift的同學或許已經把閉包應用在很多地方了,也總是會把閉包跟OC中的block劃等號,的確Swift中的的閉包跟OC中的block有很多相似之處,但是Swift畢竟是一門新的語言,出于性能、內存優化等原因的考慮,Swift比起OC而言在很多地方是有區別。以閉包來說,Swift 的閉包分為 逃逸 與 非逃逸 兩種。
逃逸閉包
概念:一個接受閉包作為參數的函數,逃逸閉包(可能)會在函數返回之后才被調用,也就是說閉包逃離了函數的作用域
場景舉例:網絡請求請求結束后才調用的閉包,因為發起請求后過了一段時間后這個閉包才執行,并不一定是在函數作用域內執行
func requestData() -> () {
CCNetWorkTool.sharedInstance.request(method: .POST, URLString: API_TEST parameters: nil) {[weak self] (response, isSuccess) in
// 請求失敗
if isSuccess == false {
// 失敗處理
return
}
// 請求成功
// ......
}
非逃逸閉包
概念:一個接受閉包作為參數的函數, 閉包是在這個函數結束前內被調用
注意:關于非逃逸的閉包有一個默認規則:除了作為函數的即時參數傳入的閉包是非逃逸的,其他類型的都是逃逸的。
場景舉例:我們常用的masonry或者snapkit的添加約束的方法就是非逃逸的,創建完子控件,然后添加到父視圖,緊接著就是需要把約束設置好,這種情況不需要再等什么條件滿足后再來回調,所以就要求閉包馬上執行。
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)
}
}
}
閉包為什么要分為逃逸和非逃逸
其實是為了管理內存。閉包會強引用它捕獲的所有對象,比如你在閉包中訪問了當前控制器的屬性、函數,編譯器會要求你在閉包中顯示 self 的引用(其實當前對象的屬性、函數隱形帶了一個self參數,一旦調用他們,隱形中就使用了self),這樣閉包會持有當前對象,容易導致循環引用。
非逃逸閉包不會產生循環引用,它會在函數作用域內釋放,編譯器可以保證在函數結束時閉包會釋放它捕獲的所有對象;使用非逃逸閉包的另一個好處是編譯器可以應用更多強有力的性能優化,例如,當明確了一個閉包的生命周期的話,就可以省去一些保留(retain)和釋放(release)的調用;此外非逃逸閉包它的上下文的內存可以保存在棧上而不是堆上。
綜上所述,如果沒有特別需要,開發中使用非逃逸閉包是有利于內存優化的,所以蘋果把閉包區分為兩種,特殊情況時再使用逃逸閉包
Swift3.0之后怎么允許一個閉包參數逃逸
Swift3.0之前閉包作為即時函數的參數默認為逃逸的(@escaping),但是很多開發者在開發中總是忽略去判斷閉包是否為逃逸,這樣就都被當做了逃逸閉包處理,對閉包的內存管理優化不太友好。Swift3.0中做出調整,所有作為即時函數的參數的閉包默認是非逃逸( @noescape)。如果開發者想使閉包具有逃逸性,需要用@escaping修飾閉包,@escaping 標識符還有警示開發者的作用,警示開發者這是一個逃逸閉包,注意循環引用問題,比如下面的代碼
以下是幾個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)
注意
可選型的閉包總是逃逸的:更令人驚訝的是,即便閉包被用作參數,但是當閉包被包裹在其他類型(例如元組、枚舉的 case 以及可選型)中的時候,閉包仍舊是逃逸的。由于在這種情況下閉包不再是即時的參數,它會自動變成逃逸閉包。
參考資料:
http://www.lxweimin.com/p/120069d493f5
http://swift.gg/2016/11/15/optional-non-escaping-closures/