Exploring Swift: Enumerated Parallel Tasks withThrowingTaskGroup
Book

Exploring MusicKit and Apple Music API

Unlock the full power of MusicKit & Apple Music APIs in your apps with the best guide! Use code musickit-blog for a limited-time 35% discount!

With Swift 5.5’s new concurrency features, Swift has become efficient in handling parallel tasks. In this article, we will explore how to use Swift’s withThrowingTaskGroup function with enumerated parallel tasks, returning a tuple of integers and structure while maintaining their positions.

What are withThrowingTaskGroup?

withThrowingTaskGroup is a function provided by Swift’s new concurrency model. It allows you to perform multiple tasks concurrently while also being able to throw errors if needed. It runs a group of tasks in parallel and awaits their completion, returning an array of results or propagating the first thrown error.

Here’s the basic syntax for withThrowingTaskGroup:

try await withThrowingTaskGroup(of: T.self) { group in
    // Add tasks to the group
}

Example of Using withThrowingTaskGroup

Let’s take an example where we are fetching a user’s followers. The follower’s data is lean and is sorted according to the timestamp, i.e., the first item in the list is the latest follower, and the last one is the oldest.

We need to fetch detailed user data for each follower, and to make it faster, we use parallel tasks with withThrowingTaskGroup:

func fetchFollowers(for user: User) async throws -> [User] {
  let followersIDs = await followService.fetchFollowers(for: user.id)

  return try await withThrowingTaskGroup(of: User?.self) { group in
    var followers: [User] = []

    for followerID in followersIDs {
      group.addTask {
        return await self.userService.fetchUser(for: followerID)
      }
    }

    for try await user in group {
      guard let user else { continue }
      followers.append(user)
    }
    return followers
  }
}

Well, this example does not maintain the order of results. In this case, as tasks are completed, the fetched users are appended to the followers, and their order will depend on the order in which tasks finish. This means the final list of followers will have a random order. We can sort by the timestamp at the end, but let’s try a different approach.

Implementing Enumerated Parallel Tasks

Let’s create a function that uses withThrowingTaskGroup to perform enumerated parallel tasks. This will ensure that the order of the returned followers list matches the order of the input followersIDs:

func fetchFollowerss(for user: User) async throws -> [User] {
  let followersIDs = await followService.fetchFollowers(for: user.id)
  var followers: [User?] = Array(repeating: nil, count: followersIDs.count)

  return try await withThrowingTaskGroup(of: (Int, User?).self) { group in
    for (index, followerID) in followersIDs.enumerated() {
      group.addTask {
        return (index, await self.userService.fetchUser(for: followerID))
      }
    }
    
    for try await (index, user) in group {
      followers[index] = user
    }

    return followers.compactMap { $0 }
  }
}

In this version, you first create an array of followers with placeholders for each user based on the count of followersIDs. Then, we use enumeration to track the index of each followerID and return a tuple of (Int, User?) within the task group.

After awaiting the results of the task group, we update the followers array at the corresponding index with the fetched user. Finally, we use compactMap to remove the optional User values and return an array of User instances in the same order as the input followersIDs.

Conclusion

In this article, we explored how to use Swift’s withThrowingTaskGroup function for handling concurrent tasks while maintaining their original order. By utilizing enumeration and a pre-initialized array with placeholders for each result, we could keep track of the order of the processed tasks.

I hope you enjoyed reading this post! If you’ve any other ways to implement withThrowingTaskGroup or any other feedback/constructive criticism, please let me know on Twitter!

Book

Exploring MusicKit and Apple Music API

Unlock the full power of MusicKit & Apple Music APIs in your apps with the best guide! Use code musickit-blog for a limited-time 35% discount!

Written by

Rudrank Riyam

Hi, my name is Rudrank. I create apps for Apple Platforms while listening to music all day and night. Author of "Exploring MusicKit". Apple WWDC 2019 scholarship winner.