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)
}
}
Overview. The Combine framework provides a declarative Swift API for processing values over time. These values can represent many kinds of asynchronous events. Combine declares publishers to expose values that can change over time, and subscribers to receive those values from the publishers.
Publisher defines how values and errors are produced. Publishers allow registration of a subscriber. A publisher provides data when available and upon request. A publisher that has not had any subscription requests will not provide any data.
An object that coordinates a group of related, network data transfer tasks.
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()
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With