Why does skipWhile behave differently in these examples? - swift

In the first example, my terminal outputs
next(1)
next(2)
next(3)
next(4)
next(5)
next(6)
next(7)
next(8)
next(9)
next(10)
completed
In the second example it will output
next(3)
next(6)
next(8)
next(9)
completed
I know the values are different in each, but I would expect the first example to have had filtering applied so it follows the logic within my .skipWhile { $0 % 2 == 0 } block
func skipWhile() {
let bag = DisposeBag()
Observable
.from(Array(1...10))
.skipWhile { $0 % 2 == 0 }
.subscribe { print($0) }
.disposed(by: bag)
Observable
.from([2,3,6,8,9])
.skipWhile { $0 % 2 == 0 }
.subscribe { print($0) }
.disposed(by: bag)
}
skipWhile()

skipWhile is not filter. It skips elements at the start of the observable's lifetime while the predicate as true. As soon as an element comes along that no longer satisfies the predicate, it opens the flood gates and let everything else through.
Your first observable says "skip everything until the first odd number". The first element is odd, so nothing is skipped, and that's why you see all array elements being printed.
If you notice in your second observable, you didn't filter out even numbers (because there's an 8). You merely skipped over elements until the first odd number (3), causing the 2 to be skipped.
On a side note
Int.isMultiple(of: ) has been added in Swift 5, and I suggest you use it in cases like this. It just makes it clearer, and side-steps errors caused by misreading == vs !=.
Observable
.from(Array(1...10))
.skipWhile { $0.isMultiple(of: 2) }
.subscribe { print($0) }
.disposed(by: bag)
You could even name your predicate:
let isEven: (Int) -> Bool = { $0.isMultiple(of: 2) }
Observable
.from(Array(1...10))
.skipWhile(isEven)
.subscribe { print($0) }
.disposed(by: bag)
Or my favourite, add it as a computed property:
extension BinaryInteger {
var isEven: Bool { return self.isMultiple(of: 2) }
var isOdd: Bool { return !self.isEven }
}
Observable
.from(Array(1...10))
.skipWhile(\.isEven)
.subscribe { print($0) }
.disposed(by: bag)

Related

How can I convert an element to an array?

The array was converted to Observable using the from operator as shown below.
Observable.from([1, 2, 3, 4, 5])
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
//Result Value
1
2
3
4
5
After that, I want to use several map or flatMap operators to transform, and finally I want to make an array again.
Is there an Rx operator that can create an array without using toArray()?
Observable.from([1, 2, 3, 4, 5])
.map { .... }
.flatMap { .... }
.map { .... }
????? -> convert to Array
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
//Expected Value
[someItem1, someItem2, ..., someItem5]
No, the toArray operator is exactly what you want given that you have used Observable.from. That said, you don't need to break the array up in the first place.
Also, the way you have things now, the return order is not guaranteed. If the various operations in the flatMap return at different times, the resulting array will be in a different order than the source.
If you don't break the array up with from then you won't need to recombine it later with toArray and you won't have the ordering issues.
Observable.just([1, 2, 3, 4, 5])
.map { $0.map { /* transform each value */ } }
.flatMap { Observable.zip($0.map { /* create an Observable using each value */ }) }
.map { $0.map { /* transform each value */ } }
.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)

Subscription call 2 times, how to unsubscribe observable in RxSwift

Reset password call occurs 2 times, how can I remove the subscription in that block.
let digits = [firstDigit, secondDigit, thirdDigit, fourthDigit, fifthDigit, sixthDigit]
let digitsValid = digits.map { $0?.textField.rx.text.orEmpty.map({ $0.count == 1 }).share(replay: 1) }
let allDigitsFilled = Observable.combineLatest(digitsValid.map{ $0 ?? BehaviorRelay(value: false).asObservable()}).share(replay: 1)
allDigitsFilled.subscribe (onNext: { [weak self] (boolArray) in
guard let self = self else { return }
let isFilled = boolArray.allSatisfy({ $0 })
if isFilled {
self.viewModel.resetPassword()
}
}).disposed(by: disposeBag)
Your onNext block will be called every time any of the text fields changes its contents. Your first goal should be to make it so the block is only called when you want to reset the password.
So first, you want to put the .allSatisfy inside an Observable.filter call so your onNext block will only be called when all six text fields contain exactly 1 character. Once you do that, then you can simply use .take(1) which will complete the subscription once a next value is emitted.
Something like this should do it, and looks quite a bit cleaner:
let digits = [firstDigit, secondDigit, thirdDigit, fourthDigit, fifthDigit, sixthDigit]
let texts = digits.compactMap { $0?.textField.rx.text.orEmpty }
Observable.combineLatest(texts)
.filter { $0.allSatisfy { $0.count == 1 } }
.map { _ in }
.take(1)
.subscribe(onNext: { [viewModel] in
viewModel?.resetPassword()
})
.disposed(by: disposeBag)

What is the purpose of .enumerated() and .map() in this code?

I'm working a tutorial from https://www.raywenderlich.com/921-cocoa-bindings-on-macos. I'm wondering what the .enumerated() and .map() functions are operating on in this section:
#IBAction func searchClicked(_ sender: Any) {
guard let resultsNumber = Int(numberResultsComboBox.stringValue)
else {
return
}
iTunesRequestManager.getSearchResults(searchTextField.stringValue, results: resultsNumber, langString: "en_us") { (results, error) in
let itunesResults = results.map {
return Result(dictionary: $0)
}
.enumerated()
.map({ (index, element) -> Result in
element.rank = index + 1
return element
})
DispatchQueue.main.async {
self.searchResultsController.content = itunesResults
print(self.searchResultsController.content!)
}
}
}
I can usually figure out most things eventually in Swift but I'm stumped here and the explanatory text isn't clear to me either. I hope someone can help me understand this part of the tutorial. Thanks!
Map is used for modifications. At this point you are basically initialising an object of Result by giving results array as a param to it:
results.map {
return Result(dictionary: $0)
}
$0 means the first input. In a following case, $0 is equal to param(we just gave it a name):
results.map { param in
return Result(dictionary: param)
}
.enumerated() returns each element of an array with its index number. Without it you would have only the element like this:
.map({ (element) -> Result in
// you don't have `index` value inside the closure anymore
// element.rank = index + 1
return element
})
Note that the element in the closure above is the same Result(dictionary: $0) object that you created in a previous map function.
At the end, you are making and modification by assigning elements index number increased by 1 to the element's rank property, and returning it:
.map({ (index, element) -> Result in
// element.rank = index + 1
return element
})
Note that the value we get after the 3rd step, including all modification is assigned to let itunesResults.

RxSwift How to split progress and result observables?

I need to make a long async calculation based on a String input and return a big Data instance.
I use Single trait to achieve this:
func calculateData(from: String) -> Single<Data>
This example is simple and works. But I also need to track progress — a number between 0 and 1. I'm doing something like this:
func calculateData(from: String) -> Observable<(Float, Data?)>
where I get the following sequence:
next: (0, nil)
next: (0.25, nil)
next: (0.5, nil)
next: (0.75, nil)
next: (1, result data)
complete
I check for progress and data to understand if I have a result, it works, but I feel some strong smell here. I want to separate streams: Observable with progress and Single with a result. I know I can return a tuple or structure with two observables, but I don't like this as well.
How can I achieve this? Is it possible?
What you have is fine although I would name the elements in the tuple
func calculateData(from: String) -> Observable<(percent: Float, data: Data?)>
let result = calculateData(from: myString)
.share()
result
.map { $0.percent }
.subscribe(onNext: { print("percent complete:", $0) }
.disposed(by: disposeBag)
result
.compactMap { $0.data }
.subscribe(onNext: { print("completed data:", $0) }
.disposed(by: disposeBag)
Another option is to use an enum that either returns percent complete OR the data:
enum Progress {
case incomplete(Float)
case complete(Data)
}
func calculateData(from: String) -> Observable<Progress>
However, doing that would make it harder to break the Observable up into two streams. To do so, you would have to extend Progress like so:
extension Progress {
var percent: Float {
switch self {
case .incomplete(let percent):
return percent
case .complete:
return 1
}
}
var data: Data? {
switch self {
case .incomplete:
return nil
case .complete(let data):
return data
}
}
}
And as you see, doing the above essentially turns the enum into the tuple you are already using. The nice thing about this though is that you get a compile time guarantee that if Data emits, the progress will be 1.
If you want the best of both worlds, then use a struct:
struct Progress {
let percent: Float
let data: Data?
init(percent: Float) {
guard 0 <= percent && percent < 1 else { fatalError() }
self.percent = percent
self.data = nil
}
init(data: Data) {
self.percent = 1
self.data = data
}
}
func calculateData(from: String) -> Observable<Progress>
The above provides the compile time guarantee of the enum and the ease of splitting that you get with the tuple. It also provides a run-time guarantee that progress will be 0...1 and if it's 1, then data will exist.

Skip an event from source observable if a new event from a given observable was received in a given time interval

I'm trying to write a method on UIView extension, which will observe long press on a given view. I know it can be done using UILongPressGestureRecognizer, but I really want to figure out the question and do it this way.
I tried to use takeUntil operator, but it completes an observable, but I need to skip the value and receive further events.
The question can be also transformed to: How to omit completed event and keep receiving further events?
func observeLongPress(with minimumPressDuration: Double = 1) ->
Observable<Void> {
let touchesBeganEvent = rx.methodInvoked(#selector(touchesBegan))
let touchesEndedEvents = [#selector(touchesEnded), #selector(touchesCancelled)]
.map(rx.methodInvoked)
let touchesEndedEvent = Observable.merge(touchesEndedEvents)
return touchesBeganEvent
.observeOn(MainScheduler.instance)
.delay(minimumPressDuration, scheduler: MainScheduler.instance)
.takeUntil(touchesEndedEvent)
.map { _ in }
}
This will work, but will complete the whole sequence (as it intended to do).
The answer if floating around (as it always do), but after a few hours I decided to ask. :)
Update
The floating answer just flew inside (~15 mins for doing so), but I'm still interested in answer, because maybe there's something that I'm missing here.
func observeLongPress(with minimumPressDuration: Double = 1) -> Observable<Void> {
let touchesBeganEvent = rx.methodInvoked(#selector(touchesBegan))
let touchesEndedEvents = [#selector(touchesEnded), #selector(touchesCancelled)]
.map(rx.methodInvoked)
let touchesEndedEvent = Observable.merge(touchesEndedEvents)
return touchesBeganEvent
.observeOn(MainScheduler.instance)
.flatMapLatest { _ -> Observable<Void> in
return Observable.just(())
.delay(minimumPressDuration, scheduler: MainScheduler.instance)
.takeUntil(touchesEndedEvent)
.void()
}
}
Your Updated code won't work. Even if you don't emit the completed event out of the function, it still got emitted from the takeUntil and therefore that operator won't emit any more values.
That said, this idea can be accomplished. Since you said you want to learn, I'll talk through my entire thought process while writing this.
First let's outline our inputs and outputs. For inputs we have two Observables, and a duration, and whenever we are dealing with duration we need a scheduler. For outputs we only have a single Observable.
So the function declaration looks like this:
func filterDuration(first: Observable<Void>, second: Observable<Void>, duration: TimeInterval, scheduler: SchedulerType) -> Observable<Void> {
// code goes here.
}
We are going to be comparing the time that the two observables fire so we have to track that:
let firstTime = first.map { scheduler.now }
let secondTime = second.map { scheduler.now }
And since we are comparing them, we have to combine them somehow. We could use merge, combineLatest, or zip...
combineLatest will fire whenever either Observable fires and will give us the latest values from both observables.
zip will mate, 1 for 1, events from both observables. This sounds intriguing, but would break down if one of the observables fires more often than the other so it seems a bit brittle.
merge will fire when either of them fire, so we would need to track which one fired somehow (probably with an enum.)
Let's use combineLatest:
Observable.combineLatest(firstTime, secondTime)
That will give us an Observable<(RxTime, RxTime)> so now we can map over our two times and compare them. The goal here is to return a Bool that is true if the second time is greater than the first time by more than duration.
.map { arg -> Bool in
let (first, second) = arg
let tickDuration = second.timeIntervalSince(first)
return duration <= tickDuration
}
Now the above will fire every time either of the two inputs fire, but we only care about the events that emit true. That calls for filter.
.filter { $0 } // since the wrapped value is a Bool, this will accomplish our goal.
Now our chain is emitting Bools which will always be true but we want a Void. How about we just throw away the value.
.map { _ in }
Putting it all together, we get:
func filterDuration(first: Observable<Void>, second: Observable<Void>, duration: TimeInterval, scheduler: SchedulerType) -> Observable<Void> {
let firstTime = first.map { scheduler.now }
let secondTime = second.map { scheduler.now }
return Observable.combineLatest(firstTime, secondTime)
.map { arg -> Bool in
let (first, second) = arg
let tickDuration = second.timeIntervalSince(first)
return duration <= tickDuration
}
.filter { $0 }
.map { _ in }
}
The above isolates our logic and is, not incidentally, easy to test with RxTests. Now we can wrap it up into a UIView (that would be hard to test.)
func observeLongPress(with minimumPressDuration: Double = 1) -> Observable<Void> {
let touchesBeganEvent = rx.methodInvoked(#selector(touchesBegan)).map { _ in }
let touchesEndedEvents = [#selector(touchesEnded), #selector(touchesCancelled)]
.map(rx.methodInvoked)
let touchesEndedEvent = Observable.merge(touchesEndedEvents).map { _ in }
return filterDuration(first: touchesBeganEvent, second: touchesEndedEvent, duration: minimumPressDuration, scheduler: MainScheduler.instance)
}
There you go. One custom operator.