How to implement the logic without state variables - reactive-programming

There are two observables: the first named activator emits booleans. The second named signaler emits void events. There's a function f() which must be called under the next conditions:
If the last event from activator is true, and event from signaler comes, call f(). Otherwise (the last activator's event is false, or activator has not yet emitted anything), "remember" that signaler sent the event. As soon as activator emits true, call f() and clear "remembered" flag.
Example:
let activator = PublishRelay<Bool>()
let signaler = PublishRelay<Void>()
signaler.accept(()) // activator not emitted yet, just remember that signal came
activator.accept(true) // go to active state. signal is waiting. call f()
signaler.accept(()) // already activated. call f()
activator.accept(false)// go to inactive state
activator.accept(true) // go to active state.
signaler.accept(()) // call f()
activator.accept(false)// go to inactive state
signaler.accept(()) // inactive state, remember that signal came
signaler.accept(()) // still inactive state, remember that signal came
activator.accept(true) // go to active state. there is signal waiting. call f().
signaler.accept(()) // active state. call f().
I can achieve the desired behaviour using two state variables _isActive and _waiting:
var _isActive = false
var _waiting = false
activator.bind { isActive in
self._isActive = isActive
if isActive && self._waiting {
f()
self._waiting = false
}
}.disposed(by: _bag)
signaler.bind {
if self._isActive {
f()
} else {
self._waiting = true
}
}.disposed(by: _bag)
The question is: can I implement it without state variables, only by means of reactive operators?

You need a state machine, but you can contain the state so you aren't leaving the monad... Something like this:
func example(activator: Observable<Bool>, signaler: Observable<Void>) -> Observable<Void> {
enum Action {
case signal
case active(Bool)
}
return Observable.merge(signaler.map(to: Action.signal), activator.map(Action.active))
.scan((isWaiting: false, isActive: false, fire: Void?.none)) { state, action in
switch action {
case .signal:
if state.isActive {
return (state.isWaiting, state.isActive, ())
}
else {
return (true, state.isActive, .none)
}
case .active(let active):
if active && state.isWaiting {
return (false, active, ())
}
else {
return (state.isWaiting, active, .none)
}
}
}
.compactMap { $0.fire }
}
Note how the logic inside the scan closure is the same as the external logic you already have. With the above, you can now do something like this:
let activator = PublishRelay<Bool>()
let signaler = PublishRelay<Void>()
example(
activator: activator.asObservable(),
signaler: signaler.asObservable()
)
.bind(onNext: f)
Lastly, as a bonus. Here's a unit test proving it works:
class RxSandboxTests: XCTestCase {
func test() {
let scheduler = TestScheduler(initialClock: 0)
let activator = scheduler.createColdObservable([.next(20, true), .next(40, false), .next(50, true), .next(70, false), .next(100, true)])
let signaler = scheduler.createColdObservable([.next(10, ()), .next(30, ()), .next(60, ()), .next(80, ()), .next(90, ()), .next(110, ())])
let result = scheduler.start {
example(activator: activator.asObservable(), signaler: signaler.asObservable())
}
XCTAssertEqual(result.events.map { $0.time }, [220, 230, 260, 300, 310])
}
}

Related

Coredata/swift : hasChanges not as expected

This function is not acting as expected. I'm trying to nil a set of fields.The earlier section gets the correct field names, and is used in other functions. I've got about ten tables, and they all share the same context, in case that matters.
The first unexpected thing is that "yes, changes" never runs, so I presume that the settings object is detached from its context. Or perhaps CoreData treats nil as some kind of exception to triggering the .hasChanges flag?
When it runs, the save throws no errors, and the object displays as expected, displayed with the values set to nil. But there are no changes in the db.
I can save data into these fields without problem, and confirm that in the db; this problem only happens with setting the value to nil.
static func clearSettings(_ s : Set<PaletteElementType>? = nil) {
guard var setting = activeSetting() else {return}
print(setting.id)
let cats = s ?? PaletteView.compCatButtons
let tgts = setting.getAssociatedFieldNames(tgts: cats, clearing : true, values: false)
for (key, val) in tgts {
var src = Set((val as? Dictionary<FieldNameSuffixes, String>)!.values)
if key == .catBgndButton {
src = src.union(["opacity", "opacityStart", "opacityStartDelta","opacityEnd", "opacityEndDelta", "opacityTimer"])
}
for s in src {
print(s)
setting.setNilValueForKey(s)
if Blocks.context!.hasChanges {
print("yes, changes")
}
do {
try Blocks.context!.save()
print("deleted \(setting.value(forKey: s))")
} catch { print("deadly dogs")}
}
print("val is \(setting)")
}
}
OK, working when I do it this way:
static func clearSettings(_ s : Set<PaletteElementType>? = nil) {
guard var setting = activeSetting() else {return}
print(setting.id)
let cats = s ?? PaletteView.compCatButtons
let tgts = setting.getAssociatedFieldNames(tgts: cats, clearing : true, values: false)
for (key, val) in tgts {
var src = Set((val as? Dictionary<FieldNameSuffixes, String>)!.values)
if key == .catBgndButton {
src = src.union(["opacity", "opacityStart", "opacityStartDelta","opacityEnd", "opacityEndDelta", "opacityTimer"])
}
for n in setting.entity.attributesByName.enumerated() {
if src.contains( n.element.key as String) {
print("found one")
setting.setNilValueForKey(n.element.key)
}
}
do {
try Blocks.context!.save()
} catch {print("bumpy beasts")}
print("val is \(setting)")
}
}
Happy it's working, but I don't really understand the distinction here. What is the better way to handle this? I'm not chasing some super performant code, so I don't mind a few extra loops... but what's the deal?

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.

Is there a way to make a signal similar to combineLatest without needing all the signals to initially fire?

I have an array of signals
var signals = [Signal<ActionResult?, NoError>]()
where
enum ActionResult
case failed
case pending
case completed
}
I want to create a combined signal that returns true if one or more of the signals fires a .pending
let doesAnyOfTheActionsLoad = Signal.combineLatest(signals).map { values in
values.reduce(false, { (result, nextResult) -> Bool in
if result == true { return true }
if case .pending? = nextResult {
return true
}
return false
})
}
My only problem is that the combineLatest will only fire if all signals have fired at least once, and i need my signal to fire regardless if all signals have fired. Is there a way to do this in ReactiveSwift?
Try this:
let doesAnyOfTheActionsLoad = Signal.merge(signals).map { $0 == .pending}
If you want the signal to stay true after one .pending, then you need to store the current state with something like the scan operator:
let doesAnyOfTheActionsLoad = Signal.merge(signals).scan(false) { state, next in
if case .pending? = next {
return true
}
else {
return state
}
}
scan is like the "live" reactive version of reduce; it sends along the current result each time a new value comes in and is accumulated.
The other solutions are technically correct but I thought this might fit your use case better.
// Only ever produces either a single `true` or a single `false`.
let doesAnyOfTheActionsLoad =
SignalProducer<Bool, NoError>
.init(signals)
.flatten(.merge) // Merge the signals together into a single signal.
.skipNil() // Discard `nil` values.
.map { $0 == .pending } // Convert every value to a bool representing whether that value is `.pending`.
.filter { $0 } // Filter out `false`.
.concat(value: false) // If all signals complete without going to `.pending`, send a `false`.
.take(first: 1) // Only take one value (so we avoid the concatted value in the case that something loads).

XCTWaiter Expectation not firing

I'm using XCTWaiter to wait for certain conditions in a UI automation setup. This is my custom waitFor method:
// UITools.swift
public class func waitFor(_ element:Any, timeout:UInt, clause:String) -> Bool
{
let predicate = NSPredicate(format: clause)
let expectation = testcase.expectation(for: predicate, evaluatedWith: element)
print("Waiting for \(element) to become \"\(clause)\" within \(timeout) seconds ...")
let result = XCTWaiter.wait(for: [expectation], timeout: TimeInterval(timeout))
switch result
{
case .completed:
return true
case .invertedFulfillment:
print("waitFor result is in inverted fulfillment.")
return true
case .timedOut:
print("waitFor result is timed out.")
case .incorrectOrder:
print("waitFor result is in incorrect order.")
case .interrupted:
print("waitFor result is interrupted.")
}
return false
}
This method works fine in cases where I wait for XCUIElements but I have a case where I want to wait for a network request to finish so I use a flag that is set to true once the network request is finished. Here's a simplified example:
class Hub : NSObject
{
var isTestRailCasesRetrived = false
func retrieveTestRailCaseData()
{
isTestRailCasesRetrived = false
testrailClient.getTestCases()
{
(response:TestRailModel) in
// Do processing here ...
print("Found \(totalCases) TestRail cases for suite with ID \(suite.id).")
self.isTestRailCasesRetrived = true
}
UITools.waitFor(self, timeout: 30, clause: "isTestRailCasesRetrived == true")
}
}
However, the XCTWaiter never reaches a complete and just times out after the timeout. It seems that isTestRailCasesRetrived is never evaluated in this situation. Does somebody know why?
Found a workaround. This method works successfully in this particular case where above code didn't. It blocks XCUITest code execution until this wait loop is finished ...
/// Ass-simple brute force wait method for when nothing else works.
///
public class func waitUntil(timeout:Int = 30, evalblock:#escaping (() -> Bool))
{
var count = 0
repeat
{
if count >= timeout || evalblock() == true
{
break
}
else
{
Darwin.sleep(1)
}
count += 1
}
while true
}
NSPredicate works with Objective-C runtime and a properties or functions evaluated by it, must be marked with #objc attribute.
Solution:
class Hub : NSObject
{
#objc var isTestRailCasesRetrived = false
func retrieveTestRailCaseData()
{
isTestRailCasesRetrived = false
testrailClient.getTestCases()
{
(response:TestRailModel) in
// Do processing here ...
print("Found \(totalCases) TestRail cases for suite with ID \(suite.id).")
self.isTestRailCasesRetrived = true
}
UITools.waitFor(self, timeout: 30, clause: "isTestRailCasesRetrived == true")
}
}

How do I emulate a computed property with access to the latest value in RxSwift?

If I want to emulate a standard property of e.g a Bool in RxSwift I can use let isValid = Variable<Bool>(false) and then use .value to get the last value inline and .asObservable() to access the stream.
However I want to emulate a computed propery e.g. var isValid { return self.password.characters.count > 0 } and also be able to get the last value inline as well as in the form of an observable stream.
I want to be able to do both so I can write code like ...
if isValid.value { // isValid is Variable<Bool>
// ... do something ....
}
as well as bind to e.g. a TextField
I know I can write as a pure Observable as follows ...
var isValid: Observable<Bool> {
return self.username.asObservable().map { username in // username is Variable<String>
return username.characters.count > 0
}
}
but then I have to refactor the previous example to be ....
isValid.subscribe { isValid in
if isValid.element {
// ... do something ....
}
}.dispose(of: self.disposeBag)
How then do I express a computed property in RxSwift which can be consumed as an inline value as well as a stream?
I had the same problem and ended up with a solution that does not look clean but works. I would add a new var valid: Bool = false and a subscription to the isValid observable that updates it
isValid.subscribe(onNext:{ [weak self] value in self?.valid = valid})
With this isValid is used for binding and subscriptions and valid for use in imperative code.
Btw your definition of isValid is kind of "unnatural", just make it a let isValid: Observable<Bool> and assign it in init like isValid = username.asObservable().map({$0.characters.count > 0})
Update:
Another solution is using RxBlockling, you can get the current value with try! isValid.toBlocking().single() but I think it is a worse solution and generally not recommended.
Try this:
let username = Variable("")
private let _isValid: Observable<Bool> = username.asObservable().map{ $0.characters.count > 0 }
let isValid = Variable(false)
Then, in your init, viewDidLoador wherever:
_isValid.asObservable()
.bind(to: isValid)
.disposed(by: bag)
You can test it with:
isValid.asObservable()
.subscribe(onNext: {
print("value: \($0)")
})
.disposed(by: disposeBag)
username.value = "xfreire"
// OUTPUTS:
// value: false
// value: true
and you are still able to do:
if isValid.value {
// ...
}