I want to add a function you can call when using URLSession.DataTaskPublisher to fetch output from a URLRequest
, that can print out the request and the data (whenever they are received.
URLSession.shared.dataTaskPublisher(for: URL(string: "https://jsonplaceholder.typicode.com/posts")!)
.logEvents()
.map { data, _ in return data }
.decode(type: [Post].self, decoder: JSONDecoder())
.eraseToAnyPublisher()
.sink { /// .. handles the sinked values }
where the logEvents()
function should print out the data, and the response object to console, or error if it fails. For URLSession.dataTask
i've written it like this:
func dataTask(withLogging request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> ()) -> URLSessionDataTask {
return self.dataTask(with: request) { data, response, error in
URLRequest.debugPrintYAML(request: request, response: response, received: data, error: error)
completionHandler(data, response, error)
}
}
this just replaces dataTask(with:
, with dataTask(withLogging:
, and makes it really easy to see good console logs of all requests that are called using it. The goal i'm doing is to write a similar function for the Publisher version, dataTaskPublisher
what i would like the .logEvents() to do is calling the same
URLRequest.debugPrintYAML(request: request, response: response, received: data, error: error)
What i've tried:
doing it in .map
:
I have access to data
and response
there, but i have to change the return type of the function, and i don't really want to to that in a generic logging function. Also having it as a side effect in a map function isn't really optimal, that function should only map. This is what i've tried in the example given below
doing it in .sink
:
Since i allready have mapped to responses by the time i get to the .sink, i have lost the information about response and data, which means i ofc cant write them out.
doing it in .handleEvents
inside a function:
If i use .handleEvents
, i don't really have a place to store the cancellable, and i'm allready creating a Cancellable
when calling .sink
later, so having two cancellables is not optimal.
extension Publisher where Output == (data: Data, response: URLResponse),
Self == URLSession.DataTaskPublisher
{
func logEvents() -> URLSession.DataTaskPublisher {
let cancellable = self.handleEvents { values in
print(values.description)
}
// the cancellable is not retained, and therefore this will never fire
return self
}
}
This extension is open source and located here
Try it this way:
import UIKit
import Combine
fileprivate func logOneEvent(data: Data, response: URLResponse) {
print("Got \(data.count) bytes")
print("Response: \(response)")
}
extension Publisher where Output == URLSession.DataTaskPublisher.Output
{
func logEvents() -> Publishers.HandleEvents<Self> {
return self.handleEvents(receiveOutput: logOneEvent)
}
}
let jsonURL = URL(string: "https://jsonplaceholder.typicode.com/posts")!
let subscription = URLSession.shared.dataTaskPublisher(for: jsonURL)
.logEvents()
.sink { completion in
switch completion {
case .finished:
print("Done")
case .failure(let error):
print("Failed \(error)")
}
} receiveValue: {
debugPrint($0)
}
A publisher (in this case a Publishers.HandleEvents
instance) is a thing you can return from a function like logEvents
. You can just let that thing handle subscriptions normally, there's no need to try and inject and maintain your own subscription.