1. 程式人生 > >swift3.0中@escaping 和 @noescape 的含義

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作為一個引數傳遞給HKHealthStorerequestAuthorizationToShareTypes(_: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的功能。