Add Value to Variable Inside Closure in Swift

Add value to variable inside closure in swift

The problem is that geocodeAddressString runs asynchronously (i.e. it takes so long they have written it to return immediately and call its closure later, when the request is done), so viewDidLoad is printing latLong well before this property was eventually set in the geocodeAddressString completion handler closure.

The solution is to adopt asynchronous programming pattern in your own code. For example, employ your own completion handler pattern:

override func viewDidLoad() {
super.viewDidLoad()

findCordiante(adress: "Cupertino, California, U.S.") { string in
// use `string` here ...

if let string = string {
self.latLong = string
print(string)
} else {
print("not found")
}
}

// ... but not here, because the above runs asynchronously and it has not yet been set
}

func findCordiante(adress:String, completionHandler: @escaping (String?) -> Void) {
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(adress) { placemarks, error in
if let location = placemarks?.first?.location, location.horizontalAccuracy >= 0 {
completionHandler("\(location.coordinate.latitude), \(location.coordinate.longitude)")
} else {
completionHandler(nil)
}
}
}

Can't assign a value to variable inside of closure Swift

Once it is a Async call - you cannot synchronously return the value from the function. You should accept a callback to the function that will accept the count. That callback function or closure will be passed the value asynchronously.

func getEventCount (callback: @escaping(Result<Int, Error>) -> Void) {
db.collection("Events").whereField("owner", isEqualTo: currentUser.email).getDocuments { (snapshot, error) in
if error != nil {
let result = Result.failure(error)
callback(result)
}else if let snapshot = snapshot {
let result = Result.success(snapshot.count)
callback(result)
} else {
let result = Result.failure(SomeCustomAppError)
callback(result)
}
}
}

Then you can call this function passing in a callback

self.getCount() { result in
switch result {
case .success(let count): /// use count
print(count)
// only here u can assign the count value to ur variable
case .error(let error): /// handle error
print(error.localizedDescription)
}
}

Note: In the above I've used the Result datatype from Swift standard library - https://developer.apple.com/documentation/swift/result so that both error
or result can be passed back

In Swift, what is the scope of local variable if it is used inside a closure?

This is why they're called closures.

Closures can capture and store references to any constants and
variables from the context in which they’re defined. This is known as
closing over those constants and variables. Swift handles all of the
memory management of capturing for you.

https://docs.swift.org/swift-book/LanguageGuide/Closures.html#ID103

Swift variable declared outside closure is updated inside closure but when accessed outside closure it returns the default value?

Please use tabs to make your code easier readable.

Anyway geocoder.geocodeAddressString(address) is a method with a callback and in this callback method you have the placemark. Your return will not wait for that callback since it will take time to calculate the coordinates hence it returns 0.0 which you set at the start.


Edit: longer version since you asked in a comment:

CLGeocoder()'s function geocodeAddressString has in fact 2 parameters: the adress and a socalled callback. A callback is simply a method called when the task (in this case calcualting the placemark) finishes. Swift lets you write the callback in "swifty" syntax but actually it looks like

geocoder.geocodeAddressString(address, callAfterFinishGeocoding)

func callAfterFinishGeocoding(_ placemark: Placemark) {
// do stuff with placemark
}

as you can see we pass the geocode function another function to be called when done. The parameters of callAfterFinishGeocoding are defined in geocodeAddressString. It will look similar to this:

callback: @escaping (_ placeMark: Placemark) -> Void

This would mean the callback should be a function accepting a placemark and returning Void. Jump to the definition of the method see what kind of function it wants as parameter.

also read more here: https://stackoverflow.com/a/46245943/13087977

Modify Global Variable Inside Closure (Swift 4)

All you need is a closure.

You cant have synchronous return statement to return the response of web service call which in itself is asynchronous in nature. You need closures for that.

You can modify your answer as below. Because you have not answered to my question in comment I have taken liberty to return the wether object rather than returning bool which does not make much sense.

func getCurrentWeather (completion : @escaping((CurrentWeather?) -> ()) ){
let jsonUrlString = "https://api.wunderground.com/api/KEY/conditions/q/"

guard let url = URL(string: jsonUrlString) else { return false }

URLSession.shared.dataTask(with: url) { (data, response, err) in
// check error/response

guard let data = data else { return }

do {
let weather = try JSONDecoder().decode(CurrentWeather.self, from: data)
CurrentWeather.currentWeather = weather
if let currentWeatherUnwrapped = currentWeather {
completion(CurrentWeather.currentWeather)
}
} catch let jsonErr {
print("Error serializing JSON: ", jsonErr)
completion(nil)
}

// cannot update currentWeather here, as weather is local to do block

}.resume()
}

Assuming currentWeather is a static variable in your CurrentWeather class you can update your global variable as well as return the actual data to caller as shown above

EDIT:

As pointed out by Duncan in comments below, the above code executes the completion block in background thread. All the UI operations must be done only on main thread. Hence its very much essential to switch the thread before updating the UI.

Two ways :

1- Make sure you execute the completion block on main thread.

DispatchQueue.main.async {
completion(CurrentWeather.currentWeather)
}

This will make sure that whoever uses your getCurrentWeather in future need not worry about switching thread because your method takes care of it. Useful if your completion block contains only the code to update UI. Lengthier logic in completion block with this approach will burden the main thread.

2 - Else In completion block that you pass as a parameter to getCurrentWeather whenever you update UI elements make sure you wrap those statements in

DispatchQueue.main.async {
//your code to update UI
}

EDIT 2:

As pointed out by Leo Dabus in comments below, I should have run completion block rather than guard let url = URL(string: jsonUrlString) else { return false } That was a copy paste error. I copied the OP's question and in a hurry din realize that there is a return statement.

Though having a error as a parameter is optional in this case and completely depends on how you designed your error handling model, I appreciate the idea suggested by Leo Dabus which is more general approach and hence updating my answer to have error as a parameter.

Now there are cases where we may need to send our custom error as well for example if guard let data = data else { return } returns false rather than simply calling return you may need to return a error of your own which says invalid input or something like that.

Hence I have taken a liberty to declare a custom errors of my own and you can as well use the model to deal with your error handling

enum CustomError : Error {
case invalidServerResponse
case invalidURL
}

func getCurrentWeather (completion : @escaping((CurrentWeather?,Error?) -> ()) ){
let jsonUrlString = "https://api.wunderground.com/api/KEY/conditions/q/"

guard let url = URL(string: jsonUrlString) else {
DispatchQueue.main.async {
completion(nil,CustomError.invalidURL)
}
return
}

URLSession.shared.dataTask(with: url) { (data, response, err) in
// check error/response

if err != nil {
DispatchQueue.main.async {
completion(nil,err)
}
return
}

guard let data = data else {
DispatchQueue.main.async {
completion(nil,CustomError.invalidServerResponse)
}
return
}

do {
let weather = try JSONDecoder().decode(CurrentWeather.self, from: data)
CurrentWeather.currentWeather = weather

if let currentWeatherUnwrapped = currentWeather {
DispatchQueue.main.async {
completion(CurrentWeather.currentWeather,nil)
}
}

} catch let jsonErr {
print("Error serializing JSON: ", jsonErr)
DispatchQueue.main.async {
completion(nil,jsonErr)
}
}

// cannot update currentWeather here, as weather is local to do block

}.resume()
}

How closure captures values in Swift?

From Swift Programming Guide - Closures

A closure can capture constants and variables from the surrounding context in which it’s defined. The closure can then refer to and modify the values of those constants and variables from within its body, even if the original scope that defined the constants and variables no longer exists.

A closure captures variables, not the contents of variables. When we are talking about local variables in a function (which are normally allocated on stack), it makes sure they are accessible even when the function exits and other local variables are deallocated, therefore we can do things like this:

func myFunc() {
var array: [Int] = []

DispatchQueue.main.async {
// executed when myFunc has already returned!
array.append(10)
}
}

Your example is similar. The closure captures the variable. This is a variable on module level, therefore its scope always exists. When you reassign its value, it will affect the value read inside the closure.

Or, in other words, the closure will be equivalent to:

var closure = {
print(CurrentModule.element?.name ?? "default value")
}

where CurrentModule is the name of your main module (which is usually the name of your project).

To prevent this behavior and capture the value of the variable instead, we can use closure capture list. Unfortunately, the official documentation does not properly explain what exactly is a capture list. Basically, using a capture list you declare variables local to the closure using values that are available when the closure is created.

For example:

var closure = { [capturedElement = element] in
print(capturedElement?.name ?? "default value")
}

This will create a new variable capturedElement inside the closure, with the current value of variable element.

Of course, usually we just write:

var closure = { [element] in
print(element?.name ?? "default value")
}

which is a shorthand for [element = element].

How do I set the return value of a closure function to a variable?

Try to set value of x variable inside of the closure:

loadData1(onCompletion: { (json) in
x = json
})

In your approach variable x initialized with a closure, that's why you received a warning.

Until closure will be executed, variable x have default value var x = [[String: String]]()
or remain unresolved if you did not provide default value along with declaration.

Assign Value from Closure to Variable Swift

change var userId: Int to var userId: Int!

The reason is that the closure is expecting a value to be in the variable when you "capture it". By using a forcefully unwrapped variable, the closure will be able to capture it as it holds a value of nil, which you can then assign a value to when the closure is called.

How to access a class's variable inside a closure?

If you need to use global variable/method in closure put self. before your variable/method

So you can replace this

saleOrder = saleEdited

with

self.saleOrder = saleEdited

but it looks that problem is also somewhere else. You just need to pass saleOrder from first to second ViewController where you edit it and then pass data back to first view controller

So, first delete unneccessary code from ViewControllerData:

let funcReturnFromEdit = { (saleEdited : SaleOrder) -> () in
saleOrder = saleEdited // I can't do this.
}

and to fix completion handler in ViewControllerEdit replace

var funcReturn = { (SaleOrder) -> Void in {} }

with

var funcReturn : ((SaleOrder) -> ())?

also edit your endEditions method because funcReturn is optional

func endEditions() {
funcReturn?(saleOrderToEdit)
}

Now just in your ViewControllerData fix prepare method to set what should be done when user call funcReturn from ViewControllerEdit

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segueEditOrder" {
let editVc = segue.destination as! ViewControllerEdit
if let saleOrderToPass = saleOrder {
editVc.saleOrderToEdit = saleOrderToPass
editVc.funcReturn = { saleEdited in
self.saleOrder = saleEdited //This code is executed when you call funcReturn?(saleOrderToEdit) from ViewControllerEdit
}
}
}
}


Related Topics



Leave a reply



Submit