Apple Reference, Articles, and Opinions

Handling asynchronous calls in Swift

In this article, I will discuss some of the issues when using closures that are asynchronous that a new developer may run into. Handling the asynchronous data as you receive it, gathering it, and sorting it to present to your user after you know it is ready.

Asynchronous code is code that is executed concurrently or in parallel with other threads and processes. A real world example of asynchronous execution would be something like food preparation on a kitchen cooking line. Suppose this kitchen is run only by humans and the angry British chef is the one coordinating the execution of the orders.

In this example, we might have a table of customers that order the following items with the associated times to cook and prepare.

If these orders were executed synchronously, the amount of time for everyone to have their food would simply be 5 + 3 + 8 + 18 = 34 minutes. Because the customers are polite, let’s assume nobody can begin eating until everyone has there food. A good chef would realize that the bottleneck is the well-done steak and since everything else is quicker, we can concurrently execute other orders while the well-done steak is cooking with a little communication from the cooks. This way, the pork tenderloin would begin preparation after the well-done steak had been cooking for 10 minutes, then 3 minutes later the rare steak could begin, and finally 2 minutes after that, the chicken plate.

The final product, all of these dishes would then be served to the customers at the same time and all be fresh. This example works whether you have one really good cook (single core processor), or 4 cooks (multi-core processor) each working on one dish. The end goal is to present the dishes (data) at the optimal time (fastest) and in the best way for the customer.

Asynchronous operations can be tricky to handle the first time and even more so when you need to deal with a group of asynchronous calls. Some programming languages have an ability to make some asynchronous call and you can await some response and then handle it. In Swift, we can expect this feature to be coming soon, but currently we must find some way of dealing with lengthy operations and safely presenting data to a user.

When using some frameworks, you may be given a function that makes an asynchronous call and returns a group of items, let’s call them dishes in this example and it can be an array of Strings. The function below will asynchronously fetch all of the dishes it has to offer and provide the array.

var d = [String]()

restaurant.getDishes { (dishes) in
	guard let dishesResponse = dishes else {
		return
	}
	d = dishesResponse
	print("The dishes are: \(d)”)// Prints ["pork", "steak-r", "chicken", "steak-wd"]
}
…
	

Completion handlers are a way of handling a single asynchronous call. The function asks for a “completion handler” to handle the data and request. In this case it’s the body of the code after the in keyword.

So far, we haven’t run into issues with code execution and have successfully retrieved our dishes array and stored it in a variable d

Continuing with the dishes example, suppose that we need to retrieve some information on the dishes, the time to cook a specific dish could vary based on the popularity of it at any given time of the day, whether the kitchen is busy due to the restaurant being very busy, etc. Therefore suppose that we have an asynchronous function that returns only one Double object called the prepTime for each request. However, we want to show the customer the dishes you offer sorted by the given prepTime of each dish just in case they are in a rush. Initially, you may try and solve this like so

var dishesWithTimes1 = [(dish: String, prepTime: Double)]()

func pairDishesWithTimesIncorrectly() {
	for dish in d {
		let r = RestaurantPrepTimes()
		r.dish = dish
		r.getPrepTime {(time) in
			guard let t = time else {
				return
			}
			dishesWithTimes1.append((dish: r.dish, prepTime: t))
		}
	}
	print("Done getting dishes: \(dishesWithTimes1)”) // Prints an empty array because code is executed immediately after the async function
}
…

The above could may seem correct at first glance, but the issue is that we're making multiple async calls in a for loop and we don't know how long or when we will be receiving that information back. therefore we risk presenting empty information to the customer!

How then could we handle it? I have found a pretty straightforward way of synchronizing asynchronous data calls is to use a DispatchGroup. A dispatch group allows us to essentially go off to the side and do our asynchronous work and come back to a point where we were in the code execution and execute it's completion handler. Until an ability to await on async calls comes in the next version of Swift, this is how one could handle the data retrieval correctly.

var dishesWithTimes2 = [(dish: String, prepTime: Double)]()

func pairDishesWithTimesCorrect() {
	let group = DispatchGroup()
	for dish in d {
		group.enter() // One of our async calls will start here
		let r = RestaurantPrepTimes()
		r.dish = dish
		r.getPrepTime {(time) in
			guard let t = time else {
				return
			}
			let tuple = (r.dish, t)
			dishesWithTimes2.append(tuple)
			group.leave() // This is where we will say we’re done per group entry
		}
	}
	
	group.notify(queue: .main) { // We can notify the main queue that we’re done with our work and you can now execute this code
		print("Actually Done now: \(dishesWithTimes2)")
	}
// Code here will still execute while our groups are working
}

Keep in mind that the work inside the group is still done asynchronously and therefore the order of completion is not guaranteed. In other words, the dishesWithItems2 array may not be in the same order as the d array. If you would like to sort it, you could call a sorting function in the body of the group.notify call like so

group.notify(queue: .main) { // We can notify the main queue that we’re done with our work and you can now execute this code
		print("Actually Done now: \(dishesWithTimes2)”)
		dishesWithTimes2 = dishesWithTimes2.sorted { (first, second) -> Bool in
			return first.prepTime < second.prepTime
		}
		print("Sorted dishes by time: \(dishesWithTimes2)")

Now that we have the sorted array, we could present the data to the customer safely knowing that we have all of the information and sorted without worrying about serving partial or incomplete information.

That’s it for this article on asynchronous handling of data, I hope you enjoyed and learned something! I have a swift playground uploaded on my Github if you would like to experiment.

With gratitude,
Jav Solo