2016-08-17 23 views
80

我有一個協議:封閉使用非參數逸出的可允許其逸出

enum DataFetchResult { 
    case success(data: Data) 
    case failure 
} 

protocol DataServiceType { 
    func fetchData(location: String, completion: (DataFetchResult) -> (Void)) 
    func cachedData(location: String) -> Data? 
} 

與示例實現:

/// An implementation of DataServiceType protocol returning predefined results using arbitrary queue for asynchronyous mechanisms. 
    /// Dedicated to be used in various tests (Unit Tests). 
    class DataMockService: DataServiceType { 

     var result  : DataFetchResult 
     var async  : Bool = true 
     var queue  : DispatchQueue = DispatchQueue.global(qos: .background) 
     var cachedData : Data? = nil 

     init(result : DataFetchResult) { 
      self.result = result 
     } 

     func cachedData(location: String) -> Data? { 
      switch self.result { 
      case .success(let data): 
       return data 
      default: 
       return nil 
      } 
     } 

     func fetchData(location: String, completion: (DataFetchResult) -> (Void)) { 

      // Returning result on arbitrary queue should be tested, 
      // so we can check if client can work with any (even worse) implementation: 

      if async == true { 
       queue.async { [weak self ] in 
        guard let weakSelf = self else { return } 

        // This line produces compiler error: 
        // "Closure use of non-escaping parameter 'completion' may allow it to escape" 
        completion(weakSelf.result) 
       } 
      } else { 
       completion(self.result) 
      } 
     } 
    } 

上述代碼編譯和Swift3工作(Xcode8- beta5),但不再適用於beta 6。你能指出我的根本原因嗎?

+5

這是爲什麼它這樣做的方式在斯威夫特非常[偉大的文章(https://cocoacasts.com/what-do-escaping-and-noescaping-mean-in-swift-3/) 3 – Honey

回答

130

這是由於功能參數的默認行爲的改變。在Swift 3之前(特別是Xcode 8 beta 6附帶的版本),他們將默認轉義 - 您必須將它們標記爲@noescape以防止它們被存儲或捕獲,因此保證它們不會被調用函數退出後。

但是,現在@noescape是默認值 - 您現在必須將函數參數標記爲@escaping,以告知編譯器它們可以被存儲或捕獲。

protocol DataServiceType { 
    func fetchData(location: String, completion: @escaping (DataFetchResult) -> Void) 
    func cachedData(location: String) -> Data? 
} 

func fetchData(location: String, completion: @escaping (DataFetchResult) -> Void) { 
    // ... 
} 

Swift Evolution proposal有關此更改的詳細信息。

+1

但是,你如何使用閉包,這樣它不允許逃脫? –

+6

@EnekoAlonso不完全確定你在問什麼 - 你可以直接在函數本身中調用非轉義函數參數,也可以在非轉義閉包中捕獲時調用它。在這種情況下,由於我們正在處理異步代碼,因此不能保證''async'函數參數(因此'completion函數)將在'fetchData'退出之前被調用 - 因此必須是'@ escaping' 。 – Hamish

+0

感到醜陋,我們必須指定@escaping作爲協議的方法簽名......是我們應該怎麼做?建議沒有說! :S – Sajjon

4

由於@noescape是默認的,有2個選項來修復錯誤:

1)@Hamish在他的回答中指出,剛剛慶祝完成,如果你不關心結果,真正@escaping想要逃離(這可能是與單元測試例子和異步完成的可能性@盧卡斯的問題的情況下)

func fetchData(location: String, completion: @escaping (DataFetchResult) -> Void) 

OR

2)通過使完成可選保持默認@noescape行爲丟棄結果altoget她在你不關心結果的情況下。例如,當用戶已經「走開」並且調用視圖控制器不必因存在粗心的網絡調用而在內存中掛起時。就像我在這裏遇到的情況是,當我來到這裏尋找答案時,示例代碼與我不相關,所以標記@noescape並不是最好的選擇,事件雖然它從第一眼看起來是唯一的。

func fetchData(location: String, completion: ((DataFetchResult) -> Void)?) { 
    ... 
    completion?(self.result) 
}