一尘不染

Swift 3.0错误:转义的闭包只能按值显式捕获inout参数

swift

我正在尝试将项目更新为Swift 3.0,但遇到了一些困难。我收到下一个错误:“转义的闭包只能按值显式捕获inout参数”。

问题出在此函数内部:

fileprivate func collectAllAvailable(_ storage: inout [T], nextUrl: String, completion: @escaping CollectAllAvailableCompletion) {
    if let client = self.client {
        let _ : T? = client.collectionItems(nextUrl) {

            (resultCollection, error) -> Void in

            guard error == nil else {
                completion(nil, error)
                return
            }

            guard let resultCollection = resultCollection, let results = resultCollection.results else {
                completion(nil, NSError.unhandledError(ResultCollection.self))
                return
            }

            storage += results // Error: Escaping closures can only capture inout parameters explicitly by value

            if let nextUrlItr = resultCollection.links?.url(self.nextResourse) {

                self.collectAllAvailable(&storage, nextUrl: nextUrlItr, completion: completion) 
                // Error: Escaping closures can only capture inout parameters explicitly by value

            } else {
                completion(storage, nil) 
                // Error: Escaping closures can only capture inout parameters explicitly by value
            }
        }
    } else {
        completion(nil, NSError.unhandledError(ResultCollection.self))
    }
}

有人可以帮我解决这个问题吗?


阅读 169

收藏
2020-07-07

共1个答案

一尘不染

inout仅将参数用于异步任务是一种滥用inout–就像在调用函数时一样,传递给inout参数的调用者的值不会更改。

这是因为inout它不是传递引用,它只是参数的可变影子副本,该副本在函数退出时写回到调用方–并且由于异步函数立即退出,因此不会写回任何更改。

您可以在下面的Swift 2示例中看到这一点,其中inout转义的闭包允许捕获参数:

func foo(inout val: String, completion: (String) -> Void) {
    dispatch_async(dispatch_get_main_queue()) {
        val += "foo"
        completion(val)
    }
}

var str = "bar"
foo(&str) {
    print($0) // barfoo
    print(str) // bar
}
print(str) // bar

由于传递给该闭包dispatch_async的函数将逃逸该函数的生命周期,因此foo对它所做的任何更改val都不会写回到调用方的str-只能将更改传递给完成函数才能观察到。

在Swift 3中,闭包inout不再允许捕获参数@escaping,这消除了期望传递引用的困惑。相反,您必须通过 复制 参数来捕获参数,方法是

其添加到闭包的捕获列表中

func foo(val: inout String, completion: @escaping (String) -> Void) {
    DispatchQueue.main.async {[val] in // copies val
        var val = val // mutable copy of val
        val += "foo"
        completion(val)
    }
    // mutate val here, otherwise there's no point in it being inout
}

编辑:
自发布此答案以来,inout现在可以将参数编译为通过引用,可以通过查看所发出的SIL或IR来查看。但是由于没有这样的事实,您无法将其视为此类引用保证
任何 调用者的价值将在函数调用后仍然有效。)


但是,根据您的情况,根本不需要inout。您只需要将请求中的结果数组附加到传递给每个请求的当前结果数组中即可。

例如:

fileprivate func collectAllAvailable(_ storage: [T], nextUrl: String, completion: @escaping CollectAllAvailableCompletion) {
    if let client = self.client {
        let _ : T? = client.collectionItems(nextUrl) { (resultCollection, error) -> Void in

            guard error == nil else {
                completion(nil, error)
                return
            }

            guard let resultCollection = resultCollection, let results = resultCollection.results else {
                completion(nil, NSError.unhandledError(ResultCollection.self))
                return
            }

            **let storage = storage + results // copy storage, with results appended onto it.**

            if let nextUrlItr = resultCollection.links?.url(self.nextResourse) {
                self.collectAllAvailable(storage, nextUrl: nextUrlItr, completion: completion) 
            } else {
                completion(storage, nil) 
            }
        }
    } else {
        completion(nil, NSError.unhandledError(ResultCollection.self))
    }
}
2020-07-07