closures使用非转义参数可能会让它逃脱

我有一个协议:

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)中编译和工作,但不再支持beta6。 你能指出我的根本原因吗?

这是由于function参数的默认行为的改变。 在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提议 。

由于@ noescape是默认的,有两个选项来修复错误:

1)正如@Hamish在他的回答中指出的那样,只要把完成标记为@escaping,如果你真的关心结果并且真的希望它逃跑的话(这可能就是在@ Lukasz的unit testing问题中的例子和asynchronous的可能性完成)

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

要么

2)在不关心结果的情况下,通过使完成可选地丢弃结果来保持默认的@noescape行为。 例如,当用户已经“走开”,并且调用视图控制器不必挂在内存中,只是因为有一些粗心的networking调用。 就像我来我这里寻找答案的时候一样,示例代码对我来说也不是很相关,所以@noescape并不是最好的select,事件虽然从第一眼看起来是唯一的。

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