I have an observable that I only want to kick off once. The docs say:
Using dispose bags or takeUntil operator is a robust way of making sure resources are cleaned up. We recommend using them in production even if the sequences will terminate in finite time.
My observable terminates after just one event
let observable = Observable.create() { observer in
webservice.makeHTTPRequestWithCompletionBlock {
if something {
observer.on(.Next(...))
observer.onCompleted()
} else {
observer.on(.Error(...))
}
}
}
Say I wasn't interested in cancelling subscribers to this observable, I just want it run once and complete. I want the lifecycle of this observable to end when the work itself is completed. Meaning there are no good candidates for disposeBag that I can see. takeUntil also expects an 'event', and there are no good ones that I can see.
Right now I just solve the warning by throwing away the disposable:
_ = observeable.subscribeNext { ... }
Is there a way to do this, or a different paradigm that I should use?
Both DiposeBag and takeUntil are used to cancel a subscription prior to receiving the .Completed/.Error event.
When an Observable completes, all the resources used to manage subscription are disposed of automatically.
As of RxSwift 2.2, You can witness an example of implementation for this behavior in AnonymousObservable.swift
func on(event: Event<E>) {
switch event {
case .Next:
if _isStopped == 1 {
return
}
forwardOn(event)
case .Error, .Completed:
if AtomicCompareAndSwap(0, 1, &_isStopped) {
forwardOn(event)
dispose()
}
}
}
See how AnonymousObservableSink calls dispose on itself when receiving either an .Error or a .Completed event, after forwarding the event.
In conclusion, for this use case, _ = is the way to go.
Related
Suppose you have a method that executes asynchronously in a global context. Depending on the execution you need to update the UI.
private func fetchUser() async {
do {
let user = try await authService.fetchCurrentUser()
view.setUser(user)
} catch {
if let error = error {
view.showError(message: error.message)
}
}
}
Where is the correct place to switch to the main thread?
Assign #MainActor to the fetchUser() method:
#MainActor
private func fetchUser() async {
...
}
Assign #MainActor to the setUser(_ user: User) and showError(message: String) view's methods:
class SomePresenter {
private func fetchUser() async {
do {
let user = try await authService.fetchCurrentUser()
await view.setUser(user)
} catch {
if let error = error {
await view.showError(message: error.message)
}
}
}
}
class SomeViewController: UIViewController {
#MainActor
func setUser(_ user: User) {
...
}
#MainActor
func showError(message: String) {
...
}
}
Do not assign #MainActor. Use await MainActor.run or Task with #MainActor instead to run setUser(_ user: User) and showError(message: String) on the main thread (like DispatchQueue.main.async):
private func fetchUser() async {
do {
let user = try await authService.fetchCurrentUser()
await MainActor.run {
view.setUser(user)
}
} catch {
if let error = error {
await MainActor.run {
view.showError(message: error.message)
}
}
}
}
Option 2 is logical, as you are letting functions that must run on the main queue, declare themselves as such. Then the compiler can warn you if you incorrectly call them. Even simpler, you can declare the class that has these functions to be #MainActor, itself, and then you don't have to declare the individual functions as such. E.g., because a well-designed view or view controller limits itself to just view-related code, it is safe for that whole class to be declared as #MainActor and be done with it.
Option 3 (in lieu of option 2) is brittle, requiring the app developer to have to remember to manually run them on the main actor. You lose compile-time warnings should you fail to do the right thing. Compile-time warnings are always good. But WWDC 2021 video Swift concurrency: Update a sample app points out that even if you adopt option 2, you might still use MainActor.run if you need to call a series of MainActor methods and you might not want to incur the overhead of awaiting one call after another, but rather wrap the group of main actor functions in a single MainActor.run block. (But you might still consider doing this in conjunction with option 2, not in lieu of it.)
In the abstract, option 1 is arguably a bit heavy-handed, designating a function that does not necessarily have to run on the main actor to do so. You should only use the main actor where it is explicitly needed/desired. That having been said, in practice, I have found that there is often utility in having presenters (or controllers or view models or whatever pattern you adopt) run on the main actor, too. This is especially true if you have, for example, synchronous UITableViewDataSource or UICollectionViewDataSource methods grabbing model data from the presenter. If you have the relevant presenter using a different actor, you cannot always return to the data source synchronously. So you might have your presenter methods running on the main actor, too. Again, this is best considered in conjunction with option 2, not in lieu of it.
So, in short, option 2 is prudent, but is often married with options 1 and 3 as appropriate. Routines that must run on the main actor should be designated as such, rather than placing that burden on the caller.
The aforementioned Swift concurrency: Update a sample app covers many of these practical considerations and is worth watching if you have not already.
Say in a composable I have two states:
var stateA by remember { mutableStateOf(varA) }
var stateB by remember { mutableStateOf(varB) }
varA and varB are class variables of type Int and are set elsewhere in the code.
Then somewhere in the composable, in the same scope, I have
processA(stateA)
processB(stateB)
processA and processB are not composable functions.
So after initial rendering, if neither state changes, then nothing is further processed, that is cool.
Then if say stateB is changed, then both process statements get called. But I hope only to call processB in the case and not processA. How can I detect which of the states has changed?
You should not run heavy processing directly from Composable functions. More information can be found in side effects documentation.
In this case, you can use LaunchedEffect. Using snapshotFlow, you can create a flow that emits values every time a state changes, so you can process it. You can have a flow for each state, so they will be processed independently.
LaunchedEffect(Unit) {
launch {
snapshotFlow { stateA }
.collect(::processA)
}
launch {
snapshotFlow { stateB }
.collect(::processB)
}
}
I am trying to make several API calls and populate a Realm Database.
Everything works fine. However when I try to run performSegue() on subscribe() method an exception is raised, informing that I can't do this on a background thread, which is perfectly reasonable.
But since I am subscribing to MainScheduler.instance shouldn't the subscribe() method run on UI Thread?
Single.zip(APIClient.shared.getSchools(), APIClient.shared.getPointsOfInterest())
.observeOn(SerialDispatchQueueScheduler(qos: .background))
.flatMap { zip in return Single.zip(SchoolDao.shared.insertSchools(schoolsJson: zip.0), PointOfInterestDao.shared.insertPointsOfInterest(poisJson: zip.1))}
.flatMap{ _ in Single.zip(SchoolDao.shared.countSchools(), PointOfInterestDao.shared.countPointsOfInterest())}
.subscribeOn(MainScheduler.instance)
.subscribe(onSuccess: { tableCounts in
let (schoolsCount, poisCount) = tableCounts
if(schoolsCount != 0 && poisCount != 0){
print(Thread.isMainThread) //Prints False
self.performSegue(withIdentifier: "splashToLogin", sender: nil)
}
}, onError: {
error in return
}).disposed(by: disposeBag)
Am I making a wrong assumption on how does RXSwift works?
Edit: If I add this line .observeOn(MainScheduler.instance) after .subscribeOn(MainScheduler.instance) the subscribe method runs on Main thread. Is this correct behavior? What is .subscribeOn(MainScheduler.instance) even doing?
Your edit explains all. Your initial assumption on what subscribeOn and observeOn were backwards.
The subscribeOn operator refers to how the observable above the operator in the chain subscribes to the source of events (and likely doesn't do what you think it does in any case. Your two network calls likely set up their own background thread to emit values on regardless of how they are subscribed to.)
For example, look at this:
extension ObservableType {
func subscribeOnMain() -> Observable<Element> {
Observable.create { observer in
let disposable = SingleAssignmentDisposable()
DispatchQueue.main.async {
disposable.setDisposable(self.subscribe(observer))
}
return disposable
}
}
}
It makes it obvious why the operator is called subscribeOn. It's because the subscribe is happening on the scheduler/thread in question. And this helps you understand better what is happening when you stack subscribeOn operators...
The observeOn operator refers to the scheduler that will be emitting elements to the observer (which is the block(s) of code that are passed to the subscribe operator.)
Which would look like this:
extension ObservableType {
func observeOnMain() -> Observable<Element> {
Observable.create { observer in
self.subscribe { event in
DispatchQueue.main.async {
observer.on(event)
}
}
}
}
}
From this you can see that the subscribe is happening on the original scheduler, while the observer is being called on the new scheduler.
Here is a great article explaining the whole thing: http://rx-marin.com/post/observeon-vs-subscribeon/
I'm learning RxSwift and I've come across the following pattern when creating Observables:
return Observable.create { observer in
let disposable = Disposables.create()
// Do some stuff with observer here
return disposable
}
As far as I can tell the returned Disposable doesn't actually do anything, does it serve a purpose other than to meet the requirements of the API to return a Disposable?
Is there any scenario where you might need to return a configured Disposable?
I suppose the thing that's confusing me the most is that the returned Disposable seems separate from the implementation of the Observable being created, i.e. it's not assigned to any properties or passed anywhere it's just created and returned.
There are two variations of the create method in relation to Disposables.
The first one, as Daniel mentioned, is used when you create a new Observable; you'll use the Disposables.create { ... } closure to "do cleanup", basically.
This is highly useful when using flatMapLatest, as your previous request will be disposed when a new ones comes in. Whenever it would be disposed, that "clean up" block will be called.
Observable<Int>.create { observer in
let someRequest = doSomeLongRunningThing { result in
observer.onNext(result)
observer.onCompleted()
}
return Disposables.create {
// How can I "cleanup" the process?
// Cancel the request, for example.
someRequest.cancel()
}
}
The second variation of Disposables.create is used for an entirely different purpose - grouping several Disposable objects as a single disposable object (a CompositeDisposable).
For example:
let disposable1 = someAction()
let disposable2 = someOtherAction()
let compositeDisposable = Disposables.create(disposable1, disposable2)
The Disposables.create function takes an optional closure. You should put any cancelation code in that closure. If you don't have any way to cancel, then the code is empty.
A good example is the wrapper around URLSession's dataTask method. In non-Rx code when you call URLRequest.shared.dataTask it returns a URLSessionDataTask object which can be used to cancel the network call. That object's cancel function gets called in the disposable.
Another common use is when you subscribe to some other observable from within your create closure. You then have to pass the disposable from that/those subscriptions by returning a Disposables.create(myDisposable) So that those subscriptions will get canceled properly when your Observable is disposed of.
I am using RxSwift for caching in my iOS app and have a piece of code like this:
let observable = Observable.of(cache.getItem(itemID), network.getItem(itemID)).concat().take(1)
observable.subscribeNext // and do some stuff
I have the cache.getItem method doing an onError if it has no value, and would like it to then defer to the network, but for some reason the network is never run. I assume its because I am using the take(1), but I would like the observable to stop emitting once the cache finds something (or continue to the network if it does not).
Any ideas on how to do this?
I've been following this guide but he does not go into detail about his cache's behavior when it fails to find something.
You shouldn't be using .Error like that. That's not really conceptually an error case. There's just nothing in the cache. That's a common situation. Nothing went "wrong" out of the ordinary. Instead, just send a .Completed event.
As for why your code isn't working, it's because an error coming from an Observable included in the concat will become an error on the final concat Observable. The thing to remember with Rx is that once there's a .Completed event or (in your case) an .Error event, that's it, it's over, no more .Next events (or any events).
So instead, if you use .Completed, your code would work as so:
class Cache {
func getItem(itemID: Int) -> Observable<Item> {
return Observable<Item>.create { observer in
// if not found...
observer.onCompleted() // you would of course really try to get it
// from the cache first.
return NopDisposable.instance
}
}
}
class Network {
func getItemN(itemID: Int) -> Observable<Item> {
return Observable<Item>.create { observer in
// get some `item` from the network and then..
observer.onNext(item)
return NopDisposable.instance
}
}
}
let observable = Observable.of(cache.getItem(itemID), network.getItem(itemID)).concat().take(1)
observable.subscribeNext { item in
print(item)
}