I have a Subject observable representing the result of the network request that needs to be delivered to multiple subscribers.
I can use ReplaySubject of buffer 1 and publish() method. However, the network request gets executed only once.
I'd like to trigger the fetch event at any give point in the future. How can I trigger a new requst?
Currently, I have a Service object that holds the ReplaySubject and has a method reload() which triggers the network request and publishes the result to the aReplaySubject.
Is there any method on Observable that can "refresh" it and deliver a new value to all the current subscribers?
If I'm interpreting this question correctly, this is a fairly common problem in RxSwift. You need to be able to recreate your network request Observable each time your fetch is "triggered," but you need these results delivered on a single Observable that only gets created once, and has multiple subscribers. This is done with a flatMap:
struct Service {
var resultsObservable: Observable<Results> {
return resultsSubject.asObservable()
}
private let resultsSubject: ReplaySubject<Results> = .create(bufferSize: 1)
private let reloadSubject = PublishSubject<Void>()
private let disposeBag = DisposeBag()
init() {
bindFetch()
}
func reload() {
reloadSubject.onNext(())
}
private func bindFetch() {
reloadSubject
.asObservable()
.flatMap(fetch)
.bind(to: resultsSubject)
.disposed(by: disposeBag)
}
private func fetch() -> Observable<Results> {
// URLSession just one example
let urlRequest = URLRequest(url: URL(string: "https://apple.com")!)
return URLSession
.shared
.rx
.data(request: urlRequest)
.map(Results.init)
.catchErrorJustReturn(Results.empty())
}
}
In this example, you can subscribe to resultsObservable multiple times, and each should be updated after a new reload() occurs.
Related
I have Singleton class to which i have used to observe a property and trigger next action.
Singleton Class:
public class BridgeDispatcher: NSObject {
open var shouldRespondToBridgeEvent = SafePublishSubject<[String: Any]>()
open var shouldPop = SafePublishSubject<Void>()
open var shouldUpdate = SafePublishSubject<Void>()
public let disposeBag = DisposeBag()
open static let sharedInstance: BridgeDispatcher = BridgeDispatcher()
override init() {
super.init()
shouldRespondToBridgeEvent.observeNext { event in
if let type = event["type"] as? String {
switch type {
case "ShouldUpdate":
self.onShiftBlockDidUpdateHeight.next()
case "shouldPop":
self.onPopCurrentViewController.next(())
default:
print("Event not supported")
}
}
}.dispose(in: self.disposeBag)
}
}
Above method will trigger by calling:
BridgeDispatcher.sharedInstance.shouldRespondToBridgeEvent.next(body)
Register for onPopCurrentViewController:
BridgeDispatcher.sharedInstance.onPopCurrentViewController.observeNext { doSomething() }.dispose(in: BridgeDispatcher.sharedInstance.disposeBag)
On my application, BridgeDispatcher.sharedInstance.onPopCurrentViewController.observeNext{} method will be called multiple times due to the business logic, due to this doSomething() method will trigger multiple times when calling BridgeDispatcher.sharedInstance.shouldRespondToBridgeEvent.next(body).
Is this issue with my singleton design pattern or observeNext calling multiple times. (BridgeDispatcher.sharedInstance.onPopCurrentViewController.observeNext{} )
Need help.
I have used .updateSignal on ObservableComponent.
valueToUpdate.updateSignal.compactMap { (arg0) -> String? in
let (value, _, validationFailure) = arg0
return validationFailure == nil ? value?.value : nil
}
.removeDuplicates()
.debounce(for: 1.0)
.observeNext { [unowned self] _ in
self.doYourWork()
}
.dispose(in: self.bag)
It attempts to deal with the multiple calls in two ways: first by discarding any duplicate events, so if the duration hasn’t changed, then no call is made. Second, by debouncing the signal so if the user makes a bunch of changes we only call the method when they’re done making changes.
I'm trying to achieve my Observables to execute only when previous Observable has completed. I can't use flatMap, because subscriptions can be called from different places, and this Observables is not connected with each other. To be specific: I have my CollectionView loading more content from server and 2 seconds after that user clicks "Send comment" button while CollectionView is still loading its batch. So I want to wait until CollectionView update completes and only then execute my comment's posting request. I created a class named ObservableQueue and it's working just fine. But I need to know if it has issues like memory leaks, dead locks or maybe I just missing something. Here it is:
extension CompositeDisposable {
#discardableResult
func insert(disposeAction: #escaping () -> ()) -> DisposeKey? {
return insert(Disposables.create(with: disposeAction))
}
}
class ObservableQueue {
private let lock = NSRecursiveLock()
private let relay = BehaviorRelay(value: 0)
private let scheduler = SerialDispatchQueueScheduler(internalSerialQueueName: "ObservableQueue.scheduler")
func enqueue<T>(_ observable: Observable<T>) -> Observable<T> {
return Observable.create({ observer -> Disposable in
let disposable = CompositeDisposable()
let relayDisposable = self
.relay
.observeOn(self.scheduler)
.filter({ value -> Bool in
if value > 0 {
return false
}
self.lock.lock(); defer { self.lock.unlock() }
if self.relay.value > 0 {
return false
}
self.relay.accept(self.relay.value + 1)
disposable.insert {
self.lock.lock(); defer { self.lock.unlock() }
self.relay.accept(self.relay.value - 1)
}
return true
})
.take(1)
.flatMapLatest { _ in observable }
.subscribe { observer.on($0) }
_ = disposable.insert(relayDisposable)
return disposable
})
}
}
And then I can use it like this:
let queue = ObservableQueue()
...
// first observable
let observable1 = Observable
.just(0)
.delay(5, scheduler: MainScheduler.instance)
queue
.enqueue(observable1)
.subscribe(onNext: { _ in
print("here1")
})
.disposed(by: rx.disposeBag)
// second observable
let observable2 = Observable
.just(0)
.delay(5, scheduler: MainScheduler.instance)
queue
.enqueue(observable2)
.subscribe(onNext: { _ in
print("here2")
})
.disposed(by: rx.disposeBag)
// third observable
let observable3 = Observable
.just(0)
.delay(5, scheduler: MainScheduler.instance)
queue
.enqueue(observable3)
.subscribe(onNext: { _ in
print("here3")
})
.disposed(by: rx.disposeBag)
CLGeocoder has the same issue. According to the documentation, you can't call one of the geocoder methods while it's working on a previous request so very much like what you are trying to do. In this gist (https://gist.github.com/danielt1263/64bda2a32c18b8c28e1e22085a05df5a), you will find that I make the observable calls on a background thread and protect the job with semaphore. That's the key, you need a semaphore, not a lock.
Something like this should work for you:
class ObservableQueue {
private let semaphore = DispatchSemaphore(value: 1)
private let scheduler = ConcurrentDispatchQueueScheduler(qos: .userInitiated)
func enqueue<T>(_ observable: Observable<T>) -> Observable<T> {
let _semaphore = semaphore // To avoid the use of self in the block below
return Observable.create { observer in
_semaphore.wait()
let disposable = observable.subscribe { event in
switch event {
case .next:
observer.on(event)
case .error, .completed:
observer.on(event)
}
}
return Disposables.create {
disposable.dispose()
_semaphore.signal()
}
}
.subscribeOn(scheduler)
}
}
I will give you some suggestions that I think will help you in the future.
Avoid as much as possible the Observable.create, this is the "brute force" creation of an observable and it doesn't handle back pressure at all, you'll have to implement it yourself, and it's not something easy.
Usually for HTTP api calls, you don't need Observable, you should use Single or Completable since you expect only one response from your server, not a stream of responses.
You should be careful with strong self inside the onNext/on..., as a rule of thumb if the class that subscribes to the observer has the dispose bag, you should use a weak self.
Now for your particular case, if you need to just this pair of observers (fetch & send comment), I think the queue is a little bit overkill. You can simply call the post comment observer (if available) on the do(onNext:) method of your "fetch" observer. Do on next is called every time an "onNext" event is triggered.
If you still need a queue, I would go with an OperationQueue that enqueues only operations and has a method like observeOperationchanges() -> Observeble<Operation> this will be triggered every time an operation is completed. In this way you subscribe once and enqueue multiple times, but this might not fit your needs.
I would use .combineLatest() to produce an event once both observables have emitted something. See http://rxmarbles.com/#combineLatest
I´m following an MVVM architecture, so I have a viewController that holds a reference to its viewModel like this:
lazy private var viewModel: MyViewModel = {
return MyViewModel()
}()
Such viewModel is initialized within the viewDidLoad() method this way:
private func initViewModel() {
viewModel.onModelChange = { [unowned self] () in
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
viewModel.getData()
}
Where getData() viewModel's method calls another object to perform a networking async task to retrieve the data to be displayed by the viewController:
func getData() {
guard let url = ServiceAPI.getServiceUrl() else {
return
}
dataProvider.getData(url: url, completion: {[weak self] (result, error) -> Void in
if let error = error {
self?.model = [MyCustomModel]()
} else {
if let result = result {
self?.total = result.data.total
self?.processFetchedData(items: result.data.items)
} else {
self?.model = [MyCustomModel]()
}
}
})
}
Regarding threading, the scenario is: the viewModel is initialized by the viewController and bound to it in the main thread. The viewModel is asked to call a networking object (dataProvider), also initialized in the main thread. dataProvider calls also from the main thread an URLSessionDataTask to retrieve the data needed. The data task is supposed to perform asynchronoulsy in a thread different from main. Once finished, its completionHandler is also executed in a thread different from main. There is where I update the viewModel (see code snippet above). Then, as the model in the viewModel is being observed, an onModelChange closure is executed in the viewController, where I update a tableView with the data. Since this operation is UI related, I do it in the main thread (see also code snippet above).
Question is: this seems to work, but is it actually correct/safe to update the viewModel in a thread different from main? It was created in main thread. May this be a potential issue?
viewModel is a class, and the model it holds is an array of MyCustomModel that are structs.
I am currently having an issue with multiple network requests executing when using RxSwift Observables. I understand that if one creates a cold observable and it has multiple observers, the observable will execute its block each time it is subscribed to.
I have tried to create a shared subscription observable that executes the network request once, and multiple subscribers will be notified of the result. Below is the what I have tried.
Sequence of events
Create the view model with the tap event of a uibutton
Create the serviceStatus Observable as a public property on the view model. This Observable is mapped from the buttonTapped Observable. It then filters out the "Loading" status. The returned Observable has a shareReplay(1) executed on it to return a shared subscription.
Create the serviceExecuting Observable as a public property on the view model. This observable is mapped from the serviceStatus Observable. It will return true if the status is "Loading"
Bind the uilabel to the serviceStatus Observable
Bind the activity indicator to the serviceExecuting Observable.
When the button is tapped, the service request is executed three time where I would be expecting it to be executed only once. Does anything stand out as incorrect?
Code
class ViewController {
let disposeBag = DisposeBag()
var button: UIButton!
var resultLabel: UILabel!
var activityIndicator: UIActivityIndicator!
lazy var viewModel = { // 1
return ViewModel(buttonTapped: self.button.rx.tap.asObservable())
}
override func viewDidLoad() {
super.viewDidLoad()
self.viewModel.serviceStatus.bindTo(self.resultLabel.rx_text).addDispsoableTo(disposeBag) // 4
self.viewModel.serviceExecuting.bindTo(self.activityIndicator.rx_animating).addDispsoableTo(disposeBag) // 5
}
}
class ViewModel {
public var serviceStatus: Observable<String> { // 2
let serviceStatusObseravble = self.getServiceStatusObservable()
let filtered = serviceStatusObseravble.filter { status in
return status != "Loading"
}
return filtered
}
public var serviceExecuting: Observable<Bool> { // 3
return self.serviceStatus.map { status in
return status == "Loading"
}
.startWith(false)
}
private let buttonTapped: Observable<Void>
init(buttonTapped: Observable<Void>) {
self.buttonTapped = buttonTapped
}
private func getServiceStatusObservable() -> Observable<String> {
return self.buttonTapped.flatMap { _ -> Observable<String> in
return self.createServiceStatusObservable()
}
}
private func createServiceStatusObservable() -> Observable<String> {
return Observable.create({ (observer) -> Disposable in
someAsyncServiceRequest() { result }
observer.onNext(result)
})
return NopDisposable.instance
})
.startWith("Loading")
.shareReplay(1)
}
EDIT:
Based on the conversation below, the following is what I was looking for...
I needed to apply a share() function on the Observable returned from the getServiceStatusObservable() method and not the Observable returned from the createServiceStatusObservable() method. There were multiple observers being added to this observable to inspect the current state. This meant that the observable executing the network request was getting executed N times (N being the number of observers). Now every time the button is tapped, the network request is executed once which is what I needed.
private func getServiceStatusObservable() -> Observable<String> {
return self.buttonTapped.flatMap { _ -> Observable<String> in
return self.createServiceStatusObservable()
}.share()
}
.shareReplay(1) will apply to only one instance of the observable. When creating it in createServiceStatusObservable() the sharing behavior will only affect the one value returned by this function.
class ViewModel {
let serviceStatusObservable: Observable<String>
init(buttonTapped: Observable<Void>) {
self.buttonTapped = buttonTapped
self.serviceStatusObservable = Observable.create({ (observer) -> Disposable in
someAsyncServiceRequest() { result in
observer.onNext(result)
}
return NopDisposable.instance
})
.startWith("Loading")
.shareReplay(1)
}
private func getServiceStatusObservable() -> Observable<String> {
return self.buttonTapped.flatMap { [weak self] _ -> Observable<String> in
return self.serviceStatusObservable
}
}
}
With this version, serviceStatusObservable is only created once, hence it's side effect will be shared everytime it is used, as it is the same instance.
I want to unsubscribe from Observable in RxSwift. In order to do this I used to set Disposable to nil. But it seems to me that after updating to RxSwift 3.0.0-beta.2 this trick does not work and I can not unsubscribe from Observable:
//This is what I used to do when I wanted to unsubscribe
var cancellableDisposeBag: DisposeBag?
func setDisposable(){
cancellableDisposeBag = DisposeBag()
}
func cancelDisposable(){
cancellableDisposeBag = nil
}
So may be somebody can help me how to unsubscribe from Observable correctly?
In general it is good practice to out all of your subscriptions in a DisposeBag so when your object that contains your subscriptions is deallocated they are too.
let disposeBag = DisposeBag()
func setupRX() {
button.rx.tap.subscribe(onNext : { _ in
print("Hola mundo")
}).addDisposableTo(disposeBag)
}
but if you have a subscription you want to kill before hand you simply call dispose() on it when you want too
like this:
let disposable = button.rx.tap.subcribe(onNext : {_ in
print("Hallo World")
})
Anytime you can call this method and unsubscribe.
disposable.dispose()
But be aware when you do it like this that it your responsibility to get it deallocated.
Follow up with answer to Shim's question
let disposeBag = DisposeBag()
var subscription: Disposable?
func setupRX() {
subscription = button.rx.tap.subscribe(onNext : { _ in
print("Hola mundo")
})
}
You can still call this method later
subscription?.dispose()