Synchronizing asynchronous networking calls using iOS 15 Group task API.
By: James Rochabrun
Tags: Asyncawait, Swift Programming, Swiftui, Concurrency, Swift

Today I want to show how I used the new group tasks iOS 15 API in a sample project, to execute a group of networking calls to the Itunes API.

The project is pretty simple and looks like this…

Itunes feed resources

It shows a feed of different categories that you can find from the Itunes API. In order to get each category, we need to perform an independent API call, then we need to somehow mix the results of each category so we can load them in the same feed.

First, let's see how we can achieve this using a DispatchGroup, and then we will see how to achieve the same result but using a group task.

You can find the full implementation of this tutorial here if you want to skip the rest of the post :)

On that repo you will find…

ItunesCategoryIdentifier.swift

ItunesCategoryIdentifier represents the categories that we will display in our app.

The categories. The title that we will display for each section. A static property that represents the number of items we will retrieve from each API call. (For simplicity we will ask for 4 items) The media type to fetch, the iTunes API endpoint takes as a parameter a media type, we created an enum to easily access a media type, to see its implementation go to FeedGenerator.swift inside the repo.

As you see ItunesCategoryIdentifier conforms to CaseIterable which will allow us to loop through all the cases to fetch each category, it also has an Int raw value, which will help us to display the results in the order that we want.

Now we need to explore the ItunesRemote class….

ItunesRemote conforms to ObservableObject to publish results into our SwiftUI views. (We will be using SwiftUI here but we can use this object in an UIKit app also). ItunesCategorySection is a struct that represents a section for each category, it has sectionID property that represents the section identifier and a cellIDs property that represents the items inside that section. It conforms to IdentifiableHashable (a protocol composition from Hashable and Identifiable that helps avoiding boilerplate code, check it out if you want it's inside the IdentifiableHashable.swift file in the repo 🤓), this conformance is needed so this object can be used inside a SwiftUI view. ItunesClient is a client that uses a generic networking layer to fetch items from a server, if you are interested in learning more about implementing networking layers using combine you can go here, or if you want to learn how to use async/await you can go here. The publisher and cancellable objects needed to publish the results.

Ok, now we are going to create a function inside ItunesRemote to perform our group of tasks using a Dispatchgroup…

DispatchGroup implementation

The instance of a dispatch group. An array of sections, we will store our sections here. Loop through all the category identifiers array passed as argument. We call the enter method from a dispatch group to introduce the request to the group. We execute a networking call for each category. We leave the group when the call is done. Using Combine we receive the feed of a certain category, we later convert them into view models and then create the section object and append it into the sections array. Using a notify closure, that will be executed after all the networking tasks in the loop are finished, we set the Published property itunesSection with the array of sections sorted by the sectionID rawValue.

Sorting is needed because when we execute different asynchronous tasks inside a dispatch group, we can not warranty the order where the response will be available for each request. One way to solve this is to use an enum with an integer raw value as a section identifier, by doing this we can sort the sections and easily modify the order as we want. This is one way to do it and I am sure there are so many others, if you want to share your approach please share it in a comment!. 😉

Before moving into implementing this using iOS 15 group tasks, let's use this in our SwiftUI view…

@available(iOS 15, *) struct ContentView: View { @StateObject private var itunesRemote = ItunesRemote()

let columns = [GridItem(.flexible()), GridItem(.flexible())] var body: some View {

ScrollView {

LazyVGrid(columns: columns, spacing: 0) {

ForEach(itunesRemote.itunesSections, id: \.self) { section in

Section(header: Text("\(section.sectionID.title)").font(.title).bold().padding(15)) {

ForEach(section.cellIDs) {

FeedItemView(artwork: $0)

}

}

}

}

}

.task {

itunesRemote.dispatchGroups(from:ItunesCategoryIdentifier.allCases)

}

}

}

As you can see here we are displaying our categories in a LazyGrid with different sections, and the items displayed comes from the itunesSections array published property.

Ok, now let's create a new function that uses group tasks instead…

Sadly, marking this function as available for iOS 15 is needed because these new API’s are not available in previous versions. 😭 Wrapping the logic inside an async block will “bridge” an asynchronous call within a synchronous context, if you want to know what this means you can go here. An array where we will store our section categories. A group task starts a new scope in which a dynamic number of throwing tasks can be spawned. It takes as an argument the type of the result of a certain child task, and a closure with the value for a group task, in this example “categorySection” is a throwing task group that looks like this…

ThrowingTaskGroup<ItunesRemote.ItunesCategorySection, Error>

5. We loop through the array of category identifiers to execute a child task for each category.

6. Inside an async call, the group executes a child task which in this example is a throwable async call that performs a network request. Later we use the feed results from that request to create view models and then create our ItunesCategorySection objects.

7. This is an AsyncSequence implementation, it is basically how we can execute asynchronous functions in a sequence, they suspend each element by using the await keyword and resume when the underlying iterator produces a value or throws. For more about AsyncSequence please check this WWDC 2021 video.

8. Appending the iTunes Category section in the sections array.

One cool thing to know is that by using the AsyncSequence design we can also avoid race conditions when writing a mutable value. For more about this go into Explore structured concurrency in Swift from WWDC 2021.

9. Same as with the dispatch group implementation, we set the Published property itunesSection with the array of sections sorted by the sectionID rawValue.

Now in our SwiftUI view, let's use this new method instead, remove the dispatch group call and add this one inside the task closure in the ContentView struct…

.task {

itunesRemote.asyncGroups(from: ItunesCategoryIdentifier.allCases)

}

Now if you run the app you will see the categories in the app but this time using a group task!

For more about Swift Concurrency, you can find a curated list of videos from WWDC 2021 here.

Thanks. 🤖