Swift 3.0 Error: Escaping closures can only capture inout parameters explicitly by value

后端 未结 3 1102
遇见更好的自我
遇见更好的自我 2020-11-29 22:54

I\'m trying to update my project to Swift 3.0 but I have some difficulties. I\'m getting next error: \"Escaping closures can only capture inout parameters explicitly by valu

相关标签:
3条回答
  • 2020-11-29 23:35

    Using an inout parameter exclusively for an asynchronous task is an abuse of inout – as when calling the function, the caller's value that is passed into the inout parameter will not be changed.

    This is because inout isn't a pass-by-reference, it's just a mutable shadow copy of the parameter that's written back to the caller when the function exits – and because an asynchronous function exits immediately, no changes will be written back.

    You can see this in the following Swift 2 example, where an inout parameter is allowed to be captured by an escaping closure:

    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
    

    Because the closure that is passed to dispatch_async escapes the lifetime of the function foo, any changes it makes to val aren't written back to the caller's str – the change is only observable from being passed into the completion function.

    In Swift 3, inout parameters are no longer allowed to be captured by @escaping closures, which eliminates the confusion of expecting a pass-by-reference. Instead you have to capture the parameter by copying it, by adding it to the closure's 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
    }
    

    (Edit: Since posting this answer, inout parameters can now be compiled as a pass-by-reference, which can be seen by looking at the SIL or IR emitted. However you are unable to treat them as such due to the fact that there's no guarantee whatsoever that the caller's value will remain valid after the function call.)


    However, in your case there's simply no need for an inout. You just need to append the resultant array from your request to the current array of results that you pass to each request.

    For example:

    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))
        }
    }
    0 讨论(0)
  • 2020-11-29 23:38

    If you are sure that your variable will be available the whole time just use a true Pointer (same what inout actually does)

    func foo(val: UnsafeMutablePointer<NSDictionary>, completion: @escaping (NSDictionary) -> Void) {
        val.pointee = NSDictionary()
        DispatchQueue.main.async {
            completion(val.pointee)
        }
    }
    
    0 讨论(0)
  • 2020-11-29 23:46

    If you want to modify a variable passed by reference in an escaping closure, you can use KeyPath. Here is an example:

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

    You can see the full example here.

    0 讨论(0)
提交回复
热议问题