关闭使用非转义参数可能允许它逃脱

时间:2016-08-17 07:46:04

标签: swift swift3 closures xcode8-beta6

我有一个协议:

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。你能指出我的根本原因吗?

2 个答案:

答案 0 :(得分:159)

这是由于函数类型参数的默认行为发生了变化。在Swift 3之前(特别是随Xcode 8 beta 6一起提供的版本),它们将默认为转义 - 您必须将它们标记为@noescape以防止它们被存储或捕获,这可以保证它们赢得& #39; t比函数调用的持续时间更长。

但是,现在@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 :(得分:12)

由于@noescape是默认值,因此有2个选项可以解决错误:

1)正如@Hamish在他的回答中指出的那样,如果你关心结果并且真的希望它能够逃脱,那就把完成标记为@escaping(这可能是@Lukasz对单元的问题)测试作为示例和异步完成的可能性)

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

OR

2)保留默认的@noescape行为,方法是在您不关心结果的情况下完成可选项完全丢弃结果。例如,当用户已经走开了#34;并且调用视图控制器不必因为有一些不小心的网络调用而挂在内存中。正如在我的情况下,当我来到这里寻找答案时,示例代码对我来说并不是非常重要,所以标记@noescape并不是最好的选择,尽管它听起来是第一眼中唯一的一个

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