how to translate an if-else in RxSwift? - swift

I'm trying to learn the library RxSwift
I have some code like this:
if data.checkAllIsOk()
{
[do things]
}
else
{
[show alert]
}
Now i need to update the data from the server before checking, so i have modeled a getData() that return an Observable.
My current approach is this:
getData()
>- flatMap{ (data:Data) -> Observable<Bool> in
_=0 // workaround for type inference bugs
return just(data.checkAllIsOk())
}
>- subscribeNext{ (ok) -> Void in
if ok
{
[do the things]
}
else
{
[show the alert]
}
}
>- disposeBag.addDisposable()
It works (or it should, i'm still writing it), but it feels wrong.. is there a more "reactive" way to do it?
What are the most appropriate operators to use?
Maybe returning an error for “false” and use the catch block?
Update
Following the approach suggested by ssrobbi i splitted the 2 branches in 2 different subscribeNext, and used filter to select the positive or negative branch. This is the code resulting:
let checkData=getData()
>- flatMap{ (data:Data) -> Observable<Bool> in
_=0
return just(data.checkAllIsOk())
}
>- shareReplay(1)
}
[...]
checkData
>- filter{ (ok) -> Bool in
ok == true
}
>- subscribeNext{ (_) -> Void in
[do the things]
}
>- disposeBag.addDisposable()
checkData
>- filter{ (ok) -> Bool in
ok == false
}
>- subscribeNext{ (_) -> Void in
[show the alert]
}
>- disposeBag.addDisposable()
The advantage of this approach is that i can reuse only one of the two branches in other parts of the code, without rewriting the subscribe body (less duplication is always good!)
Update
After some discussions in the RxSwift slack i added the shareReplay(1), so the getData() isn't repeated.

So to be honest I'm still learning as well, and I don't have RxSwift in front of me right now (so someone correct me if I'm spewing BS), but maybe I can give lead you into the right direction.
Your solution does work, but as you said it isn't very "reactive". I think the problem is that you have your data flow set up in a way that it has to make an imperative decision on whether to show an alert or do stuff. What should happen, is that instead of getData returning an observable, getData should get whatever data it needs to (whether it's from a network, core data, etc), and then it should update an observable property.
For the do things:
Now, you would observe that property, map it to check if it's okay, and subscribe to it like you did, check if it's true, and if it is do things. (and add disposable)
For the alert:
You'd do the exact same thing, observing that same property again, but check for the opposite case and do stuff.
I think what's not super reactive about it is that you're synchronously waiting on a response from that getData() function, which creates a scenario where you now have state, whether to show an alert, or do that extra work. They're not derived from the value stream of some other property. Showing the alert and doing things are only related to each other because you set up your code imperatively.
EDIT: Instead of checking with an if statement whether or not that's true, perhaps you could put it through a filter before subscribing instead.

I don't know RXSwift, but I do know functional programming (RXSwift is functional as well). An if else statement is already on its lowest form and you don't need to do functional branching, which would work but it makes it less readable.
You could change your if else to condition ? a : b if you want to be more functional (Haskell's if else is exactly that). But that makes it less readable as well, I would just stick to what you got ;)

getData() should return an Observable<Data> and the contained data should already be ok. In other words, if getData() is implemented correctly, then calling data.checkAllIsOk() on the data pushed out of the observable should always return true.
So what you should have outside of getData() is something like (in Rx v2 with Swift v2):
getData().subscribe { event in
switch event {
case .Next(let data):
[do things with data]
case .Error(let error):
[show the alert]
}

The problem with your updated approach is that checkData is an Observable<Bool>, so you can't really "do stuff" with it.
I think what you want is this (decomposed for clarity):
func isDataOk(_ data: Data) -> Bool { /* ... */ }
let data = getData().shareReplay(1)
let goodData = data.filter(isDataOk)
let badData = data.filter{ isDataOk($0) == false }
goodData
.subscribe( /* do stuff */ )
.addDisposableTo(disposeBag)
badData
.subscribe( /* show alert */ )
.addDisposableTo(disposeBag)
But I agree with Daniel in that this is a great opportunity to use an observable that errors. Something like this:
func passOrThrow(_ data: Data) throws -> Data { /* throw an error here if data is bad */ }
getData()
.map(passOrThrow)
.subscribe(onNext: { data in
// do the things
}, onError: { error in
// show the alert
}).addDisposableTo(disposeBag)

Related

What is the right approach for resolving save operations with Swift Combine

I have a classic situation where I want to commit some action only once. For example update some entity in database using Swift Combine. My problem is that I don't really know what is the best approach for doing something only once. How do I unsubscribe when the update is finished?
This is code snippet through the layers that I am currently using:
ViewModel:
let settingsModel: LocalSettingsModel
func saveLocalSettings(){
let cancelable = settingsUseCase
.saveLocalSettings(localSettingsModel: settingsModel)
.sink(receiveCompletion: {_ in
print("Completed!!!")
}) { _ in
print("Result of Save operation!!!")
}
}
UseCase:
func saveLocalSettings(settings: LocalSettingsModel) -> AnyPublisher<LocalSettingsModel, Error> {
return repository.saveLocalSettings(settings: settings)
}
Repository:
guard let realmSettings = LocalSettingsRealmModel(fromModel: settings) else {
return Fail<LocalSettingsModel, Error>(error: .postconditionError(errorMessage: ""))
.eraseToAnyPublisher()
}
return self.localDataSource
.saveLocalSettings(localSettings: realmSettings)
.receive(on: DispatchQueue.main)
.subscribe(on: DispatchQueue.global())
.mapError { (error) -> Error in
// do error mapping
}
.compactMap { settings in
return (LocalSettingsModel(fromModel: settings))
}
.eraseToAnyPublisher()
Data Source:
func saveLocalSettings(localSettings: LocalSettingsRealmModel) -> AnyPublisher<LocalSettingsRealmModel, LocalDataSourceError> {
do {
return Just(try saveSettings(localSettings: localSettings))
.mapError({ (Never) -> LocalDataSourceError in})
.eraseToAnyPublisher()
} catch let error as NSError {
// return some error
}
}
func saveSettings(localSettings: LocalSettingsRealmModel) throws -> LocalSettingsRealmModel
{
let realm = try Realm()
try realm.write {
realm.add(localSettings, update: .modified)
}
return localSettings
}
I would really appreciate some pointers in the direction of what is a good practice when we are not expecting continuous stream of information in reactive world like in case of functions whose purpose is to execute single action. Do I really need to use Just() like in this example or is there a way do deal with this kind of situations from subscriber side.
You want something that will convert a Publisher into a single value and then terminate, and the sequence operators in Combine are what you want to use for that kind of thing.
Combine is set up to deal with one OR many values. So you, as the consumer, need to give it some way to constrain the potentially many values into a single value, if you want to use an assign subscriber to set a value (or sink subscriber to invoke a closure where you do your save, in your case).
The sequence operators in Combine is where I'd think to look, but I can't really describe which one without knowing how many values and how you'd choose which one to apply. The two "easy" options are either first or last, but there's a variety of sequence operators that let you construct more complicated choices (including firstWhere and lastWhere which let you determine based on your own closure, which can be darned handy.
The embedded links are all to the online/free-version of Using Combine (disclosure: which I wrote) - and while I don't have any explicit examples about the sequence operators, I did flesh out the reference details for them in the book quite a bit.
Unless you're explicitly working from a publisher, you may find it easier to use a Promise library - depending on what's triggering your save. I don't know the Realm end of this to know what their API focuses on, and if you've made the publisher that's generating the data, or if that's coming from their API - and hence your desire to using Combine to solve this.

How to generalize form inputs from properties using functional reactiveSwift?

When doing forms with fields i want to send if there is a change i often do
let initialOrChangedName = Signal.merge(
nameChanged.signal,
self.viewDidLoadProperty.signal
.map { _ in nil }
)
where
private let nameChangedProperty = MutableProperty<String?>(nil)
private let viewDidLoadProperty = MutableProperty(())
to get a signal that has fired once on load, so i can use it in a combineLatest when user taps a button that will fire a web request with the form value to server. Since this signal merges it will give all values that change after the initial value, allowing me to send the newest value when user taps the submit button
Usage for this is usually something like
Signal.combineLatest(intialOrChangedName, initialOrChangedAge)
.sample(on:sendButtonTappedProperty.signal)
if values sent nil, i just dont include them in the web request, but i do get the other values if some of them was changed by user.
Since this is a fairly common pattern, i want to generalize it to a single function, for example
let initialOrChangedName = nameChanged.initialOrChangedState(on: viewDidLoadProperty)
I've tried writing it
extension MutableProperty where Value: OptionalProtocol {
public func initialOrChangedState(on viewDidLoadProperty: MutableProperty<Void>) -> Signal<Value?, Error> {
return Signal.merge(self.signal.map(Optional.init),
viewDidLoadProperty.signal.map { _ in nil})
}
}
Which looks good on paper, but will return String?? for the example given, and does not work.
I've also tried writing it as a static function on Signal, but with no luck.
I guess it should be something like this:
extension MutableProperty where Value: OptionalProtocol {
public func initialOrChangedState(on viewDidLoadProperty: MutableProperty<Void>) -> Signal<Value?, Error> {
return self.signal.map({Optional<Value>($0)}).merge(with: viewDidLoadProperty.signal.map({_ in nil}))
}
}
But, whats the point of using viewDidLoadProperty? Actually, if you subscribe to your signal at the end of viewDidLoad(), you don't even need such a property and as a result you wont need that merge() thing and you wont need to extend MutableProperty protocol.
So, all you need to do is something like:
submitButton.reactive.controlEvents(.touchUpInside).observer(on: UIScheduler()).observeValues({ _ in
readInputs()
launchRequest()
})
I might be misunderstanding so forgive me, if I am. But I think something like this might help. I have this method in a library I’ve written.
public func to<T>(_ value: T) -> Signal<T, Error> {
self.map { _ in value }
}
which allows you to do something like this
let voidProperty = MutableProperty(())
let nilStringSignal: Signal<String?, Never> = voidProperty.signal.to(nil)
So then maybe your case could be something like this, which leans a bit on type inference
nameChanged.signal.merge(with: self.viewDidLoadProperty.signal.to(nil))
I know maybe that’s not quite as concise as you want. Working with generics like optionals in signals can sometimes make the type wrangling a bit frustrating 😅

RxSwift: Nested Queries and ReplaySubject

I have to fetch three types of data (AType, BType, CType) using three separate API requests. The objects returned by the APIs are related by one-to-many:
1 AType object is parent of N BType objects
1 BType object is parent of P CType objects)
I'm using the following three functions to fetch each type:
func get_A_objects() -> Observable<AType> { /* code here */ }
func get_B_objects(a_parentid:Int) -> Observable<BType> { /* code here */}
func get_C_objects(b_parentid:Int) -> Observable<CType> { /* code here */}
and to avoid nested subscriptions, these three functions are chained using flatMap:
func getAll() -> Observable<CType> {
return self.get_A_objects()
.flatMap { (aa:AType) in return get_B_objects(aa.id) }
.flatMap { (bb:BType) in return get_C_objects(bb.id) }
}
func setup() {
self.getAll().subscribeNext { _ in
print ("One more item fetched")
}
}
The above code works fine, when there are M objects of AType, I could see the text "One more item fetched" printed MxNxP times.
I'd like to setup the getAll() function to deliver status updates throughout the chain using ReplaySubject<String>. My initial thought is to write something like:
func getAll() -> ReplaySubject<String> {
let msg = ReplaySubject<String>.createUnbounded()
self.get_A_objects().doOnNext { aobj in msg.onNext ("Fetching A \(aobj)") }
.flatMap { (aa:AType) in
return get_B_objects(aa.id).doOnNext { bobj in msg.onNext ("Fetching B \(bobj)") }
}
.flatMap { (bb:BType) in
return get_C_objects(bb.id).doOnNext { cobj in msg.onNext ("Fetching C \(cobj)") }
}
return msg
}
but this attempt failed, i.e., the following print() does not print anything.
getAll().subscribeNext {
print ($0)
}
How should I rewrite my logic?
Problem
It's because you're not retaining your Disposables, so they're being deallocated immediately, and thus do nothing.
In getAll, you create an Observable<AType> via get_A_objects(), yet it is not added to a DisposeBag. When it goes out of scope (at the end of the func), it will be deallocated. So { aobj in msg.onNext ("Fetching A \(aobj)") } will never happen (or at least isn't likely to, if it's async).
Also, you aren't retaining the ReplaySubject<String> returned from getAll().subscribeNext either. So for the same reason, this would also be a deal-breaker.
Solution
Since you want two Observables: one for the actual final results (Observable<CType>), and one for the progress status (ReplaySubject<String>), you should return both from your getAll() function, so that both can be "owned", and their lifetime managed.
func getAll() -> (Observable<CType>, ReplaySubject<String>) {
let progress = ReplaySubject<String>.createUnbounded()
let results = self.get_A_objects()......
return (results, progress)
}
let (results, progress) = getAll()
progress
.subscribeNext {
print ($0)
}
.addDisposableTo(disposeBag)
results
.subscribeNext {
print ($0)
}
.addDisposableTo(disposeBag)
Some notes:
You shouldn't need to use createUnbounded, which could be dangerous if you aren't careful.
You probably don't really want to use ReplaySubject at all, since it would be a lie to say that you're "fetching" something later if someone subscribes after, and gets an old progress status message. Consider using PublishSubject.
If you follow the above recommendation, then you just need to make sure that you subscribe to progress before results to be sure that you don't miss any progress status messages, since the output won't be buffered anymore.
Also, just my opinion, but I would re-word "Fetching X Y" to something else, since you aren't "fetching", but you have already "fetched" it.

Proper way to dispose of a disposable within an observable

I have an HTTPService which returns an Observable<NSData>. My goal is to compose that service into another service, ServiceA which transforms that data for my use case. Using Observable.create in RxSwift 2.0.0-rc.0 in ServiceA it's straight forward enough. My question is how to properly handle the disposable returned from the subscription of the HTTPService.
If I don't do anything I get the compile time warning that the result of call is unused: http://git.io/rxs.ud. I understand from reading that if I do nothing it's likely ok: (where xs mentioned below is let xs: Observable<E> ....
In case xs terminates in a predictable way with Completed or Error message, not handling subscription Disposable won't leak any resources, but it's still preferred way because in that way element computation is terminated at predictable moment.
So here is how I am currently addressing it, and also where I am wondering if I am doing this properly or if I have misunderstood something.
public struct ServiceA{
public static func changes() -> Observable<ChangeSet>{
return Observable.create{ observable in
// return's Observable<NSData>
let request = HTTPService.get("https://httpbin.org/get")
let disposable = request.subscribe(
onNext: { data in
// Do more work to transform this data
// into something meaningful for the application.
// For example purposes just use an empty object
observable.onNext(ChangeSet())
observable.onCompleted()
},
onError:{ error in
observable.onError(error)
})
// Is this the right way to deal with the
// disposable from the subscription in this situation?
return AnonymousDisposable{
disposable.dispose()
}
}
}
}
As documentation says
subscribe function returns a subscription Disposable that can be used to cancel computation and free resources.
Preferred way of terminating these fluent calls is by using
.addDisposableTo(disposeBag) or in some equivalent way.
When disposeBag gets deallocated, subscription will be automatically
disposed.
Actually your example looks fine in terms of rules, but it loos pretty bad ;) (Also it would be ok, if you would just return this disposable) :
public static func changes() -> Observable<ChangeSet>{
return Observable.create{ observable in
// return's Observable<NSData>
let request = HTTPService.get("https://httpbin.org/get")
return request.subscribe(
onNext: { data in
// Do more work to transform this data
// into something meaningful for the application.
// For example purposes just use an empty object
observable.onNext(ChangeSet())
observable.onCompleted()
},
onError:{ error in
observable.onError(error)
})
}
But as you you returning Observeble I wonder, why you dont just use map operator ?
In your example it would be something like this:
public static func changes() -> Observable<ChangeSet> {
return HTTPService.get("https://httpbin.org/get")
.map(ChangeSet.init)
}

Combining two Observable<Void>s

I'm still a reactive newbie and I'm looking for help.
func doA() -> Observable<Void>
func doB() -> Observable<Void>
enum Result {
case Success
case BFailed
}
func doIt() -> Observable<Result> {
// start both doA and doB.
// If both complete then emit .Success and complete
// If doA completes, but doB errors emit .BFailed and complete
// If both error then error
}
The above is what I think I want... The initial functions doA() and doB() wrap network calls so they will both emit one signal and then Complete (or Error without emitting any Next events.) If doA() completes but doB() errors, I want doIt() to emit .BFailed and then complete.
It feels like I should be using zip or combineLatest but I'm not sure how to know which sequence failed if I do that. I'm also pretty sure that catchError is part of the solution, but I'm not sure exactly where to put it.
--
As I'm thinking about it, I'm okay with the calls happening sequentially. That might even be better...
IE:
Start doA()
if it completes start doB()
if it completes emit .Success
else emit .BFailed.
else forward the error.
Thanks for any help.
I believe .flatMapLatest() is what you're looking for, chaining your observable requests.
doFirst()
.flatMapLatest({ [weak self] (firstResult) -> Observable<Result> in
// Assuming this doesn't fail and returns result on main scheduler,
// otherwise `catchError` and `observeOn(MainScheduler.instance)` can be used to correct this
// ...
// do something with result #1
// ...
return self?.doSecond()
}).subscribeNext { [weak self] (secondResult) -> Void in
// ...
// do something with result #2
// ...
}.addDisposableTo(disposeBag)
And here is .flatMapLatest() doc in RxSwift.
Projects each element of an observable sequence into a new sequence of observable sequences and then
transforms an observable sequence of observable sequences into an observable sequence producing values only from the most recent observable sequence. It is a combination of map + switchLatest operator.
I apologize that I don't know the syntax for swift, so I'm writing the answer in c#. The code should be directly translatable.
var query =
doA
.Materialize()
.Zip(doB.Materialize(), (ma, mb) => new { ma, mb })
.Select(x =>
x.ma.Kind == NotificationKind.OnError
|| x.mb.Kind == NotificationKind.OnError
? Result.BFailed
: Result.Success);
Basically the .Materialize() operator turns the OnNext, OnError, and OnCompleted notifications for an observable of type T into OnNext notifications for an observable of type Notification<T>. You can then .Zip(...) these and check for your required conditions.
I've learned RxSwift well enough to answer this question now...
func doIt() -> Observable<Result> {
Observable.zip(
doA().map { Result.Success },
doB().map { Result.Success }
.catch { _ in Observable.just(Result.BFailed) }
) { $1 }
}