Sometimes in an application, there is the need to periodically update data or perform some processing in the background. There are several different ways this can be accomplished, but with the newer Swift concurrency features this can be done in a relatively simple way.
Here is an example of an object responsible for fetching stock quotes in the background and updating an observable dictionary when new quotes are received.
@Observable
class QuoteManager {
var quotes: [String: Quote] = [:]
private var refreshTask: Task<Void, Never>? = nil
func startUpdating() {
refreshTask = Task {
repeat {
await refreshQuotes()
try? await Task.sleep(nanoseconds: 60_000_000_000)
} while (true)
}
}
func stopUpdating() {
refreshTask?.cancel()
}
private func refreshQuotes() async {
for ticker in quotes.keys {
await refreshQuote(for: ticker)
}
}
private func refreshQuote(for ticker: String) async {
do {
let quote = try await provider.fetchQuote(for: ticker)
await MainActor.run {
quotes[ticker] = quote
}
} catch {
print(error)
}
}
}
There are a few items to note here:
- We are using the new
@Observable
macro which automates a lot of the boilerplate code we would have previously needed withObservableObject
. - We create a
Task
instartUpdating()
which simply repeats in a loop to do the background work of fetching new quotes each minute. We also save the task item in a variable to simplify canceling the background process instopUpdating()
. - In
refreshQuote(for:)
we useawait
to fetch the new data and then update thequotes
dictionary on the main thread viaMainActor.run
Before Swift 5.5, we could have done this with DispatchQueue
using .main.async
for code in the main thread and .global(qos: .background).async
for the background processing, but I find the newer approach easier to understand.