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
Why Extensions Cannot Add Stored Properties
Protocol with Associatedtype Protocol for Generic Functions
Transparency Issues with Repeated Stamping of Textures on an Mtkview
Cannot Use Mutating Member on Immutable Value of Type 'string'
How to Highlight a Custom UIbutton That Has No Text or Background
Hide Placeholder Programatically Using Swift 3X
How to Stop Dispatchgroup or Operationqueue Waiting
Swift 4, Simultaneous Access to Tuple Members as Inout
Uiscrollview Controller Not Scrolling Fully
Choppy Animation Swiftui Nested Views
Overlaying Image on Video Reduces Video Resolution
Is It a Good Way to Access Instance Variable with Self? If I Use a Lot
Swift Coredata: Unable to Section Tableview Using Sectionnamekeypath with Custom Function
How to Rotate Text in Nsview? (Mac Os, Cocoa, Swift)
Why I Can't Access My 3Rd Level Coredata Data in Swift