I have the following extension on a Publisher
which allows me to paginate a URL request. I originally used this in a specific use case, where the Output
of the publisher was of type CustomType
.
extension Publisher where Output == CustomType,
Failure == Error {
func paginate(pageIdPublisher: CurrentValueSubject<String?, Never>) -> AnyPublisher<[User], Never> {
return self
.handleEvents(receiveOutput: { response in
if let maxId = response.pageId {
pageIdPublisher.send(maxId)
} else {
pageIdPublisher.send(completion: .finished)
}
})
.reduce([]) { allUsers, response in
return response.users allUsers
}
.catch { error in
Just([])
}
.eraseToAnyPublisher()
}
}
struct CustomType: Codable {
let users: [User]
let pageId: String?
}
This is called like this:
func loadItem() async throws -> [String] {
let pageIdPublisher = CurrentValueSubject<String?, Never>(nil)
return try await pageIdPublisher
.flatMap { pageId in
urlSession
.publisher(
for: .item(pageId: pageId),
receiveOn: queue
)
}
.paginate(pageIdPublisher: pageIdPublisher) // <- This part
.singleOutput()
}
However, I now want to make it generic so that it can be used on any Output
type, so long as it has a pageId
and some kind of array.
I tried using a protocol Pageable
like this:
protocol Pageable {
associatedtype T
var pageId: String? {get}
var items: [T] {get}
}
But I can't use that with the extension because Output
can't have be used with a protocol that contains an associatedType
.
Is this possible?
CodePudding user response:
If you constrained the Output
type with :
to your Pageable
protocol, and used Output.T
for the returned publisher's output type, the paginate
method should compile:
extension Publisher where Output: Pageable,
Failure == Error {
func paginate(pageIdPublisher: CurrentValueSubject<String?, Never>) -> AnyPublisher<[Output.T], Never> {
return self
.handleEvents(receiveOutput: { response in
if let maxId = response.pageId {
pageIdPublisher.send(maxId)
} else {
pageIdPublisher.send(completion: .finished)
}
})
.reduce([]) { allItems, response in
return response.items allItems
}
.catch { error in
Just([])
}
.eraseToAnyPublisher()
}
}
Another idea of making this more generic would be to constrain Output
to be an Identifiable
type, where ID
is an optional, and provide an extra closure argument to specify the mapping from Output
to the array:
extension Publisher where Output: Identifiable,
Failure == Error {
func paginate<T, ID>(
pageIdPublisher: CurrentValueSubject<Output.ID?, Never>,
items: @escaping (Output) -> [T]
)
-> AnyPublisher<[T], Never>
where Self.Output.ID == ID?
{
...
}
}
You would use response.id
instead of response.pageId
, and items(response)
instead of response.items
in the implementation. At the callsite, you would pass in \.users
for the closure argument, for example.