6

我想知道是否有办法使用新的 Apple 框架实现重新连接机制结合和使用 URLSession 发布者

  • 试图在 WWDC 2019 中找到一些例子
  • 试图玩waitsForConnectivity没有运气(它甚至没有在自定义会话上调用委托)
  • 尝试过URLSession.background,但在发布过程中崩溃了。

我也不明白我们如何以这种方式跟踪进度
是否有人已经尝试过这样做?

upd:
似乎在 Xcode 11 Beta 中waitsForConnectivity 不起作用

upd2:
Xcode 11 GM -waitsForConnectivity正在工作,但在设备上。使用默认会话,设置标志并实现会话委托。task is waiting for connectivity无论您是否使用带有回调的初始化任务,都会调用方法。

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

}
4

2 回答 2

3

你试过retry(_:)了吗?它在Publishers 上可用,并在失败时重新运行请求。

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

这里有一些代码来实现取得进展。

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()
    }
}
于 2019-09-08T14:58:28.877 回答
-1

我读了好几遍你的问题标题。如果您的意思是重新连接 URLSession 的发布者。由于URLSession.DataTaskPublisher有两个结果。成功输出或失败(又名URLError)。产生输出后无法重新连接。

您可以声明一个主题。例如

let output = CurrentValueSubject<Result<T?, Error>, Never>(.success(nil))

并在网络连接处于活动状态时添加一个触发器,然后请求资源并将新的发送Result到输出。在另一个地方订阅输出。让您在网络重新上线时获得新的价值。

于 2020-05-08T07:50:28.983 回答