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

时间:2016-09-19 08:56:55

标签: ios swift swift3

我正在尝试将我的项目更新为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))
    }
}

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

3 个答案:

答案 0 :(得分:48)

专门为异步任务使用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所做的任何更改都不会写回调用者{&#39}。 s str - 只有在传递给完成函数时才能观察到更改。

在Swift 3中,inout闭包不再允许@escaping参数捕获,这消除了期望传递引用的混淆。相反,您必须通过复制来捕获参数,方法是将其添加到闭包capture list

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))
    }
}

答案 1 :(得分:4)

如果要修改转义闭包中通过引用传递的变量,可以使用KeyPath。这是一个示例:

class MyClass {
    var num = 1
    func asyncIncrement(_ keyPath: WritableKeyPath<MyClass, Int>) {
        DispatchQueue.main.async {
            // Use weak to avoid retain cycle
            [weak self] in
            self?[keyPath: keyPath] += 1
        }
    }
}

您可以看到完整的示例here

答案 2 :(得分:1)

如果您确定您的变量将始终可用,则只需使用真正的指针(与inout实际作用相同)

func foo(val: UnsafeMutablePointer<NSDictionary>, completion: @escaping (NSDictionary) -> Void) {
    val.pointee = NSDictionary()
    DispatchQueue.main.async {
        completion(val.pointee)
    }
}