Swift iOS ReactiveKit: calling the observer causes to trigger action multiple times? - ios

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.

Related

Unit test API Call with internal switch case

I am trying to unit test a class that has different modes of setup.
class Controller{
enum Mode{
case listing
case pages(String?)
}
private (set) var mode : Mode = .listing
private (set) var models = [Model]()
init() {
...
}
init(id : String) {
mode = .pages(id)
}
func fetchInfo(){
switch mode{
case .listing:
ApiManager.firstNetworkCall(){ (json, error) in ...
setupModel()
}
case .pages(let id):
ApiManager.secondNetworkCall(id : id){ (json, error) in ...
setupModel()
}
}
}
}
Both of these will update the models array with different quantity of data.
What I have right now:
var controller : Controller!
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
try super.setUpWithError()
controller = Controller()
}
override func tearDownWithError() throws {
// Put teardown code here. This method is called after the invocation of each test method in the class.
controller = nil
try super.tearDownWithError()
}
func testDefaultListingMode() throws {
switch controller.mode{
case .listing:
XCTAssertTrue(true)
default:
XCTAssertFalse(false)
}
}
func testAPISetup() throws {
controller.fetchInfo()
//now what?
}
This checks if the mode is correct but I am trying to go one step further and check if the correct number of items is setup depending on the mode. And want to call the fetchInfo() method directly from the XCTestCase and just validate the model count.
All the tutorials and guides I have seen just talk about faking the behaviour with a URLSession. But the API call is dependent on the mode that happens as an internal check inside the fetchInfo method and is the only method exposed to other classes. I would simply like to test the method (in case something breaks inside that method causing a bug).
How do I go about doing that? I can't figure out how to complete the testAPISetup() method.
What I had for networking:
class NetworkingManager{
static var alamoFireManager = Session.default
static func POST(...., completion : ()->()) {
sendRequest(....., completion : completion)
}
private static func sendRequest(...., completion : ()->()) {
let request = alamoFireManager.request(.....)
request.responseJSON{
completion()
}
}
}
class APIManager{
static func firstNetworkCall(completion : ()->()){
NetworkingManager.POST(..., completion : completion)
}
}
I had to change the above and removed all mentions of static and singletons. I decided to go ahead with using class inheritance. I tried to avoid it and use protocols but it frankly was quite easier to use classes!
class NetworkingManager{
private (set) var sessionManager: Session
init(config : URLSessionConfiguration = .default){
config.timeoutIntervalForResource = 8.0
config.timeoutIntervalForRequest = 8.0
self.sessionManager = Session(configuration: config)
}
func request(...) {
//hit alamofire
}
}
class APIManager : NetworkingManager{
override init(config: URLSessionConfiguration = .default) {
super.init(config: config)
}
//other methods
...
}
class Controller{
private let apiManager : APIManager
init(manager : APIManager = APIManager()){
self.apiManager = manager
}
}
And in my test class:
override func setUpWithError() throws {
// Put setup code here. This method is called before the invocation of each test method in the class.
try super.setUpWithError()
let config = URLSessionConfiguration.ephemeral
apiManager = APIManager(config : config)
controller = Controller(manager : apiManager)
}
func testApiCalled() throws{
controller.fetchNecessaryInfo()
//had to add one second delay as alamofire adds the request on another queue. Wasn't able to put it on main queue.
sleep(1)
let promise = expectation(description: "Check request called")
apiManager.sessionManager.session.getAllTasks { (taskArray) in
if taskArray.count > 1{
XCTFail("Multiple requests when there should be only one")
}
if let task = taskArray.first, let request = task.currentRequest{
if let string = request.url?.absoluteString{
XCTAssert(...)
}else{
XCTFail("Incorrect URL")
}
}else{
XCTFail("Somehow no task exists. So this is an error")
}
promise.fulfill()
}
wait(for: [promise], timeout: 1.0)
}
I couldn't figure out any other way without having to instantiate an object for APIManager, so had to refactor!

How do you write a Swift completion block that can only be called once?

Let's say I have a Swift class that stores a completion block, and does a few asynchronous tasks.
I want that block to be called by whichever of the tasks finishes first, but only that one - I don't want it to be called again when the second task finishes.
How can I implement this in a clean way?
As long as you don't need this to be thread safe, you can solve this problem with a fairly straightforward #propertyWrapper.
#propertyWrapper
struct ReadableOnce<T> {
var wrappedValue: T? {
mutating get {
defer { self._value = nil }
return self._value
}
set {
self._value = newValue
}
}
private var _value: T? = nil
}
Mark the completion block var with #ReadableOnce, and it will be destroyed after the first time it's value is read.
Something like this:
class MyClass {
#ReadableOnce private var completion: ((Error?) -> Void)?
init(completion: #escaping ((Error?) -> Void)) {
self.completion = completion
}
public func doSomething() {
// These could all be invoked from different places, like your separate tasks' asynchronous callbacks
self.completion?(error) // This triggers the callback, then the property wrapper sets it to nil.
self.completion?(error) // This does nothing
self.completion?(error) // This does nothing
}
}
I wrote up more of a detailed discussion of this here but the key thing to be aware of is that reading the value sets it to nil, even if you don't invoke the closure! This might be surprising to someone who isn't familiar with the clever property wrapper you've written.
There is already a standard expression of onceness. Unfortunately the standard Objective-C is unavailable in Swift (GCD dispatch_once), but the standard Swift technique works fine, namely a property with a lazy define-and-call initializer.
Exactly how you do this depends on the level at which you want onceness to be enforced. In this example it's at the level of the class instance:
class MyClass {
// private part
private let completion : (() -> ())
private lazy var once : Void = {
self.completion()
}()
private func doCompletionOnce() {
_ = self.once
}
// public-facing part
init(completion:#escaping () -> ()) {
self.completion = completion
}
func doCompletion() {
self.doCompletionOnce()
}
}
And here we'll test it:
let c = MyClass() {
print("howdy")
}
c.doCompletion() // howdy
c.doCompletion()
let cc = MyClass() {
print("howdy2")
}
cc.doCompletion() // howdy2
cc.doCompletion()
If you promote the private stuff to the level of the class (using a static once property), the completion can be performed only once in the lifetime of the entire program.

iOS/Swift - What is the difference between Closure/Completion blocks and Delegates/functions?

I don't clear about these two, Nowadays the world is shifting to the closure types. But I'm not clearly understanding this. Can someone explain me with a real-time example?
So a real life example of both would be something like this:
protocol TestDelegateClassDelegate: class {
func iAmDone()
}
class TestDelegateClass {
weak var delegate: TestDelegateClassDelegate?
func doStuff() {
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
self.delegate?.iAmDone()
}
}
}
class TestClosureClass {
var completion: (() -> Void)?
func doStuff() {
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
self.completion?()
}
}
}
class ViewController: UIViewController, TestDelegateClassDelegate {
func iAmDone() {
print("TestDelegateClassDelegate is done")
}
override func viewDidLoad() {
super.viewDidLoad()
let testingDelegate = TestDelegateClass()
testingDelegate.delegate = self
testingDelegate.doStuff()
let testingClosure = TestClosureClass()
testingClosure.completion = {
print("TestClosureClass is done")
}
testingClosure.doStuff()
}
}
Here we have 2 classes TestDelegateClass and TestClosureClass. Each of them have a method doStuff which waits for 3 seconds and then reports back to whoever is listening where one uses delegate procedure and the other one uses closure procedure.
Although they do nothing but wait you can easily imagine that they for instance upload an image to server and notify when they are done. So for instance you might want to have an activity indicator running while uploading is in progress and stop it when done. It would look like so:
class ViewController: UIViewController, TestDelegateClassDelegate {
#IBOutlet private var activityIndicator: UIActivityIndicatorView?
func iAmDone() {
print("TestDelegateClassDelegate is done")
activityIndicator?.stopAnimating()
}
override func viewDidLoad() {
super.viewDidLoad()
activityIndicator?.startAnimating()
let testingDelegate = TestDelegateClass()
testingDelegate.delegate = self
testingDelegate.doStuff()
activityIndicator?.startAnimating()
let testingClosure = TestClosureClass()
testingClosure.completion = {
self.activityIndicator?.stopAnimating()
print("TestClosureClass is done")
}
testingClosure.doStuff()
}
}
Naturally you would only use one of the two procedures.
You can see there is a huge difference in code. To do a delegate procedure you need to create a protocol, in this case TestDelegateClassDelegate. A protocol is what defines the interface of a listener. And since an iAmDone method is defined it must be defined in ViewController as well as long as it is defined as TestDelegateClassDelegate. Otherwise it will not compile. So anything declared as TestDelegateClassDelegate will have that method and any class can call it. In our case we have weak var delegate: TestDelegateClassDelegate?. That is why we can call delegate?.iAmDone() without caring what delegate actually is. For instance we can create another class:
class SomeClass: TestDelegateClassDelegate {
func iAmDone() {
print("Something cool happened")
}
init() {
let testingDelegate = TestDelegateClass()
testingDelegate.delegate = self
testingDelegate.doStuff()
}
}
So a good example for instance is an UITableView that uses a delegate and dataSource (both are delegates, just properties are named differently). And table view will call the methods of whatever class you set to those properties without needing to know what that class is as long as it corresponds to the given protocols.
Same could be achieved with closures. A table view could have been defined using properties giving closures like:
tableView.onNumberOfRows { section in
return 4
}
But that would most likely lead into one big mess of a code. Also closures would in this case be giving many programmers headaches due to potential memory leaks. It is not that closures are less safe or anything, they just do a lot of code you can't see which may produce retain cycles. In this specific case a most likely leak would be:
tableView.onNumberOfRows { section in
return self.dataModel.count
}
and a fix to it is simply doing
tableView.onNumberOfRows { [weak self] section in
return self?.dataModel.count ?? 0
}
which now looks overcomplicated.
I will not go into depths of closures but in the end when you have repeated call to callbacks (like in case of table view) you will need a weak link either at delegate or in closure. But when the closure is called only once (like uploading an image) there is no need for a weak link in closures (in most but not all cases).
In retrospective use closures as much as possible but avoid or use caution as soon as a closure is used as a property (which is ironically the example I gave). But you would rather do just this:
func doSomethingWithClosure(_ completion: #escaping (() -> Void)) {
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
completion()
}
}
And use it as
doSomethingWithClosure {
self.activityIndicator?.stopAnimating()
print("TestClosureClass is done")
}
This has now removed all potential risks. I hope this clears a thing or two for you.
In Swift/obj-c the term delegate is used to refer to a protocol that responds to specific selectors.
Thing about it just like calling a method on an object.
E.g.
protocol CalculatorDelegate : class { // : class so it can be made 'weak'
func onCalculation(result: Int) -> Void
}
Now if we have a Calculator class, to use the delegate we'd do something like
class Calculator() {
weak var delegate: CalculatorDelegate?
func calculate(_ a: Int, _ b: Int) -> Int {
let result = a + b
self.delegate?.onCalculation(result: result)
return result
}
}
Then in some other class (e.g. in iOS - a View Controller) we might do:
class MyClass : CalculatorDelegate {
func onCalculation(result: Int) {
print("Delegate method on calculation called with result \(result)")
}
func someButtonPress() {
let calculator = Calculator()
calculator.delegate = self
calculator.calculate(42, 66)
}
}
So you can see how the setup is quite elaborate.
Now closures are simply blocks of code that can be called in other places, so you could change all of the code like so:
class Calculator2() {
weak var delegate: CalculatorDelegate?
func calculate(_ a: Int, _ b: Int, onCalculation: (#escaping (Int) -> Void) -> Int)?) {
let result = a + b
onCalculation?(result)
return result
}
}
class MyClass {
func someButtonPress() {
let calculator = Calculator2()
calculator.calculate(42, 66, onCalculation: { (result: Int) in
print("Closure invoked with \(result)")
})
}
}
However, with closures you need to be aware that it is a lot easier to shoot yourself in the foot by capturing variables (e.g. self) strongly, which will lead to memory leaks even under ARC regime.
Closures are first-class objects so that they can be nested and passed around
Simply,
In swift, functions are primitive data types like int, double or character that is why you can pass a function in the function parameter. In swift mechanism simplify in closures syntax like lambda expression in other languages.
E.g. If you want to call rest API through URSSession or Alamofire and return response data then you should use completionHandler(it's closure).
Void closure : - {(paramter:DataType)->Void}
Return closure : - {(paramter:DataType)->DataType}
e.g. (int, int) -> (int)
https://docs.swift.org/swift-book/LanguageGuide/Closures.html

Unit-test RxSwift observable in ViewController

I'm quite new to RxSwift. I have a view controller that has a typeahead/autocomplete feature (i.e., user types in a UITextField and as soon as they enter at least 2 characters a network request is made to search for matching suggestions). The controller's viewDidLoad calls the following method to set up an Observable:
class TypeaheadResultsViewController: UIViewController {
var searchTextFieldObservable: Observable<String>!
#IBOutlet weak var searchTextField: UITextField!
private let disposeBag = DisposeBag()
var results: [TypeaheadResult]?
override func viewDidLoad() {
super.viewDidLoad()
//... unrelated setup stuff ...
setupSearchTextObserver()
}
func setupSearchTextObserver() {
searchTextFieldObservable =
self.searchTextField
.rx
.text
.throttle(0.5, scheduler: MainScheduler.instance)
.map { $0 ?? "" }
searchTextFieldObservable
.filter { $0.count >= 2 }
.flatMapLatest { searchTerm in self.search(for: searchTerm) }
.subscribe(
onNext: { [weak self] searchResults in
self?.resetResults(results: searchResults)
},
onError: { [weak self] error in
print(error)
self?.activityIndicator.stopAnimating()
}
)
.disposed(by: disposeBag)
// This is the part I want to test:
searchTextFieldObservable
.filter { $0.count < 2 }
.subscribe(
onNext: { [weak self] _ in
self?.results = nil
}
)
.disposed(by: disposeBag)
}
}
This seems to work fine, but I'm struggling to figure out how to unit test the behavior of searchTextFieldObservable.
To keep it simple, I just want a unit test to verify that results is set to nil when searchTextField has fewer than 2 characters after a change event.
I have tried several different approaches. My test currently looks like this:
class TypeaheadResultsViewControllerTests: XCTestCase {
var ctrl: TypeaheadResultsViewController!
override func setUp() {
super.setUp()
let storyboard = UIStoryboard(name: "MainStoryboard", bundle: nil)
ctrl = storyboard.instantiateViewController(withIdentifier: "TypeaheadResultsViewController") as! TypeaheadResultsViewController
}
override func tearDown() {
ctrl = nil
super.tearDown()
}
/// Verify that the searchTextObserver sets the results array
/// to nil when there are less than two characters in the searchTextView
func testManualChange() {
// Given: The view is loaded (this triggers viewDidLoad)
XCTAssertNotNil(ctrl.view)
XCTAssertNotNil(ctrl.searchTextField)
XCTAssertNotNil(ctrl.searchTextFieldObservable)
// And: results is not empty
ctrl.results = [ TypeaheadResult(value: "Something") ]
let tfObservable = ctrl.searchTextField.rx.text.subscribeOn(MainScheduler.instance)
//ctrl.searchTextField.rx.text.onNext("e")
ctrl.searchTextField.insertText("e")
//ctrl.searchTextField.text = "e"
do {
guard let result =
try tfObservable.toBlocking(timeout: 5.0).first() else {
return }
XCTAssertEqual(result, "e") // passes
XCTAssertNil(ctrl.results) // fails
} catch {
print(error)
}
}
Basically, I'm wondering how to manually/programmatically fire an event on searchTextFieldObservable (or, preferably, on the searchTextField) to trigger the code in the 2nd subscription marked "This is the part I want to test:".
The first step is to separate the logic from the effects. Once you do that, it will be easy to test your logic. In this case, the chain you want to test is:
self.searchTextField.rx.text
.throttle(0.5, scheduler: MainScheduler.instance)
.map { $0 ?? "" }
.filter { $0.count < 2 }
.subscribe(
onNext: { [weak self] _ in
self?.results = nil
}
)
.disposed(by: disposeBag)
The effects are only the source and the sink (another place to look out for effects is in any flatMaps in the chain.) So lets separate them out:
(I put this in an extension because I know how much most people hate free functions)
extension ObservableConvertibleType where E == String? {
func resetResults(scheduler: SchedulerType) -> Observable<Void> {
return asObservable()
.throttle(0.5, scheduler: scheduler)
.map { $0 ?? "" }
.filter { $0.count < 2 }
.map { _ in }
}
}
And the code in the view controller becomes:
self.searchTextField.rx.text
.resetResults(scheduler: MainScheduler.instance)
.subscribe(
onNext: { [weak self] in
self?.results = nil
}
)
.disposed(by: disposeBag)
Now, let's think about what we actually need to test here. For my part, I don't feel the need to test self?.results = nil or self.searchTextField.rx.text so the View controller can be ignored for testing.
So it's just a matter of testing the operator... There's a great article that recently came out: https://www.raywenderlich.com/7408-testing-your-rxswift-code However, frankly I don't see anything that needs testing here. I can trust that throttle, map and filter work as designed because they were tested in the RxSwift library and the closures passed in are so basic that I don't see any point in testing them either.
The problem is that self.ctrl.searchTextField.rx.text.onNext("e") won't trigger searchTextFieldObservable onNext subscription.
The subscription is also not triggered if you set the text value directly like this self.ctrl.searchTextField.text = "e".
The subscription will trigger (and your test should succeed) if you set the textField value like this: self.ctrl.searchTextField.insertText("e").
I think the reason for this is that UITextField.rx.text observes methods from UIKeyInput.
I prefer to keep UIViewControllers far away from my unit tests. Therefore, I suggest moving this logic to a view model.
As your bounty explanation details, basically what you are trying to do is mock the textField's text property, so that it fires events when you want it to. I would suggest replacing it with a mock value altogether. If you make textField.rx.text.bind(viewModel.query) the responsibility of the view controller, then you can focus on the view model for the unit test and manually alter the query variable as needed.
class ViewModel {
let query: Variable<String?> = Variable(nil)
let results: Variable<[TypeaheadResult]> = Variable([])
let disposeBag = DisposeBag()
init() {
query
.asObservable()
.flatMap { query in
return query.count >= 2 ? search(for: $0) : .just([])
}
.bind(results)
.disposed(by: disposeBag)
}
func search(query: String) -> Observable<[TypeaheadResult]> {
// ...
}
}
The test case:
class TypeaheadResultsViewControllerTests: XCTestCase {
func testManualChange() {
let viewModel = ViewModel()
viewModel.results.value = [/* .., .., .. */]
// this triggers the subscription, but does not trigger the search
viewModel.query.value = "1"
// assert the results list is empty
XCTAssertEqual(viewModel.results.value, [])
}
}
If you also want to test the connection between the textField and the view model, UI tests are a much better fit.
Note that this example omits:
Dependency injection of the network layer in the view model.
The binding of the view controller's textField value to query (i.e., textField.rx.text.asDriver().drive(viewModel.query)).
The observing of the results variable by the view controller (i.e., viewModel.results.asObservable.subscribe(/* ... */)).
There might be some typos in here, did not run it past the compiler.
If you look at the underlying implementation for rx.text, you'll see it relies on controlPropertyWithDefaultEvents which fires the following UIControl events: .allEditingEvents and .valueChanged.
Simply setting the text, it won't fire any events, so your observable is not triggered. You have to send an action explicitly:
textField.text = "Something"
textField.sendActions(for: .valueChanged) // or .allEditingEvents
If you are testing within a framework, sendActions won't work because the framework is missing the UIApplication. You can do this instead
extension UIControl {
func simulate(event: UIControl.Event) {
allTargets.forEach { target in
actions(forTarget: target, forControlEvent: event)?.forEach {
(target as NSObject).perform(Selector($0))
}
}
}
}
...
textField.text = "Something"
textField.simulate(event: .valueChanged) // or .allEditingEvents

RxSwift: Prevent multiple network requests

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.

Resources