TLDR: How to serialize independent, heterogeneous HTTP requests using Combine
and Foundation
.
I have a view model that displays to pieces of information from an API, let's assume it's a user's bio and the latest posts from the user.
class ViewModel: ObservableObject {
@Published var bio: String?
@Published var posts: [Post]?
private var subscriptions = [AnyCancellable]()
let username: String
init (username: String, api: API) {
self.username = username
api.fetchProfile(username: username)
.assign(to: \.profile, on: self)
.store(in: &subscriptions)
api.fetchLatestPosts(username: username)
.assign(to: \.latestPosts, on: self)
}
}
As the requests don't use information from each other, a simple flatMap()
would not be what I would expect to use here.
I figured the best way to solve this is to move this issue onto the level of the API client, so it can consider these requirements for any request on any view:
class API {
// …
func publisher<T: Decodable>(for type: T.Type,
from url: URL)
-> AnyPublisher<T?, Never>
{
let nonce = UInt64(Date.timeIntervalSinceReferenceDate * 1_000)
var request = URLRequest(url: url)
request.setValue(nonce.description,
forHTTPHeaderField: "Nonce")
// Basically, I'm looking for a way to return a
// publisher here that satisfies to the outlined
// requirements.
return URLSession.shared
.dataTaskPublisher(for: request)
.map(\.data)
.decode(type: type, decoder: JSONDecoder())
.map { $0 as Optional } // Let's not make this too
.replaceError(with: nil) // complicated with error handling.
.eraseToAnyPublisher()
}
// …
}
I was considering this:
- create a publisher for the nonce,
- zip the nonce to the
URL
- map nonce+
URL
toURLRequest
- flatMap using
URLSession
. - at some point in the chain, publish a new nonce for the next request.
But then I still wonder: How to make sure each request gets a different nonce, because it would be easy to subscribe two requests to the nonce generator and let them have the same nonce (which would not work).