Swift 3.0 Error: Escaping Closures Can Only Capture Inout Parameters Explicitly by Value

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

Escaping closure captures 'inout' parameter

When you enter your function, the cani value is duplicated,
when you exit the function, the duplicated value, potentially modified, is written back. That's what inout does.

Your function is asynchronous, so it exits immediately and cani is not modified.

When your timer closure is called, first you don't even know if the caller is still living, but you can be sure the cani copy that was passed does not exist anymore since the function has already exit a while ago.

The compiler detects this non-sense and tells you to do differently.

You may consider adding a cani property in the object that calls the timer, and changes its value in the timer closure or better, since it is asynchronous, call a closure do do what you want to do when the timer issues.

func duration(out:Int,h2:Int,rep:Int,cani:inout Bool){
var io = 0
var b = 0
var test: String

Timer.scheduledTimer(withTimeInterval: 1.0,
repeats: true,
completion: (String)->Void) {
timer in
io += 1
b += 1
if b <= out {
text = "Come in"
} else if b <= out + h2 {
text = "Hold on"
}

if io == (out + h2 ) * rep {
text = "End"
timer.invalidate()
completion()
}
}
}

It's better to put simple and understandable code in StackOverflow questions. And if possible, buildable. ;)

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

The error is described in detail in this answer.

The problem with your code is that the first closure

fileprivate func fetchUserAvatar(_ internalUrl : URL, externalUrl : URL,_ task : inout URLSessionTask?, completion : @escaping (_ image : UIImage?) -> ()) {
fetchImage(externalUrl, task: &task, completion: { image in // <-- HERE --
if image == nil {

is an escaping closure. So when the code

        if image == nil {
self.fetchImage(internalUrl, task: &task, completion: completion) // <-- HERE --
} else {

tries to write to the task variable, the original fetchUserAvatar call has already completed.

Note: I have written comments like this <-- HERE -- into the snippets, to clarify which line I am talking about. Also, please make sure to check out the answer that I linked above, because it will clarify everything..

The bad news is that you will have to refactor the code a bit to fix the error. You'll need to change the signatures of both fetchUserThumbnailAvatar, as well as fetchUserAvatar for that, and that will break callers; so the callers have to be changed as well. Therefore I cannot fix it for you, because the fix depends on code that I don't have.

Swift 5 : Escaping closure captures 'inout' parameter

As @Paulw11 suggested in comments,

Is BakeryData a struct? If so then simply make it a class. If you make
BakerData a class then the array contains reference types and you can
update the element's properties

I changed the struct to class and it did work.

Assigning value to inout parameters within closure in Swift 3

I suggest this refactor:

func fetchCurrentUser(callback: @escaping (User) -> ()) {
self.fetchUser(withId: AuthProvider.sharedInstance.currentUserId(), completionHandler: {
fetchedUser in
guard let newUser = fetchedUser else { return }
callback(newUser)
})
}

or if you want fetchCurrentUser to be synchronous you could use semaphores

Capturing values from synchronous and asynchronous closures

Because it's async, you can't do this in a single synchronous statement. Assuming you understand that, using Combine would probably be easiest:

import Combine

let fooResultsPublishers = [foo1, foo2].publisher
.flatMap { foo in
Future { promise in
foo.async { promise(.success($0)) }
}
}
.collect()

To actually get the value, you need to use .sink to act on it asynchronously:

fooResultsPublishers
.sink {
print($0) // [2,1]
}
// store it in long-living property, like
// var cancellables: Set = []
.store(in: &cancellables)

Without Combine, you'd need to use something like a DispatchGroup:

let group = DispatchGroup()
var values: [Int] = []
[foo1, foo2].forEach { foo in
group.enter()
foo.async { values.append($0); group.leave() }
}

group.notify(queue: .main) {
print(values) // [2,1]
}



Related Topics



Leave a reply



Submit