RxSwift observe array change only when appended new element - ios

Is there a way to subscribe to array change only when a new element is appended?
So I want the following closure to be executed only when a new element is appended.
array.asObservable().subscribe(onNext: { array in
}).disposed(by: self.bag)
If, for example, an element is removed from this array, I don't want this closure to be executed.
EDIT:
Is there a way to only have newly appended elements in the closure? In my case I append subsequences of various lengths, so I can't just look at the last element of the array inside the closure.

Maybe something like this
let array = Variable<[Int]>([])
array.asObservable().distinctUntilChanged { $0.count > $1.count}.subscribe(onNext: {
print($0)
}).disposed(by: disposeBag)
array.value.append(1) //called
array.value.append(2) //called
array.value.remove(at: 0) //not called
array.value.append(3) //called

In that case you should resign using Variable and use Subjects. I think, that your requirements should meet PublishSubject:
let subjectArray = PublishSubject<[Int]>([])
array.asObservable().subscribe(onNext: {
print($0)
}).disposed(by: disposeBag)
When you add new elements to subjectArray only that new elements will be printed.

Related

How do I observe two rx sequences and subscribe with two closure arguments?

I want to observe two behaviorRelays with a single observer, wait for both relays to emitt their values, then in the subscription have two seperate closure arguemts, one for each relay. Something like this:
let one = firmwareService.basicIODeviceUnit.compactMap { $0?.canBeUpdated }
let two = firmwareService.motorDeviceUnit.compactMap { $0?.canBeUpdated }
Observable.of(one, two).flatMap{ $0 }.subscribe(onNext: { a, b in
print("--", a, b)
}).disposed(by: disposeBag)
The above code isn't allowed. The operators like merge or zip seem to bundle both relays into a single closure argumet so I guess they won't work. What do I use?
I have looked through this thread, so it should be possible, but I can't wrap my head around it since I use swift
RxJS Subscribe with two arguments
I'm not sure what you mean because zip does exactly what you want. So does combineLatest.
let one = firmwareService.basicIODeviceUnit.compactMap { $0?.canBeUpdated }
let two = firmwareService.motorDeviceUnit.compactMap { $0?.canBeUpdated }
Observable.zip(one, two)
.subscribe(onNext: { a, b in
print("--", a, b)
})
.disposed(by: disposeBag)
you can use combineLatest:
combineLatest is an operator which you want to use when value depends on the mix of some others Observables.
When an item is emitted by either of two Observables, combine the latest item emitted by each Observable via a specified closure and emit items based on the results of this closure.
For reference follow this -
combineLatest meduim link

Add last element to an array from for loop

I have a code like so...
for element in self.arr_Offline {
self.arr_OfflineTemp.add(element)
break
}
self.arr_Offline.removeObject(at: 0)
Here I'm looping through an array called self.arr_Offline, adding the first element from that array into another array called self.arr_OfflineTemp and immediately doing break so that the second array will have just one element and then once I'm outside the for-loop, I remove the added object from the main array by doing self.arr_Offline.removeObject(at: 0)
But I want to add the last element to self.arr_OfflineTemp instead of the first and then remove that last element from self.arr_Offline. It should look something like so...
for element in self.arr_Offline { // looping through array
self.arr_OfflineTemp.add(lastElementOf'self.arr_Offline') //add last element
break
}
self.arr_Offline.removeObject(at: 'last') //remove that last element that was added to `arr_OfflineTemp`
How can I achieve this..?
First of all never use NS(Mutable)Array and NS(Mutable)Dictionary in Swift.
Native Swift Array has a convenient way to do that without a loop
First element:
if !arr_Offline.isEmpty { // the check is necessary to avoid a crash
let removedElement = self.arr_Offline.removeFirst()
self.arr_OfflineTemp.append(removedElement)
}
Last element:
if !arr_Offline.isEmpty {
let removedElement = self.arr_Offline.removeLast()
self.arr_OfflineTemp.append(removedElement)
}
And please drop the unswifty snake_case names in favor of camelCase
Did you try the same approach with reversed() Swift:
for i in arr.reversed():
// your code logic
After adding element, you can remove the last element from the list using this:
lst.popLast() or lst.removeLast()
Try this for the above code:
for element in self.arr_Offline.reversed() {
self.arr_OfflineTemp.add(element)
break
}
self.arr_Offline.popLast()
Is this something you are looking for?

Collecting stored variable property using withLatestFrom

I'm wondering if there is a way in RxSwift to observe value of stored variable property. Eg. in following example:
var updatedValue: Int = 0
var observedValue: Observable<Int> {
return Observable.create({ (observer) -> Disposable in
observer.onNext(updatedValue)
return Disposables.create()
})
}
Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
updatedValue = updatedValue + 1;
}
let myObservable = Observable<Int>.interval(1, scheduler: MainScheduler.instance)
.publish()
myObservable.connect()
myObservable
.withLatestFrom(observedValue)
.subscribe { (event) in
print(event)
}
We have variable property updatedValue and hot observable myObservable. We also increment value of updatedValue in Timer.scheduledTimer....
Flow here is pretty straight forward. When we subscribe, observedValue gets called, we get onNext from observedValue and then Disposables.create(). Then we print event onNext(0).
As myObservable is based on Observable.interval, same withLatestFrom value gets printed in onNext every second.
Question: Is there a way to print last value of updatedValue every time myObservable emits new event? So instead of 0,0,0... we get 0,1,2...
I'm aware that updatedValue could be declared as BehaviorRelay.
I'm also aware that we could use .map { } to capture self.updatedValue.
But I'm wondering if there is any way to create a Observable wrapper around standard variable property so it calls onNext with most recent value every time trigger sequence sends an event? Without capturing self or changing declaration on updatedValue.
Thanks for any comments and ideas!
RxCocoa has a handy wrapper around KVO. You should be able to use it from .rx extension on NSObject subclasses.
For your issue, I guess you can do something like:
let updatedValueObservable = self.rx.observe(Int.self, "updatedValue")
But I'm wondering if there is any way to create a Observable wrapper around standard variable property so it calls onNext with most recent value every time trigger sequence sends an event? Without capturing self or changing declaration on updatedValue.
The correct answer is, no. There is no way to do anything to updatedValue without involving self. One way of doing it would be with Observable<Int>.interval(1, scheduler: MainScheduler.instance).compactMap { [weak self] _ in self?.updatedValue }.distinctUntilChanged() (Your use of publish and connect is odd and unnecessary,) but that involves self.
Since your property is a value type, the only way to access it is through self, even if Rx wasn't involved at all.

How to conditionally observe and bind with RxSwift

I'm trying to observe a Variable and when some property of this variable fits a condition, I want to make an "observable" API call and bind the results of that call with some UI element. It is working the way I present it here, but I'm having the thought that it could be implemented way better, because now I'm nesting the subscription methods:
self.viewModel.product
.asObservable()
.subscribe { [weak self](refreshProduct) in
self?.tableView.reloadData()
self?.marketProduct.value.marketProduct = refreshProduct.element?.productId
if refreshProduct.element?.stockQuantity != nil {
self?.viewModel.getUserMarketCart()
.map({ (carts) -> Bool in
return carts.cartLines.count > 0
}).bind(onNext: { [weak self](isIncluded) in
self?.footerView.set(buyable: isIncluded)
}).disposed(by: (self?.disposeBag)!)
}
}.disposed(by: disposeBag)
Is there any other way to do this? I can get a filter on the first observable, but I don't understand how I can call the other one and bind it on the UI.
NOTE: I excluded a few other lines of code for code clarity.
A typical solution would be using .switchLatest() as follows.
create *let switchSubject = PublishSubject<Observable<Response>>()"
bind UI to it's latest value: switchSubject.switchLatest().bind(...
update 'switchSubject' with new requests: switchSubject.onNext(newServiceCall)

RxSwift. CombineLatest. Not all observables emitted

Observable.combineLatest(...){...} contains several observables, but some of these observables were not emitted.
combineLatest emits only when all observables in this method were emitted.
How to skip not emitted observables and emit combineLatest?
let tap = firstButton.rx.tap.asObservable().map{ (_) -> Observable<Item> ...}
let textfieldObservable = viewTextField.rx.text.orEmpty.asObservable()
submitButton.rx.tap.withLatestFrom(Observable.combineLatest(textfieldObservable, tap ... )).flatMapLatest({
...
// this method will not be executed without tap on firstButton before tapping on submitButton
}
)
combineLatest uses a closure that takes in as many arguments as it combines observables. So it makes sense it will wait for all the observables it combines to provide a value before it calls its closure.
But if you can find a sain default values for each of the observables provided to combineLatest, you could use startWith(_:) to force them into having an initial value.
This is what the code would look like using nil for item and the empty string for text
let tapObservable: Observable<Item> = // ...
let textField: Observable<String> = // ...
let combined = Observable.combineLatest(
tapObservable.map { /* map everything to optional */ Optional.some($0) }.startWith(nil),
textField.startWith("")
) { item, text in
// combine item and text
}
submitButton.rx.tap.withLatestFrom(combined)

Resources