Reputation: 310
I'm wondering if there is a way to implement reconnection mechanism with new Apple framework Combine and use of URLSession publisher
waitsForConnectivity
with no luck (it even not calling delegate on custom session)URLSession.background
but it crashed during publishing. I'm also not understanding how do we track progress in this way
Does anyone already tried to do smth like this?
upd:
It seems like waitsForConnectivity
is not working in Xcode 11 Beta
upd2:
Xcode 11 GM - waitsForConnectivity
is working but ONLY on device. Use default session, set the flag and implement session delegate. Method task is waiting for connectivity
will be invoked no matter if u r using init task with callback or without.
public class DriverService: NSObject, ObservableObject {
public var decoder = JSONDecoder()
public private(set) var isOnline = CurrentValueSubject<Bool, Never>(true)
private var subs = Set<AnyCancellable>()
private var base: URLComponents
private lazy var session: URLSession = {
let config = URLSessionConfiguration.default
config.waitsForConnectivity = true
return URLSession(configuration: config, delegate: self, delegateQueue: nil)
}()
public init(host: String, port: Int) {
base = URLComponents()
base.scheme = "http"
base.host = host
base.port = port
super.init()
// Simulate online/offline state
//
// let pub = Timer.publish(every: 3.0, on: .current, in: .default)
// pub.sink { _ in
// let rnd = Int.random(in: 0...1)
// self.isOnline.send(rnd == 1)
// }.store(in: &subs)
// pub.connect()
}
public func publisher<T>(for driverRequest: Request<T>) -> AnyPublisher<T, Error> {
var components = base
components.path = driverRequest.path
var request = URLRequest(url: components.url!)
request.httpMethod = driverRequest.method
return Future<(data: Data, response: URLResponse), Error> { (complete) in
let task = self.session.dataTask(with: request) { (data, response, error) in
if let err = error {
complete(.failure(err))
} else {
complete(.success((data!, response!)))
}
self.isOnline.send(true)
}
task.resume()
}
.map({ $0.data })
.decode(type: T.self, decoder: decoder)
.eraseToAnyPublisher()
}
}
extension DriverService: URLSessionTaskDelegate {
public func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) {
self.isOnline.send(false)
}
}
Upvotes: 6
Views: 1880
Reputation: 977
I read your question title several times. If you mean reconnect the URLSession's publisher. Due to the URLSession.DataTaskPublisher
has two results. Success output or Failure (a.k.a URLError
). It's not possible to make it reconnect after the output produced.
You can declare one subject. e.g
let output = CurrentValueSubject<Result<T?, Error>, Never>(.success(nil))
And add a trigger when network connection active then request resources and send the new Result
to the output. Subscribe output in the other place. So that you can get new value when network back-online.
Upvotes: -1
Reputation: 2195
Have you tried retry(_:)
yet? It’s available on Publisher
s and reruns the request upon failure.
If you don’t want the request to immediately rerun for all failures then you can use catch(_:)
and decide which failures warrant a rerun.
Here's some code to achieve getting the progress.
enum Either<Left, Right> {
case left(Left)
case right(Right)
var left: Left? {
switch self {
case let .left(value):
return value
case .right:
return nil
}
}
var right: Right? {
switch self {
case let .right(value):
return value
case .left:
return nil
}
}
}
extension URLSession {
func dataTaskPublisherWithProgress(for url: URL) -> AnyPublisher<Either<Progress, (data: Data, response: URLResponse)>, URLError> {
typealias TaskEither = Either<Progress, (data: Data, response: URLResponse)>
let completion = PassthroughSubject<(data: Data, response: URLResponse), URLError>()
let task = dataTask(with: url) { data, response, error in
if let data = data, let response = response {
completion.send((data, response))
completion.send(completion: .finished)
} else if let error = error as? URLError {
completion.send(completion: .failure(error))
} else {
fatalError("This should be unreachable, something is clearly wrong.")
}
}
task.resume()
return task.publisher(for: \.progress.completedUnitCount)
.compactMap { [weak task] _ in task?.progress }
.setFailureType(to: URLError.self)
.map(TaskEither.left)
.merge(with: completion.map(TaskEither.right))
.eraseToAnyPublisher()
}
}
Upvotes: 3