swift3.0中@escaping 和 @noescape 的含義
開始用swift語言是很容易的,而且它確實是一門很吸引人的語言。但是隨著你頻繁的使用,你會逐漸接觸到swift更加複雜的結構.
在swift2中,你可能遇到過@noescape
屬性,你有沒有花一點時間去理解它的意思?在swift3.0中,@noescape
已經被移除了。為什麼會這樣?為什麼swift3.0會引入@escaping
?回答這些問題將成為這篇文章的主題。
@noescape的含義
即使@noescape
在swift3.0中已經被廢棄了,但是對於理解它的含義依然有用。為什麼呢?很簡單,在swift3.0中@noescape
被用作一個預設值,讓我們開始進一步研究吧。
什麼是逃逸閉包(Escaping Closure)
首先你需要理解什麼是逃逸閉包。它的定義非常簡單而且易於理解。如果一個閉包被作為一個引數傳遞給一個函式,並且在函式return之後才被喚起執行,那麼這個閉包是逃逸閉包。並且這個閉包的引數是可以“逃出”這個函式體外的。理解了這個定義,這個逃逸閉包是不是就很好理解了呢?
If a closure is passed as an argument to a function and it is invoked after the function returns, the closure is escaping.
在swift2中,你可以標記一個函式引數@noescape
屬性,來告訴編譯器傳遞給這個函式的閉包不允許“逃逸”出函式體外,讓我們看一下下面的例子,注意這是用swift2寫的。
import HealthKit
class HealthKitManager: NSObject {
private let healthStore = HKHealthStore()
func requestAuthorization(completion: (Bool, NSError?) -> Void) {
var shareTypes = Set<HKSampleType>()
var readTypes = Set<HKSampleType>()
// Add Workout Type
shareTypes.insert(HKSampleType.workoutType())
readTypes.insert(HKSampleType.workoutType())
// Request Authorization
healthStore.requestAuthorizationToShareTypes(shareTypes, readTypes: readTypes,completion: completion)
}
}
可以看到requestAuthorization(_:)
方法接收了一個引數:一個閉包。在swift2中,閉包預設是可以“逃逸”到函式體外的,這就是為什麼上面的例子沒有報編譯錯誤。注意completion作為一個引數傳遞給HKHealthStore 的requestAuthorizationToShareTypes(_:readTypes:completion:)
方法,這個方法是非同步的,也就是說這個閉包會在requestAuthorization(_:)
函式return之後執行。換句話說,我們傳給requestAuthorization(_:)
的這個閉包逃逸了,它已經逃逸出了這個函式體。
如果我們給requestAuthorization(_:)
函式引數新增一個@noescape
屬性會變得非常有趣,下面是新增@noescape
屬性的例子
import HealthKit
class HealthKitManager: NSObject {
private let healthStore = HKHealthStore()
func requestAuthorization(@noescape completion: (Bool, NSError?) -> Void) {
var shareTypes = Set<HKSampleType>()
var readTypes = Set<HKSampleType>()
// Add Workout Type
shareTypes.insert(HKSampleType.workoutType())
readTypes.insert(HKSampleType.workoutType())
// Request Authorization
healthStore.requestAuthorizationToShareTypes(shareTypes, readTypes: readTypes,completion: completion)
}
}
我們直接告訴編譯器這個completion引數不能逃逸出函式體外,結果就是編譯報錯,並解釋了是什麼錯誤
swift3.0
下面給出上面例子的swift3版本。這個例子會展示“逃逸閉包”在swift3.0會有什麼變化。
import HealthKit
class HealthKitManager: NSObject {
private let healthStore = HKHealthStore()
func requestAuthorization(@noescape completion: (Bool, Error?) -> Void) {
var shareTypes = Set<HKSampleType>()
var readTypes = Set<HKSampleType>()
// Add Workout Type
shareTypes.insert(HKSampleType.workoutType())
readTypes.insert(HKSampleType.workoutType())
// Request Authorization
healthStore.requestAuthorization(toShare: shareTypes, read: readTypes, completion: completion)
}
}
編譯器立即告訴我們@noescape
在swift3中是預設的並且建議移除@noescape
。事實上,@noescape
在swift3中已經被廢棄,你以後都不會用到它了。
因為我們傳遞給requestAuthorization(completion:)
(注意這個方法簽名在swift3變得不一樣了)函式的閉包逃逸了,所以我們需要標記這個引數escaping.我們使用一個新的屬性@escaping
。這是SE-0103的直接結果,一個swift的進化理論提出預設建立不可逃逸的閉包。這是一個非常受歡迎的改變如果你問我的話。
import HealthKit
class HealthKitManager: NSObject {
private let healthStore = HKHealthStore()
func requestAuthorization(completion: @escaping (Bool, Error?) -> Void) {
var shareTypes = Set<HKSampleType>()
var readTypes = Set<HKSampleType>()
// Add Workout Type
shareTypes.insert(HKSampleType.workoutType())
readTypes.insert(HKSampleType.workoutType())
// Request Authorization
healthStore.requestAuthorization(toShare: shareTypes, read: readTypes, completion: completion)
}
}
你可能注意到了,這個@escaping
屬性寫在引數型別的前面而不是引數名稱的前面。這是swift3裡一個新的點。
@escaping的含義
這個提醒我們去理解@escaping
屬性的含義。因為在swift3中閉包預設是不可逃逸的,逃逸閉包需要像這樣被標記。@escaping
屬性讓我們可以那樣做。
我們通過@escaping
屬性標記閉包,編譯錯誤就消失了。
重點
這裡幾點關於建立預設不可逃逸閉包的好處: 最明顯的好處就是編譯器優化你的程式碼的效能和能力。如果編譯器知道這個閉包是不可逃逸的,它可以關注記憶體管理的關鍵細節。
而且你可以在不可逃逸閉包裡放心的使用self關鍵字,因為這個閉包總是在函式return之前執行,你不需要去使用一個弱引用去引用self.這對你而言是一個非常nice的功能。