I'm fairly new to RxSwift, so I have the following problem, lets suppose I have 3 Observable example functions which return different observable types:
func observableFunc1(item1: DummyObject) -> Observable<AVURLAsset> {
return Observable.create { observer in
let something_with_AVURLAsset = AVURLAsset(url: URL(fileURLWithPath: "file"))
observer.onNext(something_with_AVURLAsset)
observer.onCompleted()
return Disposables.create()
}
}
func observableFunc2(item: AVURLAsset) -> Observable<Data> {
return Observable.create { observer in
let something_with_data = Data()
observer.onNext(something_with_data)
observer.onCompleted()
return Disposables.create()
}
}
func observableFunc3(_ params: [String:Any]) -> Observable<Any> {
let disposeBag = DisposeBag()
return Observable.create { observer in
RxAlamofire.request(api.sendData(params)).debug().subscribe(
onNext: { reponse in
observer.onCompleted()
},
onError: { error in
observer.onError(customError.theError("error"))
}
)
.addDisposableTo(disposeBag)
return Disposables.create()
}
}
How can I execute these 3 functions sequentially with the return value of func1 to be used on func2 and then when func2 is completed finally run func3.
I hope I made the question clear enough, but then again I'm really new to RxSwift and I don't know if these operations are possible or not.
Here's an example...
Assuming you have the three functions:
func func1() -> Observable<Data1>
func func2(_ data: Data1) -> Observable<Data2>
func func3(_ data: Data2) -> Observable<Data3>
Then you can:
let a = func1()
let b = a.flatMap { func2($0) }
let c = b.flatMap { func3($0) }
or:
let c = func1()
.flatMap { func2($0) }
.flatMap { func3($0) }
That said, your observableFunc3 is quite broken. You need to remove the dispose bag from it. As it stands, the network call will cancel before it starts.
If you really don't want it to emit any values then:
func observableFunc3(_ params: [String:Any]) -> Observable<Void> {
return RxAlamofire.request(api.sendData(params))
.filter { false }
}
The above will emit either a completed or an error but no next values.
Better would be to write it like:
func observableFunc3(_ params: [String:Any]) -> Observable<Void> {
RxAlamofire.request(api.sendData(params))
.map { _ in }
}
The above will emit one next and then a completed or an error. This is better because you can map or flatMap after it to have things happen once it's done.
Related
My sample is create call rest api two observable of result from load rx.request api from moya
func dataOneObservable() -> Observable<ObJectOneClass> {
return myprovider.rx.request(API.loadDataDetail())
.asObservable()
.retry()
.observeOn(MainScheduler.instance)
.filterSuccessfulStatusAndRedirectCodes()
.catchObjectError()
.mapObject(ObJectOneClassResult.self)
.map({ (response) -> ObJectOneClass in
if ObJectOneClass.data != nil {
if let item = response.data {
return item
}
return ObJectOneClass()
}
return ObJectOneClass()
})
}
func dataTwoObservable() -> Observable<ObJectTwoClass> {
return myprovider.rx.request(API.loadDataProfile())
.asObservable()
.retry()
.observeOn(MainScheduler.instance)
.filterSuccessfulStatusAndRedirectCodes()
.catchObjectError()
.mapObject(ObJectTwoClassResult.self)
.map({ (response) -> ObJectTwoClass in
if ObJectTwoClass.data != nil {
if let item = response.data {
return item
}
return ObJectTwoClass()
}
return ObJectTwoClass()
})
}
Then I want to combine result by use combineLastest of RxSwift but when I use .subscribe my response event can't passing result
my function call is same this:
func testCombine(completion:#escaping(_ result:Result<(ObJectOneClass,ObJectTwoClass),Error>) -> ()){
_ = Observable.combineLatest(dataOneObservable(), dataTwoObservable())
.asObservable()
.subscribe({ event in
//Event<(ObJectOneClass,ObJectTwoClass)>
//case .next((a, b)):
switch event{
case .next(response):
completion(.success(response))
case let .error(error):
completion(.failure(error as NSError))
default:
break
}
})
}
Then
please help me guide to syntax completion this.
Here's an interesting solution:
func testCombine(completion: #escaping(_ result: Result<(ObJectOneClass, ObJectTwoClass), Error>) -> ()) {
_ = Observable.combineLatest(dataOneObservable(), dataTwoObservable())
.map(Result.success)
.catch(combine(Result.failure, Observable.just))
.subscribe(onNext: completion)
}
func combine<A, B, C>(_ f: #escaping (A) -> B, _ g: #escaping (B) -> C) -> (A) -> C {
{ g(f($0)) }
}
That map to Result.success, catch just Result.failure is really common when you are dealing with Result types. It's so common that you might want to make a single operator to capture the notion.
extension Observable {
func toResult() -> Infallible<Result<Element, Error>> {
map(Result.success)
.asInfallible(onErrorRecover: combine(Result.failure, Infallible.just))
}
}
In response to your comment, here's a somewhat less advanced version:
extension Observable {
func toResult() -> Infallible<Result<Element, Error>> {
map(Result.success)
.asInfallible(onErrorRecover: { Infallible.just(Result.failure($0)) })
}
}
OK This my topic can fix problem by declare let in .next
func testCombine(completion:#escaping(_ result:Result<(ObJectOneClass,ObJectTwoClass),Error>) -> ()){
_ = Observable.combineLatest(dataOneObservable(), dataTwoObservable())
.asObservable()
.subscribe({ event in
switch event{
case .next((let one,let two)):
completion(.success((one,two)))
case let .error(error):
completion(.failure(error as NSError))
default:
break
}
})
}
I found a probable leak in my code because of a unit test which tests whether a dependency is called at deinit:
func testDeinit_ShouldStopMinutesTicker() throws {
let minutesTicker = MockMinutesTicker(elapsedTicksAfterStart: [(), (), ()])
var viewModel: AppointmentViewModel? = createAppointmentViewModel(minutesTicker: minutesTicker)
viewModel = nil
XCTAssertTrue(minutesTicker.isStopCalled)
}
This test is usually green. But when I refactored this:
func selectAppointment(index: Int) {
selectedCellParamRelay.accept(index)
}
private func setupCellParamSelection() {
selectedCellParamRelay
.withLatestFrom(sortedCellParams) { ($0, $1) }
.compactMap { [weak self] index, cellParams in
guard let `self` = self,
let selectedParam = cellParams[safe: index],
self.isUpcomingAppointment(selectedParam) else { return nil }
return selectedParam
}
.bind(to: upcomingCellParamRelay)
.disposed(by: disposeBag)
}
Into this:
func selectAppointment(index: Int) {
selectedCellParamRelay.accept(index)
}
private func setupCellParamSelection() {
selectedCellParamRelay
.withLatestFrom(sortedCellParams) { ($0, $1) }
.compactMap(selectAppointment(index:from:))
.bind(to: upcomingCellParamRelay)
.disposed(by: disposeBag)
}
private func selectAppointment(
index: Int,
from cellParams: [AppointmentCellParam]
) throws -> AppointmentCellParam? {
guard let selectedParam = cellParams[safe: index],
isUpcomingAppointment(selectedParam) else { return nil }
return selectedParam
}
private func isUpcomingAppointment(_ appointment: AppointmentCellParam) -> Bool {
return appointment.status != .missed && appointment.status != .finished
}
It becomes red and the deinit is not called at all.
The setupCellParamSelection func is being called from the init to setup the event handler for selecting any appointment cell by index. The sortedCellParams is a relay that is emitted by a dependency that in turn will get the values from the backend.
Can you help me figure out what went wrong with the second code?
Thanks.
Yes, because .compactMap(selectAppointment(index:from:)) needs to hold self in order to call selectAppointment even though the function doesn't need self.
The solution here is to move isUpcomingAppointment(_:) and selectAppointment(index:from:) outside of the class or as static functions within the class.
I have a Promise<T> that I would like to transform into a Guarantee<Bool> where true means the promise fulfilled and false if it got rejected.
I managed to reach this using
return getPromise()
.map { _ in true }
.recover { _ in Guarantee.value(false) }
I'm wondering if there is a neater way to do this.
Expanding on the original code and the answers here, I would make the extension explicitly for Void Promises, and keep the naming more inline with PromiseKit:
extension Promise where T == Void {
func asGuarantee() -> Guarantee<Bool> {
self.map { true }.recover { _ in .value(false) }
}
}
You can extend promise as below for easy usage mentioned under Usage
extension Promise {
func guarantee() -> Guarantee<Bool> {
return Guarantee<Bool>(resolver: { [weak self] (body) in
self?.done({ (result) in
body(true)
}).catch({ (error) in
body(false)
})
})
}
}
Usage:
// If you want to execute a single promise and care about success only.
getPromise().guarantee().done { response in
// Promise success handling here.
}
// For chaining multiple promises
getPromise().guarantee().then { bool -> Promise<Int> in
return .value(20)
}.then { integer -> Promise<String> in
return .value("Kamran")
}.done { name in
print(name)
}.catch { e in
print(e)
}
I am trying to upload firmware to a BLE device. Therefore I push some data, wait for an acknowledgment and then push some more, and so on.
But I am struggling with repeating the observer until the observer returns complete.
This is the observer, so if there are no more packets to be sent, it should complete and stop repeating.
let transferImage = Observable<Void>.create { (obs) -> Disposable in
guard let nextPacket = dataSendingTracker.getNextPacket() else {
obs.onCompleted()
return Disposables.create()
}
return self.instrument()
.flatMap{ $0.sendFWpacket(packet: nextPacket) }
.subscribe(onNext: { () in
obs.onNext(())
}, onError: { (error) in
obs.onError(error)
})
}
Any suggestion on how I can achieve this?
Try something like this but I consider this ugly way around.... not Rx Solution
transferImage.takeWhile will stop when getNextPacket returns nil or this case Integer smaller than zero..
func test() {
let transferImage = Observable<Int>.create { (obs) -> Disposable in
if let nextPacket = self.getNextPacket() {
obs.onNext(nextPacket)
} else{
obs.onNext(-1)
obs.onCompleted()
}
return Disposables.create()
}
transferImage.takeWhile{$0 > 0}.subscribe(onNext: { nextPacket in
print(nextPacket)
let completed = self.sendFWpacket()
if !completed {
self.test()
}
}, onError: { error in
print(error)
}, onCompleted: {
print("onCompleted")
}) {
print("disposed")
}.disposed(by: disposeBag)
}
func sendFWpacket()-> Bool {
return false
}
func getNextPacket() -> Int? {
return 1
}
Assuming you have a function that writes a block of data like this test function:
func writeDataBlock(offset: Int, blockSize: Int) -> Observable<Int> {
let writtenBytesCount = min(Int(arc4random_uniform(5) + 5), blockSize)
return Observable<Int>.just(writtenBytesCount)
}
In reality this function would also use some data buffer and try to push a block of a given size from that data at a given offset, and return the number of bytes written when done. Here you could use your logic from transferImage.
Then the complete transfer function could be written using recursion like so:
func writeAllDataRec(offset: Int, totalSize: Int, observer: AnyObserver<String>) {
guard offset < totalSize else {
observer.onNext("writeAllData completed")
observer.onCompleted()
return
}
var subscriber: Disposable?
let blockSize = min(10, totalSize - offset)
subscriber = writeDataBlock(offset: offset, blockSize: blockSize).subscribe { ev in
switch ev {
case let .next(writtenBytesCount):
debugPrint("writeNextBlock from offset: \(offset); writtenBytesCount = \(writtenBytesCount)")
let newOffset = offset + writtenBytesCount
writeAllDataRec(offset: newOffset, totalSize: totalSize, observer: observer)
case .completed:
subscriber?.dispose()
//debugPrint("writeAllData block completed")
case let .error(err):
subscriber?.dispose()
observer.onError(err)
observer.onCompleted()
}
}
}
func writeAllData(totalSize: Int) -> Observable<String> {
return Observable<String>.create { (observer) -> Disposable in
writeAllDataRec(offset: 0, totalSize: totalSize, observer: observer)
return Disposables.create()
}
}
This could be tested like so:
var subscriber: Disposable?
...
self.subscriber = writeAllData(totalSize: 100).subscribe(onNext: { (message) in
print("\(message)")
})
In this solution I assume that your next block of data depends on how much of the data was written previously and that there's no shared global state.
This could be simplified a lot with the logic of RxJS expand, but unfortunately this function is not present in RxSwift (4.1.2).
I am not so convinced with RxSwift yet, and it's really hard to cleat understanding. After reviewing different materials, I cant' still work and manipulate sequences.
On the whole I have problem with type converting:
Cannot convert return expression of type 'Observable<Bool>' to return type 'Observable<Void>' (aka 'Observable<()>')
I have CocoaAction processing, and should return Observable<Void>
func stopProject() -> CocoaAction {
return CocoaAction { _ in
let stopProject = stop(project: self.projectId)
return stopProject.asObservable() //wrong converting
}
}
The stop function return Observable<Bool>
Finish view:
return stop(project: self.projectId).flatMap { _ in
Observable<Void>.empty()
}
let voidObservable = boolObservable.map { Void() }
let voidObservable = boolObservable.map { _ in Void() }
And in the case that you only want to emit a value if the boolean value is true:
let voidObservable = boolObservable.filter { $0 }.map { _ in Void() }
Adding this extension:
public extension ObservableType {
func mapToVoid() -> Observable<Void> {
return map { _ in }
}
}
so you could do
myObservable.mapToVoid()