In this demo, you’ll update TheMet app from part 3 of the SwiftUI Apprentice book. This app allows you to browse items from The Metropolitan Museum of Art that match a string query.
Instead of using basic async/await for networking, you’ll update this app to use a TaskGroup
to retrieve a collection of objects.
Open TheMet app in the Starter folder.
To begin, run the app on the simulator.
The app starts with “rhino” as the query term. Try searching for another term:
Next, change the app to use a TaskGroup
. Open TheMetStore
.
You’ll notice the fetchObjects
function is using async/await to retrieve a list of objectIds
, then a list of objects. The objects are appended to an array which is being observed. This is done sequentially, and fetching a large list of objects in this way can take time. Fetching concurrently would be a great enhancement.
At the top of fetchObjects
, add a TaskGroup
that can throw an error by using withThrowingTaskGroup
.
func fetchObjects(for queryTerm: String) async throws {
let newObjects = try await withThrowingTaskGroup(of: Object?.self, returning: [Object].self) { taskGroup in
}
}
Inside the TaskGroup, retrieve the objectIds relevant to the search term passed into the function. You’ll use these objectIds
to create child tasks for the group. Notice that the app awaits the result because this is an asynchronous network request.
func fetchObjects(for queryTerm: String) async throws {
let newObjects = try await withThrowingTaskGroup(of: Object?.self, returning: [Object].self) { taskGroup in
if let objectIds = try await self.service.getObjectIDs(from: queryTerm) {
}
}
}
To preserve the existing logic, add a for loop that enumerates through the list of objectIds while the index
is less than the maxIndex
set by the app.
func fetchObjects(for queryTerm: String) async throws {
let newObjects = try await withThrowingTaskGroup(of: Object?.self, returning: [Object].self) { taskGroup in
if let objectIds = try await self.service.getObjectIDs(from: queryTerm) {
for (index, objectID) in objectIds.objectIDs.enumerated() where index < self.maxIndex {
}
}
}
}
For each iteration, create a new task to retrieve the object.
func fetchObjects(for queryTerm: String) async throws {
let newObjects = try await withThrowingTaskGroup(of: Object?.self, returning: [Object].self) { taskGroup in
if let objectIds = try await self.service.getObjectIDs(from: queryTerm) {
for (index, objectID) in objectIds.objectIDs.enumerated() where index < self.maxIndex {
taskGroup.addTask {
return try await self.service.getObject(from: objectID)
}
}
}
}
}
Finally, use the values the TaskGroup has received to create an array of Objects to return from the TaskGroup.
func fetchObjects(for queryTerm: String) async throws {
let newObjects = try await withThrowingTaskGroup(of: Object?.self, returning: [Object].self) { taskGroup in
if let objectIds = try await self.service.getObjectIDs(from: queryTerm) {
for (index, objectID) in objectIds.objectIDs.enumerated() where index < self.maxIndex {
taskGroup.addTask {
return try await self.service.getObject(from: objectID)
}
}
}
return try await taskGroup.reduce(into: [Object]()) { partialResult, object in
if let object = object {
partialResult.append(object)
}
}
}
}
Once the TaskGroup
is complete, and passed the new objects from the query outside of the group, update the objects array with the new objects. This will update the UI as it’s listening to the array for changes.
func fetchObjects(for queryTerm: String) async throws {
let newObjects = try await withThrowingTaskGroup(of: Object?.self, returning: [Object].self) { taskGroup in
if let objectIds = try await self.service.getObjectIDs(from: queryTerm) {
for (index, objectID) in objectIds.objectIDs.enumerated() where index < self.maxIndex {
taskGroup.addTask {
return try await self.service.getObject(from: objectID)
}
}
}
return try await taskGroup.reduce(into: [Object]()) { partialResult, object in
if let object = object {
partialResult.append(object)
}
}
}
objects.append(contentsOf: newObjects)
}
With the TaskGroup
in place, the app no longer needs the old code. Remove the code and then run the app again.
The app continues to run and retrieve objects as before!
You may notice a warning about publishing changes in a background thread. Don’t worry about this for now. You’ll fix this in the next chapter.