I am having some issues and am just really lost so this is a last resort.
I have this function:
func fetchGoogleData(forLocation: CLLocation) {
//guard let location = currentLocation else { return }
googleClient.getGooglePlacesData(location: forLocation) { (response) in
self.listResults(places: response.results)
}
}
Which calls this functions:
func listResults(places: [Place]) {
dump(places)
}
However, dumping responses.result or places return a null value but looping through places returns all the correct values.
Is there a way to return response.results as the value without calling this function?
You can use completion block (Closures) to return your results. Let say your response.results is of type [Place]. Then your function should be like below
func fetchGoogleData(forLocation: CLLocation, completion: #escaping [Place]->Void) {
//guard let location = currentLocation else { return }
googleClient.getGooglePlacesData(location: forLocation) { (response) in
completion(response.results)
}
}
and call it as below:
self.fetchGoogleData(forLocation: currentPosition) { (places) in
//print(places)
}
Related
I have a function in swift to return a string from array, if the array has value return directly, if not I need load it from server and return the value from callback, I don't know how to do it, something like
func getValue(idx: Int)->string {
if arrayValue.count <= 0 {
callbackfunc() { arrayValue in
return arrayValue[idx]
}
} else {
return arrayValue[idx]
}
}
but it is impossible to return from callback, any help? thank you very much
Add a completion handler:
func getValue(idx: Int, completion: #escaping (String) -> Void) {
if arrayValue.count <= 0 {
callbackfunc() { arrayValue in
completion(arrayValue[idx])
}
} else {
completion(arrayValue[idx])
}
}
And call it:
getValue(idx: 2) { result in
print(result)
}
As you have found out you cannot directly return a value from a closure. Therefore you need to handle the returned data within the callback. This can be as complex as needs be, but for a simple example you could just pass the data back to an underlying method.
Depending on what else the app might be doing during the time the callback is running, you may need to be careful how you update this data to make it thread-safe. As a simple precaution it's probably worth performing the update on the main thread:
func getValue(idx: Int)->string {
if arrayValue.count <= 0 {
callbackfunc() { [weak self] arrayValue in
DispatchQueue.main.async {
self.processReturnedData(arrayValue[idx])
}
}
} else {
return arrayValue[idx]
}
}
func processReturnedData(_ idx: IDXType) {
//do something with the data
}
I am chaining some functions together and I can't figure out how to call a completion handler with a return value once all the functions are done running.
func getAirQuality(completion: (aqi: Int?) -> Void) {
callAPI()
}
private func callAPI() {
// ... get data
self.parseDataForAQI(data: data)
}
private func parseDataForAQI(data: Data) {
let aqi = aqi
// Send aqi up to completion handler in getAirQuality
}
So that when everything is said and done I can just do something like this:
getAirQuality(completion: { aqi -> Void in {
// Do something with aqi
})
My first assumption is that your first 3 functions are part of a class. If so, one approach is to save the completion handler as an instance variable.
class AirQualityProvider {
var aBlock: ((Int?) -> Void)?
func getAirQuality(completion: #escaping (Int?) -> Void) {
aBlock = completion
callAPI()
}
private func callAPI() {
let data = Data()
parseDataForAQI(data: data)
}
private func parseDataForAQI(data: Data) {
let aqi = 1
if let completion = aBlock {
completion(aqi)
}
}
}
Here's an example of a caller as written in a playground.
let aqp = AirQualityProvider()
aqp.getAirQuality { (value) in
if let value = value {
print("Value = \(value)")
}
}
I'm trying to return a value for a function from inside of a nested function. I understand why it doesn't work, but I just need to know a workaround for my type of problem.
In my code, I'm checking whether or not a person has already been created inside of my Firebase Database. So inside of the usersRef function, you'll see that if the value return from firebase is "nil" then the user hasn't been created and vice versa.
When I say : return userBool it's giving me this error:
Unexpected non-void return value in void function
Been looking for a clean workaround, but the only thing I can think of is having to create this function over and over again for each of the various times I want to check it. Obviously, I'd rather not do that, haha, so if you could help me out by giving some suggestions on how to fix this and have a clean solution, let me know! :)
func checkIfUserExists(userID : String) -> Bool {
var userBool = Bool()
var usersRef = FIRDatabase.database().reference()
usersRef.child("users").child(userID).observeSingleEvent(of: .value, with: {
snapshot in
if snapshot.value != nil {
userBool = true
return userBool
}
else {
userBool = false
return userBool
}
})
}
UPDATE :
Thanks to your suggestions, I added a completion, and it worked perfectly. :)
func checkIfUserExists(userID : String, completion: #escaping (_ success: Bool) -> ()){
var userBool = Bool()
var usersRef = FIRDatabase.database().reference()
usersRef.child("users").child(userID).observeSingleEvent(of: .value, with:{
snapshot in
if snapshot.value != nil {
print("userBool!", userBool)
userBool = true
}
else {
print("userBool2!", userBool)
userBool = false
}
completion(userBool)
})
}
There is no way to return from the outer function from within the inner one. The only way I can think of is by using a completion handler, that you can then call from within the nested function like so:
func checkIfUserExists(userID : String, completion: #escaping (_ userExists: Bool) -> Void)
The problem is you're in a closure, that doesn't expect a return type.. How are you call this function?
Some ideas:
* Pass a closure to this function, which then gets called, instead of trying to return.
Keep a mapping of the results somewhere, update that from the closure and always check against that mapping.
Also, it looks like instead of
if snapshot.value != nil {
userBool = true
return userBool
}
else {
userBool = false
return userBool
}
You could just do this:
guard snapshot = snapshot else { return }
return snapshot.value
The only way to make it return what you want is possible if the observeSingleEvent(...) function executes the closure synchronously to its call.
If it's true, you can try the following:
func checkIfUserExists(userID : String) -> Bool {
var userBool = false
var usersRef = FIRDatabase.database().reference()
usersRef.child("users").child(userID).observeSingleEvent(of: .value, with: {
userBool = $0.value != nil
})
return userBool
}
If the closure of observeSingleEvent(...) is called after a while, return a result of your method in a closure as Moritz suggested, not as a return value.
Another way is to create a delegate protocol and call it inside the closure
I've written a class, UserLocation (below), which uses CoreLocation to get the user's current place (string) & latitude and longitude (also a string "lat,long" for passing to APIs).
My code below works, but when I initialize the class, the rest of my code doesn't wait for the init to finish before moving on, so I miss the opportunity to assign the location-related values that I'm trying to retrieve.
Is this a sound approach (or should I think about some other more "MVC" organization), and if it is, how can I get my code to wait for the initialization (with location find & reverse geocoding) to finish before moving on. Is there a way to put the code below the initialization into some sort of #escaping closure that's specified in the class's init? I'm new to swift so thanks for your kind advice.
In ViewController.swift's viewDidAppear():
let userLocation = UserLocation() // initializes properly but code below doesn't wait.
locationsArray[0].name = userLocation.place
locationsArray[0].coordinates = userLocation.coordinates
And my UserLocation.swift class:
import Foundation
import CoreLocation
class UserLocation {
var place = ""
var coordinates = ""
let locationManager = CLLocationManager()
var currentLocation: CLLocation!
init() {
returnResults()
}
func returnResults () {
getUserLocation { placemark in
if placemark != nil {
self.place = (placemark?.name)!
self.coordinates = "\((placemark?.location?.coordinate.latitude)!),\((placemark?.location?.coordinate.longitude)!)"
} else {
print("Error retrieving placemark")
}
}
}
func getUserLocation(completion: #escaping (CLPlacemark?) -> ()) {
var placemark: CLPlacemark?
locationManager.requestWhenInUseAuthorization()
if (CLLocationManager.authorizationStatus() == CLAuthorizationStatus.authorizedWhenInUse ||
CLLocationManager.authorizationStatus() == CLAuthorizationStatus.authorizedAlways) {
currentLocation = locationManager.location
let geoCoder = CLGeocoder()
geoCoder.reverseGeocodeLocation(currentLocation) { (placemarks, error) -> Void in
if error != nil {
print("Error getting location: \(error)")
placemark = nil
} else {
placemark = placemarks?.first
}
completion(placemark)
}
}
}
}
extension CLPlacemark {
var cityState: String {
var result = ""
switch (self.locality, self.administrativeArea, self.country) {
case (.some, .some, .some("United States")):
result = "\(locality!), \(administrativeArea!)"
case (.some, _ , .some):
result = "\(locality!), \(country!)"
default:
result = name ?? "Location Unknown"
}
return result
}
}
This is not necessarily a Swift issue. Your problem is caused by the fact that returnResults executes the variables setup in an async manner because it calls an async function - getUserLocation, which is async as reverseGeocodeLocation is async (this is how CoreLocation works - you don't get the location synchronously, but in a callback).
You don't want to wait for returnResults to execute the callback, as this would mean blocking the main thread while CoreLocation initializes and tries to determine the location. Instead you should follow the async pattern by using a completion block that returnResults can use to signal the completion of the location retrieval.
An example for the above would be:
class UserLocation {
var place = ""
var coordinates = ""
let locationManager = CLLocationManager()
var currentLocation: CLLocation!
init() {
// don't call anymore from here, let the clients ask for the locations
}
// This was renamed from returnResults to a more meaningful name
// Using the Bool in the completion to signal the success/failure
// of the location retrieval
func updateLocations(withCompletion completion: #escaping (Bool) -> Void) {
getUserLocation { placemark in
if placemark != nil {
self.place = (placemark?.name)!
self.coordinates = "\((placemark?.location?.coordinate.latitude)!),\((placemark?.location?.coordinate.longitude)!)"
completion(true)
} else {
print("Error retrieving placemark")
completion(false)
}
}
}
...
You can then modify the called code to something like this:
let userLocation = UserLocation()
userLocation.updateLocations { success in
guard success else { return }
locationsArray[0].name = userLocation.place
locationsArray[0].coordinates = userLocation.coordinates
}
You don't block the main thread, and you execute the appropriate code when the location is available.
I have a function that does processing asynchronously:
func something(completion: [Something] -> Void) {
dispatch_async(queue) {
...
dispatch_async(dispatch_get_main_queue()) {
completion(something)
}
}
}
I thought it would be wise to use defer to guarantee that completion gets called every time, so I tried this:
func something(completion: [Something] -> Void) {
dispatch_async(queue) {
...
defer {
dispatch_async(dispatch_get_main_queue()) {
completion(something)
}
}
}
}
Working well. Then I tried to use a guard statement within the asynchronous dispatch that always failed, to see if defer will activate. It didn't:
func something(completion: [Something] -> Void) {
dispatch_async(queue) {
...
guard let shouldFail = ... else { return }
defer {
dispatch_async(dispatch_get_main_queue()) {
completion(something)
}
}
}
}
defer would not be called. Why?
Because you are using defer after returning. The compiler doesn't know that you specified defer instructions (because it returned already and didn't see any defer instructions in that point, so the next lines are not fired up). If you'd move defer {} before the guard, then it will be called.
guard will return before even getting to the defer. Try doing it the other way around:
func something(completion: [Something] -> Void) {
dispatch_async(queue) {
...
defer {
dispatch_async(dispatch_get_main_queue()) {
completion(something)
}
}
guard let shouldFail = ... else { return }
}
}