Swift 3.0 Error: Escaping closures can only capture inout parameters explicitly by value
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))
}
}
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.