Re-execute while loop after completion handler has been returned - swift

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)
}
}

Related

How can I combine the result of these 2 async methods?

I have 2 methods I call. I need to produce a model that contains the result of both and call another method.
I wanted to avoid placing 1 method inside another as this could expand out to 3 or 4 additional calls.
Essentially once I have the results for setUserFollowedState and loadFollowersForTopic I want to send both values to another function.
Coming from a JS land I would use async/await but this does not exist in Swift.
func setUserFollowedState() {
following.load(for: userID, then: { [weak self, topic] result in
guard self != nil else { return }
let isFollowed = (try? result.get().contains(topic)) ?? false
// do something with isFollowed?
})
}
func loadFollowersForTopic() {
followers.load(topic, then: { [weak self, topic] result in
guard self != nil else { return }
let count = (try? result.get().first(where: { $0.tag == topic })?.followers) ?? 0
// do something with count?
})
}
You can store both async call results as optional properties. When your callbacks happen, set these properties then check that both properties have been set. If they've been set, you know both async calls have returned.
private var isFollowed: Bool?
private var count: Int?
func setUserFollowedState() {
following.load(for: userID, then: { [weak self, topic] result in
guard let self = self else { return }
let isFollowed = (try? result.get().contains(topic)) ?? false
self.isFollowed = isFollowed
performPostAsyncCallFunctionality()
})
}
func loadFollowersForTopic() {
followers.load(topic, then: { [weak self, topic] result in
guard let self = self else { return }
let count = (try? result.get().first(where: { $0.tag == topic })?.followers) ?? 0
self.count = count
performPostAsyncCallFunctionality()
})
}
private func performPostAsyncCallFunctionality() {
// Check that both values have been set.
guard let isFollowed = isFollowed, let count = count else { return }
// Both calls have returned, do what you need to do.
}
The good thing about this approach is that you can easily add more async calls using the pattern. However, if you need to make that many async network calls at once, I would recommend you think about rewriting your server-side logic so you only need one network call for this functionality.
Another approach (which I believe is a bit cleaner) would be to use a DispatchGroup to combine the result of the mentioned methods.
You would modify your original methods to take a completion handler and then combine the two results where you actually need the data.
See example below.
func setUserFollowedState(completion: #escaping ((Bool) -> Void)) {
following.load(for: userID, then: { [weak self, topic] result in
guard self != nil else { return }
let isFollowed = (try? result.get().contains(topic)) ?? false
// Call completion with isFollowed flag
completion(isFollowed)
})
}
func loadFollowersForTopic(completion: #escaping ((Int) -> Void)) {
followers.load(topic, then: { [weak self, topic] result in
guard self != nil else { return }
let count = (try? result.get().first(where: { $0.tag == topic })?.followers) ?? 0
// Call completion with follower count
completion(count)
})
}
func loadFollowedAndCount() {
let group = DispatchGroup()
var isFollowed: Bool?
// Enter group before triggering data fetch
group.enter()
setUserFollowedState { followed in
// Store the fetched followed flag
isFollowed = followed
// Leave group only after storing the data
group.leave()
}
var followCount: Int?
// Enter group before triggering data fetch
group.enter()
loadFollowersForTopic { count in
// Store the fetched follow count
followCount = count
// Leave group only after storing the data
group.leave()
}
// Wait for both methods to finish - enter/leave state comes back to 0
group.notify(queue: .main) {
// This is just a matter of preference - using optionals so we can avoid using default values
if let isFollowed = isFollowed, let followCount = followCount {
// Combined results of both methods
print("Is followed: \(isFollowed) by: \(followCount).")
}
}
}
Edit: always make sure that a group.enter() is followed by a group.leave().

pausing main thread will async function runs swift

I call the following function in a while loop but the asynchronous code doesn't run even with the dispatch_group being used. Does anyone know what I may be doing incorrectly? *I am trying to essentially get the while loop to wait for the asynchronous code to be executed before continuing.
override func viewDidAppear(animated: Bool) {
if(ended != true) {
if(count2 < 2) {
print("current count is < 2")
} else {
var playRef = ref.child("LiveGames").child("05-25-17").child("RedSox")
asyncCall(playRef, ended: ended, count: count2)
}
print("successful delay")
count2++
//playIsntThere()
}//this is where the else loop ends
count2++
}
func asyncCall(ref:FIRDatabaseReference, ended:Bool, count:Int) {
var count2 = count
let myGroup = dispatch_group_create()
dispatch_group_enter(myGroup)
ref.child(String(count2-2)).observeSingleEventOfType(.Value, withBlock: { (snapshot2) in
var hitterRef = snapshot2.childSnapshotForPath("/Hitter")
var pitcherRef = snapshot2.childSnapshotForPath("/Pitcher")
pitcherEra.text = String(pitcherRef.childSnapshotForPath("/ERA").value!)
})
dispatch_group_leave(myGroup)
dispatch_group_notify(myGroup, dispatch_get_main_queue()) {
print("code completed")
}
print("successful delay")
count2++
}

Escaping completion handler never happens (Google Places API)

I'm building an app that uses the Google Places API. I currently have a button that, when tapped, gets the address of the current GPS location. The code is in my view controller:
var placesClient: GMSPlacesClient?
#IBAction func buttonTapped(_ sender: AnyObject) {
placesClient?.currentPlace(callback: { (placeLikelihoods, error) -> Void in
guard error == nil else {
print("Current Place error: \(error!.localizedDescription)")
return
}
if let placeLikelihoods = placeLikelihoods {
let place = placeLikelihoods.likelihoods.first?.place
self.addressLabel.text = place?.formattedAddress!.components(separatedBy: ", ").joined(separator: "\n")
}
})
print("Out of the brackets...")
}
When done this way, the function completes and "Out of the brackets..." is printed.
However, when I try moving this code out of the view controller and into a custom class and call it from the view controller, like below, everything within the "placesClient?.currentPlace(callback" block runs (and retrieves the correct address), but "Out of the brackets..." never gets printed and it never returns the value:
class LocationAPIService {
var placesClient: GMSPlacesClient? = GMSPlacesClient.shared()
func getCurrentLocation() -> GMSPlace? {
var thisPlace: GMSPlace?
placesClient?.currentPlace(callback: { (placeLikelihoods, error) -> Void in
guard error == nil else {
print("Current Place error: \(error!.localizedDescription)")
return
}
if let placeLikelihoods = placeLikelihoods {
let place = placeLikelihoods.likelihoods.first?.place
thisPlace = place
}
})
print("Out of the brackets...")
return thisPlace
}
}
Anybody know why this might be happening?
Fixed it. Here is the code I used for the method in my LocationAPIService class:
func setCurrentLocationPlace(completion: #escaping (_ result: Bool)->()) {
var placeFindComplete: Bool = false
placesClient?.currentPlace(callback: { (placeLikelihoods, error) -> Void in
guard error == nil else {
print("Current Place error: \(error!.localizedDescription)")
completion(true)
return
}
if let placeLikelihoods = placeLikelihoods {
let place = placeLikelihoods.likelihoods.first?.place
self.currentPlace = place
placeFindComplete = true
completion(true)
}
})
if (placeFindComplete == false) {
completion(false)
}
}
And here is the way I called it from the view controller:
locationAPIService?.setCurrentLocationPlace() { (locationFound) -> () in
if (locationFound == true) {
//Run code here that triggers once setCurrentLocationPlace() complete.
}

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))
}