具有泛型类型的可观察扩展

Observable extension with generic type

上下文

我想将 Alamofire.upload 包装成一个可观察的并包含有关上传进度的信息。
为此,我创建了一个自定义 UploadElement,它是一个代表进度和价值或结果的枚举。到目前为止我有:

enum UploadElement<Result> where Result: Codable {
    case progress(Double)
    case response(Result)
}

private func buildUploadRequest(url: URL, parts: [Data]) -> Observable<UploadRequest> {
    let uploadRequest = manager.upload(
        multipartFormData: { multipartFormData in /* build multipart */ },
        to: url
    )
    return Observable.just(uploadRequest)
}

func upload<Result: Codable>(url: URL, parts: [Data]) -> Observable<UploadElement<Result>> {
    buildUploadRequest(url: url, parts: parts)
        .flatMap { request in
            Observable<UploadElement<Result>>.create { observer in
                request.response { response in
                            do {
                                observer.on(.next(.response(/* decode here */)))
                                observer.on(.completed)
                            } catch let error {
                                observer.on(.error(error))
                            }
                }.uploadProgress { progress in
                    observer.on(.next(.progress(progress.fractionCompleted)))
                }
                .resume()

              return Disposable.create { request.cancel() }
            }
        }
}

现在我想扩展 Observable<UploadEment<Result>> 以获得更好的通知方式。

基本上是:

service.upload(url: ..., parts: ...)
    .progress { progress in /* */ }
    .result { result in /* */ }
    .subscribe()
    .dispose(by: disposeBag)

为此我尝试了:

extension ObservableType where Element == UploadElement<Resource> {
    func progress(progressCompletion: @escaping (Double) -> Void) -> Self {
        return self.do(onNext: { element in 
            switch element {
            case .progress(let progress): progressCompletion(progress)
            case .response: return
            }
        })
    }

    func result(resultCompletion: @escaping (Result) -> Void) -> Self {
        return self.do(onNext: { element in 
            switch element {
            case .response(let result): resultCompletion(result)
            case .progress: return
            }
        })
    }
}

我尝试了多种变体,但我得到的错误是:

  1. 找不到'Result in scope'
  2. 对通用类型的引用...需要参数

有没有可能达到那样的效果?

您只需将 where 子句从 class 作用域向下移动到函数作用域(如下所示)。

也就是说,我不认为在流中间像这样脱离 monad 是“更好的通知方式”。

更好的方法是将您的 Observable 分成两个流并订阅每个流:

extension ObservableType {
    func progress<Resource>() -> Observable<Double> where Element == UploadElement<Resource> {
        self.compactMap { element in
            switch element {
            case let .progress(progress):
                return progress
            case .response:
                return nil
            }
        }
    }

    func result<Resource>() -> Observable<Resource>  where Element == UploadElement<Resource> {
        self.compactMap { element in
            switch element {
            case .progress:
                return nil
            case let .response(resource):
                return resource
            }
        }
    }
}

有了上面的内容,你现在可以做这样的事情了:

let response = service.upload(url: ..., parts: ...)
    .share()

response
    .progress()
    .subscribe(onNext: { progress in /*...*/ })
    .disposed(by: disposeBag)

response
    .result()
    .subscribe(onNext: { result in /*...*/ })
    .dispose(by: disposeBag)

现在您没有任何空订阅。

我发现了一些有用的东西:

extension ObservableType {

    func progress<O: Codable>(progressCompletion: @escaping (Double) -> Void) -> Observable<UploadElement<O>> where Element == UploadElement<O> {
        return self.do(onNext: { element in
            if case .progress(let progress) = element {
                progressCompletion(progress)
            }
        })
    }

    func response<O: Codable>(responseCompletion: @escaping (O) -> Void) -> Observable<UploadElement<O>> where Element == UploadElement<O> {
        return self.do(onNext: { element in
            if case .response(let response) = element {
                 responseCompletion(response)
            }
        })
    }
}

现在我可以使用“计划”api:

service.update(data: /* ... */)
       .progress { progress in /* */ }
       .response { result in /* */ }
       .subscribe(
           onError: { error in /* */ }
       )
       .dispose(by: disposeBag)

然而,作为 Daniel ,这可能不是“更好的通知方式”。