Question about the conditional sentence of Observable. (RxSwift) - swift

I tried to create a function runsample() that uses multiple observables as below.
If I meet a specific condition in the middle of the stream, I want to start from the beginning of function.
(foo1() in the example below)
In this case, how do I modify the runsample() function?
class SampleClass {
////////////////////////////////
// private
////////////////////////////////
private func foo1() -> Observable<String> {
// Do something
return .just("TEST")
}
private func foo2() -> Observable<Bool> {
// Do something
return .just(false) // or true
}
private func foo3() -> Observable<String> {
// Do something
return .just("Result")
}
////////////////////////////////
// public
////////////////////////////////
public func runSample() -> Observable<String> {
return Observable.just(())
.flatMap { [unowned self] _ in
self.foo1()
}
.flatMap { [unowned self] _ in
self.foo2()
}
// I want to retry foo1() when foo2() is false
// I want to make foo3() run only if foo2() is true.
.flatMap { [unowned self] _ in
self.foo3()
}
}
}

Based on your comment, this is what you want:
func runSample() -> Observable<String> {
struct NotValid: Error { }
return Observable.deferred {
foo1().flatMap { _ in
foo2().do(onNext: { isValid in
if !isValid { throw NotValid() }
})
}
}
.retry()
.flatMap { _ in foo3() }
}
It's a very strange requirement you have, but it's doable. I expect this is an X-Y problem though.
You really want to retry foo1()? That would imply that it failed but it obviously didn't. In any case, this will do what you want:
func runSample() -> Observable<String> {
foo1()
.flatMap { [foo2] _ in
foo2()
}
.flatMap { [foo1, foo3] isTrue in
isTrue ? foo3() : foo1()
}
}
This function will return an Observable. Every time that Observable is subscribed to, the first foo1() will be activated.
Every time the first foo1() emits a value, the value will be ignored (which is quite odd) and foo2() will be called. This will generate a new Observable which will be subscribed to.
Whenever any of the Observables generated by foo2() emit a value, if the value is true foo3() will be called, otherwise foo1() will be called. Whichever one is called, its Observable will be subscribed to.
The entire function will emit all the values that any foo1()s or foo3()s Observables emit.
Importantly for this example, you do not need to start with Observable.just(()).
Thinking about it, I'd prefer something like this:
func runSample() -> Observable<String> {
Observable.zip(foo1(), foo2())
.flatMap { $0.1 ? foo3() : .just($0.0) }
}
That way I don't have to run foo1() twice.

Related

Swift Combine return result of first future after evaluation of second

I have the following situation:
2 futures, one returns a value I am interested in, the other does some operations and returns void. The 2 are not related to each other (so the code should not be mixed), but both need to be executed in the right order in the application logic.
What I want to do is subscribe to a publisher that does the following:
future one executes and gives a value
future two executes and returns nothing
the subscriber receives the value of future one after the execution of future two.
Here is a small code example that does not compile, that shows what I would like to achieve:
import Combine
func voidFuture() -> Future<Void, Error> {
return Future<Void, Error> { promise in
promise(.success(()))
}
}
func intFuture() -> Future<Int, Error> {
return Future<Int, Error> { promise in
promise(.success(1))
}
}
func combinedFuture() -> AnyPublisher<Int, Error> {
var intValue: Int!
return intFuture().flatMap { result in
intValue = result
return voidFuture()
}.flatMap{ _ in
return CurrentValueSubject(intValue).eraseToAnyPublisher()
}.eraseToAnyPublisher()
}
var subscriptions = Set<AnyCancellable>()
combinedFuture()
.sink(receiveCompletion: { _ in }, receiveValue: { val in print(val)})
.store(in: &subscriptions)
You need to .map the Void result of the second publisher (voidFuture) back to the result of the first publisher (intFuture), which is what the .flatMap would emit:
func combinedFuture() -> AnyPublisher<Int, Error> {
intFuture().flatMap { result in
voidFuture().map { _ in result }
}
.eraseToAnyPublisher()
}

Rxswift Map and Zip not Called

I am trying to get the element of 2 observables produced asynchronously and pass them as parameters to a function once both are received.
However my map operator in my ViewModel below is not executed and the breakpoint just skips over it.
ViewModel.swift
init(api: ApiService) {
self.api = api
}
func getData1() -> Observable<Data1> {
return api.getData1()
}
func getData2() -> Observable<NewViewModel> {
return Observable.create { observer in
let disposable = Disposables.create()
self.api.getData2()
.map {
$0.arrayOfStuff.forEach { (stuff) in
let background = stuff.background
let newViewModel = NewViewModel( background: self.spotlightBackground)
observor.onNext(newViewModel)
}
return disposable
}
}
In my ViewController i am creating the Zip of the observables because newViewModel[getData2] may return later and i want to call the function when both observables emit a value
in my viewDidLoad() i setup zip by subscribing and adding observables
let zippy = Observable.zip(viewModel.getData1(), viewModel.getData2()).subscribe(onNext: { (data1, newViewModel) in
self.layoutSetUp(data1: data1, newViewModel: newViewModel)
})
zippy.disposed(by: disposeBag)
private func layoutSetUp(data1: Data1, newViewModel: NewViewModel) {
DispatchQueue.main.async {
self.view = SwiftUIHostingView(rootView: SwiftUIContentView(data1: data1, newViewModel: newViewModel))
}
}
This is not executing and no values are passed to function either and im not sure why
Your getData2 method never emits a value so neither will the zip. The code in the method is a bit too muddled for me to understand what you are trying to do so I can't tell you exactly what you need, but I can say that when you have an observable that nothing is subscribed to, then it will not emit a value.
This bit:
self.api.getData2()
.map {
$0.arrayOfStuff.forEach { (stuff) in
let background = stuff.background
let newViewModel = NewViewModel(background: self.spotlightBackground)
observor.onNext(newViewModel)
}
return disposable
}
Is an observable with no subscribers.

How do I reverse a promise?

I'm using PromiseKit to handle flow through a process.
Prior, I did a similar app without promises but decided frick it I'm gonna try promises just because, well, why not?
So I'm throwing a back button in the mix as I did in the prior app. Only problem is, I'm not exactly sure how to handle "reversing" if you want to call it that.
So say I have a flow of
doSomething().then {
// do something else
}.then {
// do something else
}.done {
// wrap it up, boss
}.catch {
// you're an idiot, bud
}
Say I'm in the first or second part of the chain then and I want to go back up the chain - is this possible?
Is there a link y'all can give me that I can use to read up on how to do that?
I'm thinking I might have to restart the "chain", but then how would I step through the flow....WAIT (light bulb), I can programmatically fulfill the necessary promises with whatever the data is that initially was fulfilled with until I get to the point in the "chain" where I needed to go back to, right?
Advice D:?
You can always have a catch and a then on the same promise.
var somePromise = doSomething()
// first chain
somePromise.catch { error in
// handle error
}
// second chain from the same starting point
somePromise.then {
// do something else
}.then {
// do something else
}.catch {
// you can still catch the error here too
}
You're basically creating two promise chains from the same original promise.
No, you can not do that. Once you commit a promise, you can not reverse that. Because the chain is supposed to finish in the descending order, it's cumbersome to track the order in each .then block.
What you can do is, handle the internal logic responsible to fulfill or reject a promise and start the chain from the beginning.
func executeChain() {
doSomething().then {
// do something else
}.then {
// do something else
}.done {
// condition to
executeChain()
}.catch {
// you're an idiot, bud
}
}
func doSomething() -> Promise<SomeThing>{
if (condition to bypass for reversing) {
return .value(something)
}
// Normal execution
}
But if you can improve your question with an actual use case and code then it could help providing more suitable explanation.
No you can't but you can set order in array.
bar(promises: [foo1(), foo2(), foo3()])
func bar<T>(promises: [Promise<T>]) {
when(fulfilled: promises)
.done { _ in
// TODO
}
.catch { error in
// When get error reverse array and call it again
self.bar(promises: promises.reversed())
}
}
func foo1() -> Promise<Void> {
return Promise { $0.fulfill(()) }
}
func foo2() -> Promise<Void> {
return Promise { $0.fulfill(()) }
}
func foo3() -> Promise<Void> {
return Promise { $0.fulfill(()) }
}
or alternatively
bar(foo1, foo2, foo3)
.done { _ in
// TODO
}
.catch { error in
print(error.localizedDescription)
self.bar(self.foo3, self.foo2, self.foo1)
.done { _ in
// TODO
}
.catch { error2 in
print(error2.localizedDescription)
}
}
func bar<T>(_ promise1: () -> Promise<T>,
_ promise2: #escaping () -> Promise<T>,
_ promise3: #escaping () -> Promise<T>) -> Promise<T> {
return Promise { seal in
promise1()
.then { _ in return promise2() }
.then { _ in return promise3() }
.done { model in
seal.fulfill(model)
}
.catch {
seal.reject($0)
}
}
}
func foo1() -> Promise<Void> {
return Promise { $0.fulfill(()) }
}
func foo2() -> Promise<Void> {
return Promise { $0.fulfill(()) }
}
func foo3() -> Promise<Void> {
return Promise { $0.fulfill(()) }
}

Search a value in Array of Observable

I'm having array of Bool Observables in Rxswift.
let rxBoolObservableArray: [Observable<Bool>] = [Observable<Bool>]()
Now, How to get If any of the element is false?
func containsFalse(array: [Observable<Bool>]) -> Observable<Bool> {
return Observable.combineLatest(array) { $0.contains(false) }
}
The combineLatest function will subscribe to all the observables in the array.
The above will also update the array every time one of the observables updates its value so the output will always be correct. The accepted answer doesn't do that (it only works for the Observable.just function and is incorrect.)
Here is allSatisfy extension based on #DanielT answer. It might be suitable for your problem:
extension Array where Iterator.Element: ObservableType {
func allSatisfy(_ predicate: #escaping (Iterator.Element.E) throws -> Bool) -> Observable<Bool> {
return Observable.combineLatest(self) { try $0.allSatisfy(predicate) }
}
}
example usage:
rxBoolObservableArray
.allSatisfy { $0 } // { $0 == true }
.subscribe(onNext: { areTestsPassing in
print(areTestsPassing)
})
.disposed(by: disposeBag)

How can I know that a completion block didn't executed but method finished?

How can I programmatically catch the case when completion does not execute?
I can not modify exists method, because this is a simple replacer for a Firebase observe.
func exists(completion: (_ a: Int) -> ()) {
//async call with a completion handler where I get the `a` value
if a % 2 == 0 {
completion(a)
}
..............//other cases
}
func check() {
exists { a in
print(a)
}
}
I thought of some flag, but how do I know that exists ended?
There's many ways of doing what you're trying to do, you could set flags (booleans), you could use optionals in the completion closure, you can use two closures...
I'm posting a way which I find the nicest, but it's purely subjective.
You could change the completion closure argument to be a Result enum for example.
enum Result {
case .success(Int)
case .failure
}
Then in the completion closure, you would replace the argument with this enum.
func exists(completion: (_ result: Result) -> ()) {
let a = arc4random()
if a % 2 == 0 {
completion(.success(a))
} else {
//other cases
completion(.failure)
}
}
func check() {
exists { result in
switch result {
case .succeess(let number):
print(number)
case .failure:
print("Finished without number")
}
}
}
Best way to achieve what you want is to use DisptachGroup.
func exists(completion: (_ a: Int) -> ()) {
completion(1)
}
let group = DispatchGroup()
group.enter()
exists { (a) in
group.leave()
}
group.notify(queue: .main) {
print("Did finish 'exists' function!")
}