Escaping Closures in Swift

Understanding escaping closures Swift

When you call DispatchQueue.main.async{} you are creating a closure (a block in Objective-C) that is sent to the given dispatch queue. Your function will then continue running after the async call and could end before the block has a chance to run on the DispatchQueue. The closure has "escaped"!

The code in the block will actually be run when the block reaches the front of its DispatchQueue

The async doesn't mean that the operation will take a long time, it means that this function (the one calling async) will not pause to wait on the block to complete.

(if you DID want to pause and wait for the operation to complete, you could call sync instead of async).

How to create a closure to use with @escaping

You're missing a return type on the closure (ie inside the last )):

typealias sessionDidReceiveMessageReplyHandlerHandler = (WCSession, [String : Any], @escaping ([String : Any]) -> Void) -> Void

Swift: Escaping closure How to pass values to previous ViewController after success

You could add the dictionary to your success closure like this:

func addToCart(vc:UIViewController, param:[String:String], completionHandler:@escaping (_ success:Bool, _ errorC : Error?, _ stock_flag : Bool?, result: [String:Any]?) -> Void)
{ ... }

Afterwards you can just use it in the closure.

CartViewModel().addToCart(vc: self, param:params ) { (isDone, error, stock_flag, result) in 
// Use result here
}

Escaping Closures in Swift

Consider this class:

class A {
var closure: (() -> Void)?
func someMethod(closure: @escaping () -> Void) {
self.closure = closure
}
}

someMethod assigns the closure passed in, to a property in the class.

Now here comes another class:

class B {
var number = 0
var a: A = A()
func anotherMethod() {
a.someMethod { self.number = 10 }
}
}

If I call anotherMethod, the closure { self.number = 10 } will be stored in the instance of A. Since self is captured in the closure, the instance of A will also hold a strong reference to it.

That's basically an example of an escaped closure!

You are probably wondering, "what? So where did the closure escaped from, and to?"

The closure escapes from the scope of the method, to the scope of the class. And it can be called later, even on another thread! This could cause problems if not handled properly.

By default, Swift doesn't allow closures to escape. You have to add @escaping to the closure type to tell the compiler "Please allow this closure to escape". If we remove @escaping:

class A {
var closure: (() -> Void)?
func someMethod(closure: () -> Void) {
}
}

and try to write self.closure = closure, it doesn't compile!

How exactly are structs captured in escaping closures?

While it might make sense for a struct to be copied, as your code demonstrates, it is not. That's a powerful tool. For example:

func makeCounter() -> () -> Int {
var n = 0
return {
n += 1 // This `n` is the same `n` from the outer scope
return n
}

// At this point, the scope is gone, but the `n` lives on in the closure.
}

let counter1 = makeCounter()
let counter2 = makeCounter()

print("Counter1: ", counter1(), counter1()) // Counter1: 1 2
print("Counter2: ", counter2(), counter2()) // Counter2: 1 2
print("Counter1: ", counter1(), counter1()) // Counter1: 3 4

If n were copied into the closure, this couldn't work. The whole point is the closure captures and can modify state outside itself. This is what separates a closure (which "closes over" the scope where it was created) and an anonymous function (which does not).

(The history of the term "close over" is kind of obscure. It refers to the idea that the lambda expression's free variables have been "closed," but IMO "bound" would be a much more obvious term, and is how we describe this everywhere else. But the term "closure" has been used for decades, so here we are.)

Note that it is possible to get copy semantics. You just have to ask for it:

func foo(){

var wtf = Wtf()

DispatchQueue.global().async { [wtf] in // Make a local `let` copy
var wtf = wtf // To modify it, we need to make a `var` copy
wtf.x = 5
}

Thread.sleep(forTimeInterval: 2)
// Prints 1 as you expected
print("x = \(wtf.x)")
}

In C++, lambdas have to be explicit about how to capture values, by binding or by copying. But in Swift, they chose to make binding the default.

As to why you're allowed to access wtf after it's been captured by the closure, that's just a lack of move semantics in Swift. There's no way in Swift today to express "this variable has been passed to something else and may no longer be accessed in this scope." That's a known limitation of the language, and a lot of work is going into fix it. See The Ownership Manifesto for more.

Why is Swift @escaping closure not working?

return customerList in searchCustomer happens synchronously when the data (that's obtained asynchronously from getJsonFromAPI) isn't yet available. So, you're assigning and empty [Customer] array to @State var customerList.

In any case, you can't directly assign an asynchronously-obtained value to a property.

Instead, change searchCustomer to also accept a completion handler, and use .onAppear to invoke it and assign the value from within a completion handler (just like you with getJsonFromAPI):

func searchCustomer(criteria: String, 
completionHandler: @escaping ([Customer]) -> Void) -> Void {

//API call full url
let fullURL = FTP_API_URL + criteria
var customerList = [Customer]()

if criteria == "" {
completionHandler(customerList)
}

getJsonFromAPI(url: fullURL, fetchCompletionHandler: {customers, error in

if let jsonCustomers = customers{
customerList = jsonCustomers.customers
completionHandler(customerList)
}
})
}
struct CustomerList: View {

@State var customerList = []

var body: some View {
List(self.customerList, id: \.id){ customer in
CellRow(customer: customer)
}
.onAppear() {
searchCustomer(criteria: "jose") { customers in
customerList = customers
}
}
}
}


Related Topics



Leave a reply



Submit