-1

I recently had an issue with Alamofire (More generally with asynchronous calls)

I have two models, Listings and Users. Listings contains a user's email, and I would also like to get the user's first and last name (I understand I could solve this in the backend as well, however, I would like to see if there is a frontend solution as this problem comes up for something more complicated as well)

Currently I'm making a GET request to get all listings, and I'm looping through them, and making another GET request to get firstname, lastname. I need to wait to get the result of this get request, or at the minimum append it to my listings dictionary. Likewise, before I do anything else (Move on to the next screen of my app), I'd like to have all the listings be linked to a firstname, lastname. Because theres a loop, this specifically seems to cause some issues (ie if it was just two nested GET requests, it could be in a callback). Is there an easy way to get around this. I've attached psuedocode below:

GET Request to grab listings:
  for each listing:
    GET request to grab first_name, last_name

Once all listings have gotten first_name, last_name -> Load next page
MobileVet
  • 2,958
  • 2
  • 26
  • 42
Darthpepper
  • 31
  • 1
  • 3
  • Use [Promise](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Promise) and [Promise.all](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all) – Xotic750 Dec 30 '15 at 09:03
  • Hmm these seem Javascript dependent. Do promises exist for Swift? – Darthpepper Dec 30 '15 at 09:26
  • 1
    Of course they are Javascript dependant, you asked a Javascript question (with a Javascript tag). I have no idea what `Swift` is. – Xotic750 Dec 30 '15 at 09:30

3 Answers3

0

The answer for your question is called a dispatch group

Dispatch groups can be entered and left only to execute some code when no code is currently inside the dispatch group.

GET Request to grab listings{
  var downloadGroup = dispatch_group_create() 
//Create a dispatch group

  for each listing{
    dispatch_group_enter(downloadGroup)
//Enter the dispatch group
    GET request to grab first_name, last_name (Async){
      dispatch_group_leave(downloadGroup)
//Leave the dispatch group
    }
  }

  dispatch_group_notify(downloadGroup, dispatch_get_main_queue()) {
//Run this code when all GET requests are finished
  }
}

As demonstrated by this code.

Source and interesting reading material about dispatching: Grand Central Dispatch Tutorial for Swift by Ray Wenderlich

milo526
  • 5,012
  • 5
  • 41
  • 60
0

With Scala-style futures and promises you can do something like this:

let future: Future<[Listing]> = fetchListings().flatMap { listings in
    listings.traverse { listing in
        fetchUser(listing.userId).map { user in
            listing.userName = "\(user.firstName) \(user.lastName)"
            return listing
        }
    }
}

The result of the above expression is a future whose value is an array of listings.

Print the listing's user name, once the above expression is finished:

future.onSuccess { listings in
    listings.forEach {
        print($0.userName)
    }
}

Scala-style future and promise libraries: BrightFutures or FutureLib

Below a ready-to-use code example which you can paste into a playgrounds file to experiment with any of the above libraries (works in FutureLib, BrightFutures might require slight modifications).

import FutureLib
import Foundation
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true


class Listing  {
    let userId: Int
    init(userId: Int) {
        self.userId = userId
        userName = ""
    }

    var userName: String
}

struct User {
    let id: Int
    let firstName: String
    let lastName: String
    init (_ id: Int, firstName: String, lastName: String) {
        self.id = id
        self.firstName = firstName
        self.lastName = lastName
    }
}

func fetchListings() -> Future<[Listing]> {
    NSLog("start fetching listings...")
    return Promise.resolveAfter(1.0) {
        NSLog("finished fetching listings.")
        return (1...10).map { Listing(userId: $0) }
        }.future!
}

// Given a user ID, fetch a user:
func fetchUser(id: Int) -> Future<User> {
    NSLog("start fetching user[\(id)]...")
    return Promise.resolveAfter(1.0) {
        NSLog("finished fetching user[\(id)].")
        return User(id, firstName: "first\(id)", lastName: "last\(id)")
        }.future!
}


let future: Future<[Listing]> = fetchListings().flatMap { listings in
    listings.traverse { listing in
        fetchUser(listing.userId).map { user in
            listing.userName = "\(user.firstName) \(user.lastName)"
            return listing
        }
    }
}

future.onSuccess { listings in
    listings.forEach {
        print($0.userName)
    }
}
CouchDeveloper
  • 18,174
  • 3
  • 45
  • 67
-2

Here's a possible solution:

GET Request to grab listings:
  var n = the number of listings
  var i = 0 //the number of retrieved
  for each listing:
    GET request to grab first_name, last_name, callback: function(response){
      assign first/last name using response.
      i+=1
      if(i==n)
        load_next_page()
   }

So what this does is keep a counter of how many firstname/lastname records you've fetched. Make sure that you handle cases where a call to get the name fails.

Or, as suggested in a comment on the question, you could use promises. They make async code like this much nicer.

bigblind
  • 12,539
  • 14
  • 68
  • 123