RxSwift: disposed(by:) weirdness - swift

So the code below compiles with the errror
var doneSubscription: Disposable = item.doneSubjectObservable
.debug("doneSubscriptions")
.subscribe(
onNext: {
done in self.validateDone(done: done, item: item)
}).disposed(by: disposeBag)
Value of type '()' does not conform to specified type 'Disposable'
on the line .disposed(by: disposeBag)
But I can do this without error:
var doneSubscription: Disposable = item.doneSubjectObservable
.debug("doneSubscriptions")
.subscribe(
onNext: {
done in self.validateDone(done: done, item: item)
})
doneSubscription.disposed(by: disposeBag)
All I've done is moved .disposed(by: disposeBag) out of the subscription chain.
Am I missing something, aren't these two approaches equivalent?

No, they are not equivalent.
In the first case, you are storing the return value of this whole expression into doneSubscription, a variable of type Disposable:
item.doneSubjectObservable
.debug("doneSubscriptions")
.subscribe(
onNext: {
done in self.validateDone(done: done, item: item)
}).disposed(by: disposeBag)
Since disposed(by:) does not return anything, this gives you an error.
In the second case however, you actually assigned a Disposable to the variable doneSubscription - the return value of subscribe.
To fix your first case, simply remove the variable declaration.

Related

How to trigger an observable when another observable fires in RxSwift?

I have the following observable:
observable1
.withLatestFrom(observable2) { (obs1Value, obj2Value) -> [SomeDataType] in
return obj1Value.someFunction(for: obj2Value)
}
.bind(to: someSubject)
.disposed(by: disposeBag)
And it should be updated when the following PublishRelay fires:
let publishRelay = PublishRelay<AnotherDataType>()
observable1 and observable2 have 2 different types
I've tried the following:
Observable.combineLatest(observable1, observable2, publishRelay.asObservable())
.map { obs1Value, obj2Value, _ in
return obj1Value.someFunction(for: obj2Value)
}
.bind(to: someSubject)
.disposed(by: disposeBag)
But have faced the problem that observable1 & observable2 are not being binded until publishRelay fires (it basically waits for all 3 values to arrive)
I need to the observables to go through even without PublishRelay but get an extra update when the PublishRelay fires (I'm not using any data from PublishRelay inside of the observables subscription)
What's the best way to achieve the above?
Thanks!
The key here is to use startWith in order to "pre-fire" the offending observable so it has a latest value to work with. Since you don't care about the relay's value, you can map to unit and startWith unit.
It would look something like this:
Observable.combineLatest(observable1, publishRelay.asObservable().map { _ in () }.startWith(()))
.withLatestFrom(observable2) { $0.0.someFunction(for: $1) }
.bind(to: someSubject)
.disposed(by: disposeBag)

Using distinctUntilChanged with combineLatest throws Equatable error with RxSwift

I am trying to use distinctUntilChanged with combineLatest, eg:
Observable.combineLatest(focusedCourse, infoState)
.distinctUntilChanged()
.map { focusedCourse, infoState in
// Implementation
}
.bind(to: videoSource)
.disposed(by: bag)
However I am getting the error:
Type '(BehaviorRelay<Course?>.Element, BehaviorRelay<InfoState>.Element)' cannot conform to 'Equatable'
I have conformed both Course and InfoState to equatable but still get this error. How can I fix this?
I don't think a tuple conforms to Equatable even if all its elements do so you have to provide the equality check to distinctUntilChanged:
Observable.combineLatest(focusedCourse, infoState)
.distinctUntilChanged { $0 == $1 }
.map { focusedCourse, infoState in
// Implementation
}
.bind(to: videoSource)
.disposed(by: bag)

Swift Combine sink stops receiving values after first error

Im moving my project to Combine from RxSwift
I have a logic where I want publisher to emit event every time I click button. Acrually clicking button executed pushMe.send()
pushMe
.print("Debug")
.flatMap { (res) -> AnyPublisher<Bool, Error> in
return Future<Bool, Error>.init { closure in
closure(.failure(Errors.validationFail))
}.eraseToAnyPublisher()
}
.sink(receiveCompletion: { completion in
print("Completion received")
}, receiveValue: { value in
print("Value = \(value)")
})
.store(in: &subscriptions)
The console result
Debug: receive value: (true)
Completion received
Debug: receive value: (true)
Debug: receive value: (true)
I do not understand why sink receive error only on first event. The rest clicks are ignored.
What does flatMap do -
Subscribes to the given publisher (let's say XPublisher).
Sends the Errors and Output values (not finished event/ completion) emitted
by XPublisher to the down stream.
So If you handle errors inside the flat map , (which means the publisher inside the flatMap does not emit errors), then flatMap Never sends an error to the down stream.
pushMe
.print("Debug")
.flatMap { (res) -> AnyPublisher<Bool, Never> in //<= here
return Future<Bool, Error>.init { closure in
closure(.failure(Errors.validationFail))
}
.replaceError(with: false) //<= here
.eraseToAnyPublisher()
}
.sink(receiveCompletion: { completion in
print("Completion received")
}, receiveValue: { value in
print("Value = \(value)")
})
.store(in: &subscriptions)
Otherwise you can handle error outside the fatMap. Problem here is that, once an error out whole the subscription / cancellable cancelled. ( in the below example error has replace with a false value)
pushMe
.print("Debug")
.flatMap { (res) -> AnyPublisher<Bool, Error> in
return Future<Bool, Error>.init { closure in
closure(.failure(Errors.validationFail))
}
.eraseToAnyPublisher()
}
.replaceError(with: false) //<= here
.sink(receiveCompletion: { completion in
print("Completion received")
}, receiveValue: { value in
print("Value = \(value)")
})
.store(in: &subscriptions)
What is happening in the above code.
FlatMap error outs.
replace the error with false (One false value will receive Because of this)
subscription cancelled because of the error out in the stream.
The rule is that if an error propagates down the pipeline, the entire pipeline is cancelled. Thus, if your Future generates an error, it passes as an error to the Sink and thus the pipeline is cancelled all the way up to the Publisher.
The pattern for preventing this is to deal with the error inside the FlatMap. Basically, you've got two pipelines here: the one that starts with pushMe and the one that starts with Future. Simply don't let the error generated by the Future pipeline "leak" out into the pushMe pipeline, and so the pushMe pipeline will not be cancelled. Instead, catch the error inside the FlatMap and, if you want to pass something out of it to your Sink, pass out of it some sort of value that tells your Sink that there has been a bad input.
A simple solution in your case would be to change the type your FlatMap to <Bool,Never>, and pass either true or false as the Bool to indicate whether validation succeeded in the Future or not.
Or, if it's important to you to pass more detailed information about the error down the pipeline, change the type of your FlatMap to <Result<Bool,Error>,Never> and package the error information into the .failure case of the Result object.
This is how Publishers work in Combine.
The Publisher can either emit values or emit a completion event - once a completion event was emitted, the Publisher is finished and it cannot emit any other values or another completion event. If the Publisher emits an error, the error is emitted as a failure completion, meaning that once an error is emitted, the Publisher completes and it cannot emit any more values.
There are several Combine operators designed for handling errors without completing the Publisher. Have a look into the catch operator for instance.
First, thanks all for helping with this question.
Answer of #matt is one of the possible solution.
Another solution is to create new pipeline every time you clicking button.
Im using this approach because I have sequence of steps below failing publisher and Im not able to rely of dummy true/false result further.
Just<String>()
.sink(receiveValue: { value in
startProcess()
.sink(receiveCompletion: { (completion:
Subscribers.Completion<Failure>) in
// can handle ALL types of error of pipe line in ONE place
}, receiveValue: { (v: P.Output) in
// handle correct result
})
})
.store(in: &subscriptions)
func startProcess() -> AnyPublisher<Bool, Error> {
Future<Bool, Error>.init { closure in
// action 1
closure(.success(true))
}
.flatMap { (b: Bool) -> AnyPubilsher<Void, Error> in
Future<Bool, Error>.init { closure in
// action 2
closure(.success(()))
}
}
}
Benefit is that you are able to handle all types of errors in one place if second sink()

RxSwift, how to use NotificationCenter more gently?

Got a IAP purchased notification, then I request the transaction from my server.
To download a song and play , if transaction OK.
I use RxSwift, The following code works, I want to improve it.
NotificationCenter.default.rx.notification( .purchase )
.takeUntil(self.rx.deallocated)
.map { (noti) -> String in
return "Not care"
// I want to optimize this step
}.concat(self.transactionRequest())
.flatMap{ self.downloadSong($0) }.subscribe(onNext: { downloaded in
if downloaded{
self.playMusic()
}
})
.disposed(by: rx.disposeBag)
func transactionRequest() -> Observable<String> { // ... }
func downloadSong(_ src: String) -> Observable<Bool> { // ... }
I can not use like this
NotificationCenter.default.rx.notification( .purchase )
.takeUntil(self.rx.deallocated)
.concat(self.transactionRequest())
because
Instance method 'concat' requires the types 'Notification' and
'String' be equivalent
So I add a boilerplate map
Any more proper operator, or custom operator?
The return type of the Observable that is feeding concat and the one that is passed to concat must be the same. I suggest you use flatMap instead. Also, you are capturing self all over the place which means memory issues.
Here's how I would do it:
NotificationCenter.default.rx.notification(.purchase)
.flatMapLatest { [unowned self] _ in self.transactionRequest() }
.flatMapLatest { [unowned self] in self.downloadSong($0) }
.subscribe(onNext: { [unowned self] downloaded in
if downloaded {
self.playMusic()
}
})
.disposed(by: rx.disposeBag)
If you didn't put all your functions inside the class, you could get rid of the self. and not have to worry about capturing self.

RxSwift: Extra argument 'onError' in call

I have following code in Swift 4 with RxSwift
worthReacting.flatMap{ (userSearch) in
translator.getTranslation(ofWord: userSearch)
}.subscribe(
onSuccess: {(dataModel) in
state.value = .translation(word: dataModel.definition,
translations: dataModel.translations)
},
onError: {(error) in
state.value = .networkError
},
onCompleted: {
state.value = .unknownWord
}).disposed(by: disposeBag)
worthReacting has type of Observable<String>
translator.getTranslation returns Maybe<DataModel>
I'm getting build error
Extra argument 'onError' in call
Maybe flatmaped into Observable produces Observable. Observable can not emit onSuccess event, instead it will emit onNext. Following code will work:
worthReacting.flatMap{ (userSearch) in
translator.getTranslation(ofWord: userSearch)
}.subscribe(
onNext: {(dataModel) in
self.state.value = .translation(word: dataModel.definition,
translations: dataModel.translations)
},
onError: {(error) in
self.state.value = .networkError
},
onCompleted: {
self.state.value = .unknownWord
}).disposed(by: disposeBag)
For those who get the OP's error, but have a different cause, check that you have not incidentally make one of your closures throwing by not making your do-catch clause exhaustive.
Also, take care that you don't use a single-statement closure (in this case you should explicitly return).