Using distinctUntilChanged with combineLatest throws Equatable error with RxSwift - swift

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)

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)

RxSwift protocol and concrete types

I'm having trouble with a BehaviorRelay that has a protocol type and using it on concrete types. Here's my code:
protocol Item {
var title: { get }
}
struct Can: Item {
let title = "Can"
}
let canRelay = BehaviorRelay<Can?>(value: nil)
func handle(item: BehaviorRelay<Item?>) {
// do something with item here
}
handle(item: canRelay) // can't do this?
I assumed I would be able to call handle(item:) but it's not the case because the arguments don't match. I get that they don't match, but Can is a type of Item so shouldn't this be possible?
Can may be a subtype of Item, but BehaviorRelay<Can> is not a subtype of BehaviorRelay<Item>.
Also, you should not be passing BehaviorRelays around in code. Pass Observables instead.
Once you know these two rules, you end up with:
func handle(item: Observable<Item?>) {
// do something with item here
}
handle(item: canRelay.map { $0 })

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: disposed(by:) weirdness

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.

Escaping closure captures mutating 'self' parameter

I'm trying to subscribe to an observable generated by a combineLatest, after flatMap. If I'm running this code in a struct I get this error:
Escaping closure captures mutating 'self' parameter
If I change to a class the error does not occurs. I understand that with struct I cannot asynchronously mutate the state of the struct, but, in this case I'm actually not mutating it, or am I?
There's another way to fix it without using a class?
public struct ViewModel {
let disposeBag = DisposeBag()
var relay1: PublishRelay<()>
var relay2: PublishRelay<()>
init() {
relay1 = PublishRelay<()>()
relay2 = PublishRelay<()>()
Observable.combineLatest(relay1, relay2)
.filter { tuple in 1 == 1 } // some boolean logic here
.flatMap { _ in return Observable<Void>.just(()) } // some map filter here
.subscribe(onNext: { _ in
self.doCoolStuff()
}).disposed(by: disposeBag)
}
func doCoolStuff() {
// Do cool Stuff here
}
}
All instances methods receive a reference to self as an implicit first parameter, which is why you have to capture self to call the instance method. Is you don't actually need any instance variables then make doCoolStuff() a static function and you will no longer need to call it with self. (you can use Self. as of Swift 5, or just the type name ViewModel.). You can similarly avoid this issue if doCoolStuff is a closure defined inside of init.