Context
I want to wrap the Alamofire.upload
into an observable and having info regarding the upload progress.
For that I have created a custom UploadElement
that is an enum representing either the progress and the value or the result. So far I have:
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() }
}
}
}
Now I would like to have an extension on an Observable<UploadEment<Result>>
to have a nicer way to be notified.
Basically it would be:
service.upload(url: ..., parts: ...)
.progress { progress in /* */ }
.result { result in /* */ }
.subscribe()
.dispose(by: disposeBag)
To do that I tried:
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
}
})
}
}
I tried multiple variation of that but the errors that I get are:
- Cannot find 'Result in scope'
- Reference to generic type ... required argument
Is it possible to achieve something like that?
CodePudding user response:
You just need to move the where clause from class scope down to function scope (shown below).
That said, I don't think breaking out of the monad like this in the middle of a stream is "a nicer way to be notified".
Better would be to break your Observable into two streams and subscribe to each of them:
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
}
}
}
}
With the above you can now do something like this:
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)
Now you don't have any empty subscribes.
CodePudding user response:
I found something that is working:
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)
}
})
}
}
Now I can use the "planned" api:
service.update(data: /* ... */)
.progress { progress in /* */ }
.response { result in /* */ }
.subscribe(
one rror: { error in /* */ }
)
.dispose(by: disposeBag)
However as Daniel mentioned this might not be the "nicer way of being notified".