So my question is this:
I'm trying to implement a extension to CLGeoCoder to be able to get a city name from a CLLocation but I'm facing a problem when I try to set the inout city name string inside the completionhandler of the reverseGeocodeLocation
Here is my code for my extension:
extension CLGeocoder {
func findCityName(location:CLLocation, inout cityName:String) -> Void{
self.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in
// Place details
var placeMark: CLPlacemark!
placeMark = placemarks?[0]
// Address dictionary
print(placeMark.addressDictionary)
// City
if let city = placeMark.addressDictionary!["City"] as? String {
cityName = city
}
})
}
}
The goal of this extension is to be able to set a string with a city name by passing it by reference to this function.
An example would be:
var cityName = ""
let location:CLLocation(latitude: 1, longitude: 1)
let geocoder: CLGeocoder()
geocoder.findCityName(location, &cityName)
///Later on in the process I would want to be able to use cityName for different purposes in my code
The problem I'm facing is that after setting the cityName reference in the completionHandler, the value of cityName stays empty outside of the completionhandler.
I'm new to swift so I've maybe missed something on pointers or how to use them but I had the understanding that inout would make it possible to change the value of a variable from inside a function or a closure/completionhandler.
Thank you in advance for any information you could give me on this problem I'm facing.
Martin
At the time your findCityName function exits, the cityName variable has not been changed. The completion handler of reverseGeocodeLocation will eventually update a variable that is a copy of the original cityName, not the variable that you originally passed to the function. Completion handlers don't execute right away: they will execute. If they would execute right away, functions could just return their results instead of using completion handlers, right?
Now, I agree with you, this is odd. But what is odd is not that Swift does not update your inout variable. What is odd is that Swift lets this program compile. Because it has no meaning. Follow me:
Imagine the following function:
func f() {
var cityName: String = ""
findCityName(location, &cityName)
}
When you call it, the cityName variable you passed in no longer exists when the completion handler of reverseGeocodeLocation eventually executes. The f() function has long exited, and the local variable cityName has vanished out of existence.
Got it? It's meaningless to even hope that the inout variable could be updated.
So. Now, you have to refactor your code. Maybe use a completion handler.
Related
I am a student trying to make an app in Xcode, and I have run into an issue to which I cannot figure out the solution. I want to access a string I have stored in another class and make it the label text of my ViewController.
In my ViewController, I call a different class, which then calls through an API and finds me a string detailing the weather in a certain place. This is what my call looks like from my ViewController class
func getWeather() {
manager = DataManager()
manager.fetchData(query: getQueryString();
}
And this is what the call looks like from the DataManager Class
func rainyWeatherPopulate(str: String){
LoadEvents.loadEvents(type: EventType.rain, str: str)
}
in the loadEvents function, I get a string detailing the type of weather that I want to put as text of a UILabel in my ViewController class, and I am not sure how to do so
Is there any way that I can pass an instance of my ViewController to my loadEvents static function or any other way in which I can set the text of a UILabel in my ViewController from the LoadEvents class?
There are many potential ways to solve this problem, but one is by passing a closure to be used upon completion of the call.
So, it might look like this in your use case:
class DataManager {
func fetchData(query: String, onComplete: #escaping (String) -> Void) {
LoadEvents.loadEvents(type: .rain, str: query, onComplete: onComplete)
}
}
enum EventType {
case rain
}
class LoadEvents {
static func loadEvents(type: EventType, str: String, onComplete: (String) -> Void) {
//load the data
//then, when it's done, call the completion
onComplete("returnedData")
}
}
func getWeather() {
let manager = DataManager()
let uiLabel = UILabel()
manager.fetchData(query: "queryString", onComplete: { result in
uiLabel.text = result
})
}
I had to mock out some stuff (like EventType) and I had no information about the types of events you're loading, so I just used a generic String for the return type, but the concept is here.
How it works:
In getWeather, there's a closure called onComplete that will get data back once everything has completed. result holds (obviously) the result -- in this case a string, and it sets uiLabel's text property to that value.
LoadEvents does whatever it needs to and then upon finishing, calls onComplete and sends the results back through the closure.
I'd say jnpdx's solution of passing a closure to your data manager is the best answer.
Another way you could do this is to add a delegate property to your data manager. The only thing you know about the delegate is that it conforms to a specific protocol. You'd then invoke methods on the delegate from the data manager that would tell the delegate about data that was available for display. The delegate would decide what to do with that data.
I mention this as an alternative method, but would suggest using closures as jnpdx recommends in their answer.
I am using Firebase to observe event and then setting an image inside completion handler
FirebaseRef.observeSingleEvent(of: .value, with: { (snapshot) in
if let _ = snapshot.value as? NSNull {
self.img = UIImage(named:"Some-image")!
} else {
self.img = UIImage(named: "some-other-image")!
}
})
However I am getting this error
Closure cannot implicitly capture a mutating self parameter
I am not sure what this error is about and searching for solutions hasn't helped
The short version
The type owning your call to FirebaseRef.observeSingleEvent(of:with:) is most likely a value type (a struct?), in which case a mutating context may not explicitly capture self in an #escaping closure.
The simple solution is to update your owning type to a reference once (class).
The longer version
The observeSingleEvent(of:with:) method of Firebase is declared as follows
func observeSingleEvent(of eventType: FIRDataEventType,
with block: #escaping (FIRDataSnapshot) -> Void)
The block closure is marked with the #escaping parameter attribute, which means it may escape the body of its function, and even the lifetime of self (in your context). Using this knowledge, we construct a more minimal example which we may analyze:
struct Foo {
private func bar(with block: #escaping () -> ()) { block() }
mutating func bax() {
bar { print(self) } // this closure may outlive 'self'
/* error: closure cannot implicitly capture a
mutating self parameter */
}
}
Now, the error message becomes more telling, and we turn to the following evolution proposal was implemented in Swift 3:
SE-0035: Limiting inout capture to #noescape contexts
Stating [emphasis mine]:
Capturing an inout parameter, including self in a mutating
method, becomes an error in an escapable closure literal, unless the
capture is made explicit (and thereby immutable).
Now, this is a key point. For a value type (e.g. struct), which I believe is also the case for the type that owns the call to observeSingleEvent(...) in your example, such an explicit capture is not possible, afaik (since we are working with a value type, and not a reference one).
The simplest solution to this issue would be making the type owning the observeSingleEvent(...) a reference type, e.g. a class, rather than a struct:
class Foo {
init() {}
private func bar(with block: #escaping () -> ()) { block() }
func bax() {
bar { print(self) }
}
}
Just beware that this will capture self by a strong reference; depending on your context (I haven't used Firebase myself, so I wouldn't know), you might want to explicitly capture self weakly, e.g.
FirebaseRef.observeSingleEvent(of: .value, with: { [weak self] (snapshot) in ...
Sync Solution
If you need to mutate a value type (struct) in a closure, that may only work synchronously, but not for async calls, if you write it like this:
struct Banana {
var isPeeled = false
mutating func peel() {
var result = self
SomeService.synchronousClosure { foo in
result.isPeeled = foo.peelingSuccess
}
self = result
}
}
You cannot otherwise capture a "mutating self" with value types except by providing a mutable (hence var) copy.
Why not Async?
The reason this does not work in async contexts is: you can still mutate result without compiler error, but you cannot assign the mutated result back to self. Still, there'll be no error, but self will never change because the method (peel()) exits before the closure is even dispatched.
To circumvent this, you may try to change your code to change the async call to synchronous execution by waiting for it to finish. While technically possible, this probably defeats the purpose of the async API you're interacting with, and you'd be better off changing your approach.
Changing struct to class is a technically sound option, but doesn't address the real problem. In our example, now being a class Banana, its property can be changed asynchronously who-knows-when. That will cause trouble because it's hard to understand. You're better off writing an API handler outside the model itself and upon finished execution fetch and change the model object. Without more context, it is hard to give a fitting example. (I assume this is model code because self.img is mutated in the OP's code.)
Adding "async anti-corruption" objects may help
I'm thinking about something among the lines of this:
a BananaNetworkRequestHandler executes requests asynchronously and then reports the resulting BananaPeelingResult back to a BananaStore
The BananaStore then takes the appropriate Banana from its inside by looking for peelingResult.bananaID
Having found an object with banana.bananaID == peelingResult.bananaID, it then sets banana.isPeeled = peelingResult.isPeeled,
finally replacing the original object with the mutated instance.
You see, from the quest to find a simple fix it can become quite involved easily, especially if the necessary changes include changing the architecture of the app.
If someone is stumbling upon this page (from search) and you are defining a protocol / protocol extension, then it might help if you declare your protocol as class bound. Like this:
protocol MyProtocol: class {
...
}
You can try this! I hope to help you.
struct Mutating {
var name = "Sen Wang"
mutating func changeName(com : #escaping () -> Void) {
var muating = self {
didSet {
print("didSet")
self = muating
}
}
execute {
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 15, execute: {
muating.name = "Wang Sen"
com()
})
}
}
func execute(with closure: #escaping () -> ()) { closure() }
}
var m = Mutating()
print(m.name) /// Sen Wang
m.changeName {
print(m.name) /// Wang Sen
}
Another solution is to explicitly capture self (since in my case, I was in a mutating function of a protocol extension so I couldn't easily specify that this was a reference type).
So instead of this:
functionWithClosure(completion: { _ in
self.property = newValue
})
I have this:
var closureSelf = self
functionWithClosure(completion: { _ in
closureSelf.property = newValue
})
Which seems to have silenced the warning.
Note this does not work for value types so if self is a value type you need to be using a reference type wrapper in order for this solution to work.
I want to write a function to reverse geocode a location and assign the resulting string into a variable. Following this post i've got something like this:
extension CLLocation {
func reverseGeocodeLocation(completion: (answer: String?) -> Void) {
CLGeocoder().reverseGeocodeLocation(self) {
if let error = $1 {
print("[ERROR] \(error.localizedDescription)")
return
}
if let a = $0?.last {
guard let streetName = a.thoroughfare,
let postal = a.postalCode,
let city = a.locality else { return }
completion(answer: "[\(streetName), \(postal) \(city)]")
}
}
}
}
For calling this function i've just got something like this:
location.reverseGeocodeLocation { answer in
print(answer)
}
But instead i want to assign the string value of answer to a variable and i don't know how to pass that data out of the closure. What is the best way to do something like this?
The problem is that it runs asynchronously, so you can't return the value. If you want to update some property or variable, the right place to do that is in the closure you provide to the method, for example:
var geocodeString: String?
location.reverseGeocodeLocation { answer in
geocodeString = answer
// and trigger whatever UI or model update you want here
}
// but not here
The entire purpose of the closure completion handler pattern is that is the preferred way to provide the data that was retrieved asynchronously.
Short answer: You can't. That's not how async programming works. The function reverseGeocodeLocation returns immediately, before the answer is available. At some point in the future the geocode result becomes available, and when that happens the code in your closure gets called. THAT is when you do something with your answer. You could write the closure to install the answer in a label, update a table view, or some other behavior. (I don't remember if the geocoding methods' closures get called on the main thread or a background thread. If they get called on a background thread then you would need to wrap your UI calls in dispatch_async(dispatch_get_main_queue()).)
So, I am trying to do a Alamofire request, then, I'd take the information I need from the JSON data and put it into a global variable, here's my code.
struct myVariables {
static var variableOne = ""
}
func function() {
Alamofire.request(.GET, "API URL").responseJSON { response in
if let rawJSON = response.result.value {
// Here I just take the JSON and put it into dictionaries and parse the data.
myVariables.variableOne = String("data")
}
}
}
Ok, so basically, I am trying to access variableOne's data from another Swift file. Let's say I made two Swift files and in one of those files I had a function that edited the value of global variable, in the other file, if I attempted to print that global variable, I'd see the edited value. But whenever I use Alamofire, when I try to edit a global variable, the other Swift file doesn't see the changed value. So if I tried to edit the global variable within the Alamofire request block of code, I don't see the change whenever I print the variable from another file.
If anyone knows a better way to phrase that, please do correct it.
I suspect the problem isn't that you're not seeing the value change, but rather an issue arising from the fact that you're dealing with an asynchronous method. For example, when you call function, it returns immediately, but your variableOne may not be updated immediately, but rather later. I bet you're checking it before this asynchronous response closure had a chance to be called.
You wouldn't have this problem if, rather than using this "global" (which is a bad idea, anyway), you instead adopted the completion handler pattern yourself.
func performRequest(completionHandler: #escaping (String?, Error?) -> Void) {
Alamofire.request("API URL").responseJSON { response in
switch response.result {
case .failure(let error):
completionHandler(nil, error)
case .success(let responseObject):
let dictionary = responseObject as? [String: Any]
let string = dictionary?["someKey"] as? String
completionHandler(string, nil)
}
}
}
An you'd call this like so:
performRequest() { string, error in
guard let string = string, error == nil else {
// handle error here
return
}
// use `string` here
}
// but not here, because the above closure runs asynchronously (i.e. later)
By using this completion handler pattern, we solve the "how do I know when the asynchronous method is done" problem. And by passing the necessary data back as a parameter of the closure, we can excise the use of globals, keeping the scope of our data as narrow as possible.
Clearly, you should change the parameter of the closure to match whatever is appropriate in your case. But hopefully this illustrates the basic idea.
See previous revision of this answer for Swift 2/Alamofire 3 answer.
I'm trying to pass a completionHandler as a parameter in a function (no problem here).
My problem is that I have multiple precise Types possible that I can recieve in my completionHandler function.
So I thought, "Let's use templates", and I tried.
This is the scheme I want to use:
FuncA(completionHandler as MyType?)
-> FuncB(..){completionHandler(Mappable?)}
-> FuncC(sender: T?){performSegueWithIdentifier("segue", sender)}
Problem:
Func A is printing me an error
Func B seems to be ok
Func C seems to be ok
Do you guy know how to do that, I'm not used to templates yet ??
Thanks for any help :)
I don't believe you can cast completionHandler like that in a method signature. You're going to need to do your typecasting inside the method body. e.g.
typealias handler = () -> Array<AnyObject>
funcA(handler)
func funcA<T>(completion: T?) -> funcB {
if let completion = completion as? handler {
let array = completion()
//do whatever you want here
}
}