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 with ObservableObject.
  • We create a Task in startUpdating() 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 in stopUpdating().
  • In refreshQuote(for:) we use await to fetch the new data and then update the quotes dictionary on the main thread via MainActor.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.