与URLSession发布者实现重新连接并合并 [英] Implementing reconnection with URLSession publisher and Combine

查看:51
本文介绍了与URLSession发布者实现重新连接并合并的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想知道是否有一种方法可以通过新的Apple框架实现重新连接机制结合并使用URLSession Publisher

I'm wondering if there is a way to implement reconnection mechanism with new Apple framework Combine and use of URLSession publisher

  • 试图在WWDC 2019中找到一些示例
  • 试图与 waitsForConnectivity 一起玩没有运气(它甚至没有在自定义会话上调用委托)
  • 尝试了 URLSession.background ,但在发布过程中崩溃了.
  • tried to find some examples in WWDC 2019
  • tried to play with waitsForConnectivity with no luck (it even not calling delegate on custom session)
  • tried 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:
在Xcode 11 Beta中,似乎 waitsForConnectivity 无法正常工作

upd:
It seems like waitsForConnectivity is not working in Xcode 11 Beta

upd2:
Xcode 11 GM- waitsForConnectivity 正在运行,但在设备上.使用默认会话,设置标志并实现会话委托.无论您是否使用带有回调的init任务,都将调用方法 task正在等待连接.

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)
    }

}

推荐答案

您是否尝试过 retry(_:)?它可以在 Publisher s上使用,并在失败时重新运行请求.

Have you tried retry(_:) yet? It’s available on Publishers and reruns the request upon failure.

如果您不希望请求针对所有失败立即重新运行,则可以使用 catch(_:)并确定哪些失败需要重新运行.

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()
    }
}

这篇关于与URLSession发布者实现重新连接并合并的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆