Function not returning string - swift

So I'm getting back to programming and I'm having an issue. My function is not returning a value when I'm storing a Value in it. Could you guys have a look and point my out why that is happening?
func getLocation() -> NSString {
manager = OneShotLocationManager()
var tempLocation: NSString = "" // created an empty string for the var
manager!.fetchWithCompletion {location, error in
if let locatie = location {
tempLocation = String(locatie.coordinate.latitude) + "," + String(locatie.coordinate.longitude)
print(tempLocation) // It stores a value here but will not show it on the return
} else if let err = error {
tempLocation = err.localizedDescription
}
self.manager = nil
}
return tempLocation // It's not returning anything here..
}

The completion starts after you exit the function, so that is the problem i guess. You return "" and then do the stuff inside completion code

Your function is not returning the value because the fetchWithCompletion is being executed after the return statement as it is asynchronous. You can amend your function by using a completion handler to get access the tempLocation once it has been set:
func getLocation(completion: (location: String) -> ()) {
manager = OneShotLocationManager()
var tempLocation: NSString = "" // created an empty string for the var
manager!.fetchWithCompletion {location, error in
if let locatie = location {
tempLocation = String(locatie.coordinate.latitude) + "," + String(locatie.coordinate.longitude)
print(tempLocation) // It stores a value here but will not show it on the return
} else if let err = error {
tempLocation = err.localizedDescription
}
self.manager = nil
completion(location: tempLocation)
}
}
You can implement this function in the following way:
getLocation { (location) -> () in
print(location)
}

You need to call getLocation and do your stuff inside the fetchWithCompletion closure
func getLocation() {
manager = OneShotLocationManager()
manager!.fetchWithCompletion {location, error in
if let locatie = location {
tempLocation = String(locatie.coordinate.latitude) + "," + String(locatie.coordinate.longitude)
//DO THE THINGS YOU NEED TO DO WITH THE LOCATION HERE
} else if let err = error {
tempLocation = err.localizedDescription
}
self.manager = nil
}
}

The closure provided to fetchWithCompletion() is called asynchronously - upon the completion of the manager's location fetch. Your function starts the manager fetching and then returns before the manager's fetch is complete; thus the initial assignment to tempLocation is return as "".
As the fetch is asynchronous, perhaps your getLocation() should also be asynchronous?
func getLocationAsynch (handler: (String) -> Void) {
OneShotLocationManager()!.fetchWithCompletion { location, error in
if let locatie = location {
handler (String(locatie.coordinate.latitude) + "," + String(locatie.coordinate.longitude))
} else if let err = error {
handler (err.localizedDescription)
} else {
handler ("")
}
}
}
The above prevents your code from blocking; perhaps when the location becomes available you just display it for the user?
If, however, you want to block. Use a dispatch_semaphore as:
// assuming dispatch_semaphore works in Swift
func getLocation () -> String {
let sem = dispatch_semaphore_create (0);
var result = ""
OneShotLocationManager()!.fetchWithCompletion { location, error in
if let locatie = location {
result = String(locatie.coordinate.latitude) + "," + String(locatie.coordinate.longitude)
} else if let err = error {
result = err.localizedDescription
}
dispatch_semaphore_signal(sem)
}
dispatch_semaphore_wait(sem)
return result
}

Related

Function that returns an array

I'm trying to get an array to return from a function I call but the return optionArray in the below code give me a "Use of unresolved identifier optionArray.
public func getAdminSites(){
let getSiteData = UserDefaults.standard.object(forKey: "adminSites")
if getSiteData != nil
{
do {
guard let sitesData = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(getSiteData as! Data) as? [ModelSites] else {
fatalError("loadWidgetDataArray - Can't get Array")
}
var optionArray = ["All sites"]
for i in 0...sitesData.count-1 {
optionArray.append(sitesData[i].name)
}
} catch {
fatalError("loadWidgetDataArray - Can't encode data: \(error)")
}
}
return optionArray
}
There are two errors:
Function definition is missing a return type
OptionArray (stored variable) is declared in a control flow if scope and not accessible on a function body level
When you define a function, you can optionally define one or more
named, typed values that the function takes as input, known as
parameters. You can also optionally define a type of value that the
function will pass back as output when it is done, known as its return
type.
source
Fixed code:
public func getAdminSites() -> [String] {
let getSiteData = UserDefaults.standard.object(forKey: "adminSites")
var optionArray = [String]()
if getSiteData != nil
{
do {
guard let sitesData = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(getSiteData as! Data) as? [ModelSites] else {
fatalError("loadWidgetDataArray - Can't get Array")
}
optionArray = ["All sites"]
for i in 0...sitesData.count-1 {
optionArray.append(sitesData[i].name)
}
} catch {
fatalError("loadWidgetDataArray - Can't encode data: \(error)")
}
}
return optionArray
}

How to call CLGeocoder method synchronously

I have in ViewController code
var location = CLLocation()
DispatchQueue.global().sync {
let objLocationManager = clsLocationManager()
location = objLocationManager.findLocationByAddress(address: self.txtTo.stringValue)
}
lblToLatitude.stringValue = String(location.coordinate.latitude)
lblToLongitude.stringValue = String(location.coordinate.longitude)
calling findLocationByAddress method which is implemented in separate class clsLocationManager like this
func findLocationByAddress(address: String) -> CLLocation {
let geoCoder = CLGeocoder()
var location = CLLocation()
geoCoder.geocodeAddressString(address, completionHandler: {(places, error) in
guard error == nil else { return }
location = places![0].location!
})
return location
}
I try to ensure via DispatchQueue.global().sync that geo-coding is executed before passing coordinates to lblToLatitude and lblToLongitude labels but it doesn't work. Of course I could do geo-coding in ViewController code but I'm wondering how to keep it in separate class.
You need a completion
func findLocationByAddress(address: String,completion:#escaping((CLLocation?) -> ())) {
let geoCoder = CLGeocoder()
geoCoder.geocodeAddressString(address, completionHandler: {(places, error) in
guard error == nil else { completion(nil) ; return }
completion(places![0].location!)
})
}
to call
findLocationByAddress { (location) in
if let location = location {
lblToLatitude.stringValue = String(location.coordinate.latitude)
lblToLongitude.stringValue = String(location.coordinate.longitude)
}
}
Also no need for DispatchQueue.global().sync { as the geocoder runs asynchonously in a background thread

Re-execute while loop after completion handler has been returned

I have a while loop i wish to keep executing if the returned number is 0. The problem is currently have is that the loop never stops since it doesnt wait for the completion handler to return its results (which takes a couple seconds). How can i only re execute the while loop after the if statement has been completed?
while loop
var empty = true
override func viewDidLoad() {
repeat {
str = iFunctions.generateRandomStringWithLengthOf(4) as String
iFunctions.getServerData(str){(msg)
in
if (msg > 0){
self.empty = false
print("not empty")
}
else{
print("empty")
}
self.count = msg!
print(self.count)
}
} while(empty)
}
function
func getServerData(q: String, completionHandler: (Int?) -> ()) -> () {
let params = [
"term": q
]
Alamofire.request(.GET, "URL", parameters: params)
.responseJSON { response in
if response.result.error == nil {
let json = JSON(response.result.value!)
//add data to struct
completionHandler(json["results"].count)
}
}
}
You shouldn't do this with a loop. That's the whole point of Alamofire, so you don't have to deal with threads and loops. You're working with high level instructions: callbacks.
It's actually very easy to solve your problem:
var empty = true
override func viewDidLoad() {
self.tryGettingDataFromServer()
}
func tryGettingDataFromServer(){
str = iFunctions.generateRandomStringWithLengthOf(4) as String
iFunctions.getServerData(str){(msg) in
if (msg > 0){
self.empty = false
print("not empty")
}
else{
// The closure keeps a reference to self and calls
// the tryGettingDataFromServer again if "empty"
// It will happen infinitely until "not empty"
self.tryGettingDataFromServer()
}
self.count = msg!
print(self.count)
}
}

NSURLSession, Completion Block, Swift

Im working with NSURLSession. I have an array with restaurants and i'm requesting the dishes for every restaurant in the array to the api. The dataTask works,i'm just having a real hard time trying to call a method only when the all dataTasks are finished.
self.findAllDishesOfRestaurants(self.restaurantsNearMe) { (result) -> Void in
if result.count != 0 {
self.updateDataSourceAndReloadTableView(result, term: "protein")
} else {
print("not ready yet")
}
}
the self.updateDataSourceAndREloadTableView never gets called, regardless of my completion block. Here is my findAllDishesOfRestaurants function
func findAllDishesOfRestaurants(restaurants:NSArray, completion:(result: NSArray) -> Void) {
let allDishesArray:NSMutableArray = NSMutableArray()
for restaurant in restaurants as! [Resturant] {
let currentRestaurant:Resturant? = restaurant
if currentRestaurant == nil {
print("restaurant is nil")
} else {
self.getDishesByRestaurantName(restaurant, completion: { (result) -> Void in
if let dishesArray:NSArray = result {
restaurant.dishes = dishesArray
print(restaurant.dishes?.count)
allDishesArray.addObjectsFromArray(dishesArray as [AnyObject])
self.allDishes.addObjectsFromArray(dishesArray as [AnyObject])
print(self.allDishes.count)
}
else {
print("not dishes found")
}
// completion(result:allDishesArray)
})
completion(result:allDishesArray)
}
}
}
And here is my the function where i perform the dataTasks.
func getDishesByRestaurantName(restaurant:Resturant, completion:(result:NSArray) ->Void) {
var restaurantNameFormatted = String()
if let name = restaurant.name {
for charachter in name.characters {
var newString = String()
var sameCharacter:Character!
if charachter == " " {
newString = "%20"
restaurantNameFormatted = restaurantNameFormatted + newString
} else {
sameCharacter = charachter
restaurantNameFormatted.append(sameCharacter)
}
// print(restaurantNameFormatted)
}
}
var urlString:String!
//not to myself, when using string with format, we need to igone all the % marks arent ours to replace with a string, otherwise they will be expecting to be replaced by a value
urlString = String(format:"https://api.nutritionix.com/v1_1/search/%#?results=0%%3A20&cal_min=0&cal_max=50000&fields=*&appId=XXXXXXXXXappKey=XXXXXXXXXXXXXXXXXXXXXXXXXXXX",restaurantNameFormatted)
let URL = NSURL(string:urlString)
let restaurantDishesArray = NSMutableArray()
let session = NSURLSession.sharedSession()
let dataTask = session.dataTaskWithURL(URL!) { (data:NSData?, response:NSURLResponse?, error:NSError?) -> Void in
do {
let anyObjectFromResponse:AnyObject = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments)
if let asNSDictionary = anyObjectFromResponse as? NSDictionary {
let hitsArray = asNSDictionary.valueForKey("hits") as? [AnyObject]
for newDictionary in hitsArray! as! [NSDictionary]{
let fieldsDictionary = newDictionary.valueForKey("fields") as? NSDictionary
let newDish = Dish.init(dictionary:fieldsDictionary!, restaurant: restaurant)
restaurantDishesArray.addObject(newDish)
}
}
completion(result:restaurantDishesArray)
} catch let error as NSError {
print("failed to connec to api")
print(error.localizedDescription)
}
}
dataTask.resume()
}
Like i said before, I need to wait until the fun findAllDishesOfRestaurants is done. I tried writing my completion blocks but I'm not sure I'm doing it right. Any help is greatly appreciated. Thank
The problem is that you are calling the completion method in findAllDishesOfRestaurants before al tasks are complete. In fact, you are calling it once for each restaurant in the list, which is probably not what you want.
My recommendation would be for you to look into NSOperationQueue for two reasons:
It will let you limit the number of concurrent requests to the server, so your server does not get flooded with requests.
It will let you easily control when all operations are complete.
However, if you are looking for a quick fix, what you need is to use GCD groups dispatch_group_create, dispatch_group_enter, dispatch_group_leave, and dispatch_group_notify as follows.
func findAllDishesOfRestaurants(restaurants:NSArray, completion:(result: NSArray) -> Void) {
let group = dispatch_group_create() // Create GCD group
let allDishesArray:NSMutableArray = NSMutableArray()
for restaurant in restaurants as! [Resturant] {
let currentRestaurant:Resturant? = restaurant
if currentRestaurant == nil {
print("restaurant is nil")
} else {
dispatch_group_enter(group) // Enter group for this restaurant
self.getDishesByRestaurantName(restaurant, completion: { (result) -> Void in
if let dishesArray:NSArray = result {
restaurant.dishes = dishesArray
print(restaurant.dishes?.count)
allDishesArray.addObjectsFromArray(dishesArray as [AnyObject])
// self.allDishes.addObjectsFromArray(dishesArray as [AnyObject]) <-- do not do this
// print(self.allDishes.count)
}
else {
print("not dishes found")
}
// completion(result:allDishesArray) <-- No need for this, remove
dispatch_group_leave(group) // Leave group, marking this restaurant as complete
})
// completion(result:allDishesArray) <-- Do not call here either
}
}
// Wait for all groups to complete
dispatch_group_notify(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
completion(result:allDishesArray)
}
}

Swift How to wait for callback to finish before exiting function?

The problem I'm having on this request is the first function syncRequest always returns nil since the function exits before the (reply, error) comes back to fill out my return dictionary.
Is there a way for it to wait for the callback to return before returning out of my closure?
public typealias KKWatchSyncResponse = Dictionary<String, AnyObject>
func syncRequest() -> KKWatchSyncResponse? {
var syncResponseDict : KKWatchSyncResponse?
createRequest(KKWatchRequest.Sync, parameter: nil) { reply, error in
if reply == nil || error != nil {
return
} else {
syncResponseDict = KKWatchSyncResponse()
}
if let songInfo = NSKeyedUnarchiver.unarchiveObjectWithData(reply!["songInfo"] as NSData) as NSDictionary? {
syncResponseDict!["songInfo"] = songInfo
}
if let albumArtImage = NSKeyedUnarchiver.unarchiveObjectWithData(reply!["albumArt"] as NSData) as? UIImage {
syncResponseDict!["albumArtImage"] = albumArtImage
}
if let isPlaying = NSKeyedUnarchiver.unarchiveObjectWithData(reply!["isPlaying"] as NSData) as? Bool {
syncResponseDict!["isPlaying"] = isPlaying
}
}()
return syncResponseDict
}
func createRequest(request:KKWatchRequest, parameter: KKWatchAPIRequestParameter?, callback:KKWatchAPICallback) -> KKWatchAPIParentRequest {
var requestDict : Dictionary<String, AnyObject> = [KKBOXWatchAppRequestType : request.rawValue]
if parameter != nil {
requestDict += parameter! //Combine 2 dictionaries
}
return { WKInterfaceController.openParentApplication(requestDict){ reply, error in
callback(reply, error)
}
}
}
Your help us much appreciated!
Could you have syncRequest() take a closure that gets called with the results when ready? Change the definition to something like:
func syncRequest(callback:(KKWatchSyncResponse?)->Void) { ... }
Then at the end of your createRequest() call, you could call the callback on syncResponseDict, since now it's been populated with your data... callback(syncResponseDict).
EDIT: Here's the solution I had in mind.
func syncRequest(callback:(KKWatchSyncResponse?)->Void) {
createRequest(KKWatchRequest.Sync, parameter: nil) { reply, error in
if reply == nil || error != nil {
callback(nil)
} else {
var syncResponseDict : KKWatchSyncResponse? = KKWatchSyncResponse()
if let songInfo = NSKeyedUnarchiver.unarchiveObjectWithData(reply!["songInfo"] as NSData) as NSDictionary? {
syncResponseDict!["songInfo"] = songInfo
}
if let albumArtImage = NSKeyedUnarchiver.unarchiveObjectWithData(reply!["albumArt"] as NSData) as? UIImage {
syncResponseDict!["albumArtImage"] = albumArtImage
}
if let isPlaying = NSKeyedUnarchiver.unarchiveObjectWithData(reply!["isPlaying"] as NSData) as? Bool {
syncResponseDict!["isPlaying"] = isPlaying
}
callback(syncResponseDict)
}
}()
}
It is straight forward to implement a locking variable. This is most helpful for unit tests that do some async network loading.
func waitingFunction()
{
//set a lock during your async function
var locked = true
RunSome.asyncFunction() { () -> Void in
//after your sync function remove the lock
locked = false
})
//wait for the async method to complete before advancing
while(locked){wait()}
//move on from the lock
doMoreStuff()
}
func wait()
{
NSRunLoop.currentRunLoop().runMode(NSDefaultRunLoopMode, beforeDate: NSDate(timeIntervalSinceNow: 1))
}