封闭使用非转义参数可能会使它转义


139

我有一个协议:

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
这是一篇非常出色的文章,说明了为什么它在Swift 3中如此操作
Honey,

1
我们必须这样做没有任何意义。没有其他语言需要它。
安德鲁·科斯特

Answers:


243

这是由于功能类型参数的默认行为发生了变化。在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提案


2
但是,如何使用闭包使其不允许逸出?
Eneko Alonso

6
@EnekoAlonso不确定您要问的是什么–您可以直接在函数本身中调用非转义的函数参数,也可以在捕获到非转义的闭包中时调用它。在这种情况下,由于我们正在处理异步代码,因此无法保证async函数参数(以及completion函数)将在fetchData退出前被调用-因此必须为@escaping
Hamish

感到丑陋,我们必须将@escaping指定为协议的方法签名...这是我们应该做的吗?提案不说!:S
Sajjon

1
@Sajjon当前,您确实需要将@escaping协议要求中的@escaping参数与该要求的实现中的参数进行匹配(对于非转义参数,反之亦然)。在Swift 2中相同@noescape
Hamish


30

由于@noescape是默认设置,因此有2个选项可修复该错误:

1)正如@Hamish在他的答案中指出的那样,如果您确实关心结果并真的希望它逃脱,则只需将完成标记为@转义即可(在@Lukasz的问题中,单元测试为例,并且可能是异步的)完成)

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

要么

2)保留默认的@noescape行为,方法是在不需要您关心结果的情况下,完全选择是否将结果完全丢弃。例如,当用户已经“走开”并且调用视图控制器不必因为存在一些粗心的网络调用而挂在内存中时。就像我来这里寻找答案的情况一样,示例代码与我并不太相关,因此标记@noescape并不是最好的选择,尽管乍一看听起来似乎是唯一的选择。

func fetchData(location: String, completion: ((DataFetchResult) -> Void)?) {
   ...
   completion?(self.result)
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.