Grand Central Dispatch with Swift

When I’m building my LOL helper app, there’s one thing that I noticed: I had to deal with asynchronous networking requests properly so that the UI is only updated after multiple requests have been completed. I am using Alamofire to complete such requests, which does provide a callback function that is only called after getting a response. However, it is unable to do so with multiple requests. After searching online, I found the problem could be solved with Grand Central Dispatch in Swift. GCD Tutorial

The idea is that to put the requests into a “dispatch group” which can notify or be monitored. Tasks in the group are run on different threads. Before the start of a HTTP request, we need to enter the group, and after it finishes (in the callback function), we leave the group. Then we execute some methods only after the group is clean, which means no task is going on.

Here’s what we could do:

func downloadThings() {
  dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) { // 1
    var downloadGroup = dispatch_group_create() // 2
 
    for address in someURLStringGroup {
      dispatch_group_enter(downloadGroup) // 3
      download(address) { _ in
        dispatch_group_leave(downloadGroup) // 4
      }
    }
 
    dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER) // 5
    dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0)) { 
        // some cool stuff here
    }
  }
}

In this example, we first get on the user initiated queue, which has a relatively high priority. In 2, before anything happens, we need to create a group to “store” our tasks. Then before each task we enter the group, and after they complete we leave. dispatch_group_wait waits for the group to be empty and continue. After all that we get back on the main queue and update UI and stuff.

One problem with this implementation is that it blocks the main queue by waiting on the user interactive queue. The solution is thinking from the other direction: instead of waiting for the group, how about letting the group notify us? Luckily, Apple thought of that:

func downloadThings() {
  dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) { // 1
    var downloadGroup = dispatch_group_create() // 2
 
    for address in someURLStringGroup {
      dispatch_group_enter(downloadGroup) // 3
      download(address) { _ in
        dispatch_group_leave(downloadGroup) // 4
      }
    }
 
    dispatch_group_notify(downloadGroup, dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) 
    { // 2
      // some cool stuff here
    }
  }

Here we let the group notify us when everything is done. The UI is responsive while the data is being loaded. Everyone is happy.

The app is now able to retrieve current game information of a player, including who she or he is playing with. It is also able to get those players’ information with requests. However, Riot prevents “too many” requests in a second, which now seems that 10 requests in one second is still too many!! I’m working on a way to set a delay between requests, but since they are operating on different threads it is kind of difficult.

Also, I’ve been reading chapters and chapters of algorithms lately for my class. I should update what I learn urgently, maybe sometime tonight.

 

Leave a Reply

Your email address will not be published. Required fields are marked *