前言
很多剛開始寫Swift的同學(xué)或許已經(jīng)把閉包應(yīng)用在很多地方了,也總是會(huì)把閉包跟OC中的block劃等號(hào)解幽,的確Swift中的的閉包跟OC中的block有很多相似之處,但是Swift畢竟是一門新的語言烘苹,出于性能躲株、內(nèi)存優(yōu)化等原因的考慮,Swift比起OC而言在很多地方是有區(qū)別镣衡。以閉包來說霜定,Swift 的閉包分為 逃逸 與 非逃逸 兩種吞琐。
逃逸閉包
概念:一個(gè)接受閉包作為參數(shù)的函數(shù),逃逸閉包(可能)會(huì)在函數(shù)返回之后才被調(diào)用然爆,也就是說閉包逃離了函數(shù)的作用域
場景舉例:網(wǎng)絡(luò)請(qǐng)求請(qǐng)求結(jié)束后才調(diào)用的閉包站粟,因?yàn)榘l(fā)起請(qǐng)求后過了一段時(shí)間后這個(gè)閉包才執(zhí)行,并不一定是在函數(shù)作用域內(nèi)執(zhí)行
func requestData() -> () {
CCNetWorkTool.sharedInstance.request(method: .POST, URLString: API_TEST parameters: nil) {[weak self] (response, isSuccess) in
// 請(qǐng)求失敗
if isSuccess == false {
// 失敗處理
return
}
// 請(qǐng)求成功
// ......
}
非逃逸閉包
概念:一個(gè)接受閉包作為參數(shù)的函數(shù)曾雕, 閉包是在這個(gè)函數(shù)結(jié)束前內(nèi)被調(diào)用
注意:關(guān)于非逃逸的閉包有一個(gè)默認(rèn)規(guī)則:除了作為函數(shù)的即時(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)
}
}
}
閉包為什么要分為逃逸和非逃逸
其實(shí)是為了管理內(nèi)存顾犹。閉包會(huì)強(qiáng)引用它捕獲的所有對(duì)象倒庵,比如你在閉包中訪問了當(dāng)前控制器的屬性、函數(shù)炫刷,編譯器會(huì)要求你在閉包中顯示 self 的引用(其實(shí)當(dāng)前對(duì)象的屬性擎宝、函數(shù)隱形帶了一個(gè)self參數(shù),一旦調(diào)用他們浑玛,隱形中就使用了self)绍申,這樣閉包會(huì)持有當(dāng)前對(duì)象,容易導(dǎo)致循環(huán)引用顾彰。
非逃逸閉包不會(huì)產(chǎn)生循環(huán)引用极阅,它會(huì)在函數(shù)作用域內(nèi)釋放,編譯器可以保證在函數(shù)結(jié)束時(shí)閉包會(huì)釋放它捕獲的所有對(duì)象涨享;使用非逃逸閉包的另一個(gè)好處是編譯器可以應(yīng)用更多強(qiáng)有力的性能優(yōu)化筋搏,例如,當(dāng)明確了一個(gè)閉包的生命周期的話灰伟,就可以省去一些保留(retain)和釋放(release)的調(diào)用拆又;此外非逃逸閉包它的上下文的內(nèi)存可以保存在棧上而不是堆上。
綜上所述栏账,如果沒有特別需要帖族,開發(fā)中使用非逃逸閉包是有利于內(nèi)存優(yōu)化的,所以蘋果把閉包區(qū)分為兩種挡爵,特殊情況時(shí)再使用逃逸閉包
Swift3.0之后怎么允許一個(gè)閉包參數(shù)逃逸
Swift3.0之前閉包作為即時(shí)函數(shù)的參數(shù)默認(rèn)為逃逸的(@escaping)竖般,但是很多開發(fā)者在開發(fā)中總是忽略去判斷閉包是否為逃逸,這樣就都被當(dāng)做了逃逸閉包處理茶鹃,對(duì)閉包的內(nèi)存管理優(yōu)化不太友好涣雕。Swift3.0中做出調(diào)整艰亮,所有作為即時(shí)函數(shù)的參數(shù)的閉包默認(rèn)是非逃逸( @noescape)。如果開發(fā)者想使閉包具有逃逸性挣郭,需要用@escaping修飾閉包迄埃,@escaping 標(biāo)識(shí)符還有警示開發(fā)者的作用,警示開發(fā)者這是一個(gè)逃逸閉包兑障,注意循環(huán)引用問題侄非,比如下面的代碼
以下是幾個(gè)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ù)流译,但是當(dāng)閉包被包裹在其他類型(例如元組逞怨、枚舉的 case 以及可選型)中的時(shí)候,閉包仍舊是逃逸的福澡。由于在這種情況下閉包不再是即時(shí)的參數(shù)叠赦,它會(huì)自動(dòng)變成逃逸閉包。
參考資料:
http://www.reibang.com/p/120069d493f5
http://swift.gg/2016/11/15/optional-non-escaping-closures/