SwiftUI - Networking

A view will often use an observable object to reflect the latest data fetched from a web-service.

Apple has introduced the Combine framework, a way to handle asynchronous events by combining event-processing operators.

In Headlines, I needed to retrieve some data for each category and return a publisher containing the requested elements once all networks calls had completed.

Using the built-in dataTaskPublisher comes in handy to create these multiple asynchronous requests and merge them into a single publisher sequence.

The collect() operator allowed me to group all the elements from that sequence into a single array.

typealias HeadlinesResult = AnyPublisher<(HeadlinesSection, Result), Error>
class Webservice {
     func fetch(preferences: UserPreferences) -> AnyPublisher<[(HeadlinesSection, Result)], Never> {
         let decoder = JSONDecoder()
         decoder.dateDecodingStrategy = .iso8601
         let recency = preferences
              .recencies
              .filter { $0.isSelected }
              .first?.date
         
         let country = preferences
              .countries
              .filter { $0.isSelected }
              .first?.country
        
         let categories = preferences
              .categories
              .filter { $0.isSelected }
         
         let publishers = categories.map { category -> HeadlinesResult in
             let endpoint = Endpoint.search(
                 recency: recency ?? .today, 
                 country: country ?? .france, 
                 category: category.name.rawValue
             )
             let publisher = URLSession.shared.dataTaskPublisher(for: endpoint.url!)
                 .retry(2)
                 .map { $0.data }
                 .decode(type: Result.self, decoder: decoder)
                 .receive(on: RunLoop.main)
                 .map { (category.name, $0) }
                 .eraseToAnyPublisher()
             return publisher
          }
          return Publishers.MergeMany(publishers)
              .collect()
              .replaceError(with: [])
              .eraseToAnyPublisher()
          }
}

Chaining operators on the dataTaskPublisher enable us to implement successive operations on the upstream publisher delivering the expected HeadlinesResult type.

Combine facilitates the handling of asynchronous calls using a declarative approach resulting in clarity and code legibility.

SwiftUI and Combine works well together as they provide both that very declarative approach.