Code and Developers

Is Rx Still Relevant in Modern Mobile iOS Development Today

The components of Rx code can be divided into three main types: observables, operators and schedulers.

RxSwift, like any tool, is just a convenient way to solve any problem. Some time ago it was very popular to use Rx in all mobile applications. Let's see if this is so at the moment. First, let's remember what RxSwift is.

RxSwift is a library for working with asynchronous code that presents events as streams of events with the ability to subscribe to them, and also allows you to apply functional programming approaches to them, which greatly simplifies working with complex sequences of asynchronous events.

The prerequisite for the emergence of such tools is that in the process of evolution, the iOS SDK presented us with a wide selection of tools for working with asynchronous events: GCD, KVO, Notification Center, the delegation pattern, and the closure mechanism. The problem is that each of these tools requires an individual approach and a certain codebase around them, and their combination may not be very elegant.

Globally, the components of Rx code can be divided into 3 main types: observables, operators, and schedulers.

Observables

The Observable <T> class is the heart of RxSwift, its purpose is to allow one class to subscribe to sequences of events containing data of type T that are broadcast by other classes.

If you look into the sources of the ObservableType protocol, you will find there is a single subscribe method that subscribes the Observer to receive events from this sequence.

Events are a simple enum and are of three kinds:

next - serves to transmit the last arrived ("next") value to the Observer;

completed - indicates the successful completion of the sequence. This means that the Observable has successfully completed its lifecycle and will no longer broadcast events;

error - indicates that the execution of the Observable has ended with an error and other events will not be broadcast.

By their very nature, Observables can be finite for example, downloading a file and infinite for example, tracking device orientation changes. Each Observable can send 0 or more items.

In most cases, events will only come if there is at least one subscriber to the Observable (meaning cold Observable, this will be discussed in the next posts). There are no restrictions for types that can be used in events, the main rule is that there should be one type and only one. Observable cannot be of two types at once, for example Int and String.

In theory, you can make an Observable with a certain protocol that will be implemented by 2 different types, but this is usually not used.

RxSwift provides not only ways to manage your data, but also reactive presentation of user actions. RxCocoa contains everything you need. It brings most of the properties of UI components to Observable s, but not quite. There are ControlEvents and ControlProperties. 

These things contain Observable streams under the hood, but they also have some nuances:
- It never fails, so there are no errors;
- It will complete the sequence when the control is released.- It delivers events to the main thread;
- MainScheduler.instance.

Basically, you can work with them as usual:

button.rx_tap.subscribeNext { _ in   // control event
    print("User tapped the button!")
}.addDisposableTo(bag)

textField.rx_text.subscribeNext { text in // control property
    print("The TextField contains: \(SomeText)")
}.addDisposableTo(bag)
// notice that ControlProperty generates .Next event on subscription
// In this case, the log will display 
// "The TextField contains: "
// at the very start of the app.

This is very important to use: while you are using Rx forget about @IBAction stuff, whatever you need you can bind and configure right away. For example, your view controller's viewDidLoad method is a good candidate for describing how user interfaces work.

Operators

The implementation of the Observable class contains a number of helper methods that can perform various operations on the elements of the sequence. Since they have no side effects, they can be combined, which makes it possible to build quite complex logic in a declarative style.

Schedulers

Schedulers are somewhat analogous to DispatchQueues in the Rx world. The abstract the execution of tasks across multiple threads and queues to achieve optimal performance.
Although swift has ARC, RxSwift needs an additional tool for memory management. DisposeBag is a subscription container class that cleans up existing subscriptions the moment it is disposed of.

By default, a subscription creates a so-called retain-cycle between Observable and Observer. Without adding subscriptions to a special container class, we will get memory leaks. It is not a mistake to add all subscriptions to disposeBag, even though there are a couple of exceptions, for example .just (), .empty (), with which it is not necessary to do this.

Each time you reset or reassign a new value to a variable of type DisposeBag, you release all resources allocated to those subscriptions. You also forcefully terminate the Observable.

Let's take an example of using RxSwift as an example from the repository of RxSwift. First, we generate an observable searchResults for the URL requesting the GitHub API and getting the list of repositories:

let searchResults = searchBar.rx.text.orEmpty
          .throttle(0.3, scheduler: MainScheduler.instance)
          .distinctUntilChanged()
          .flatMapLatest { query -> Observable<[Repository]> in
            if query.isEmpty {
              return .just([])
            }
            return searchGitHub(query)
              .catchErrorJustReturn([])
          }
          .observeOn(MainScheduler.instance)

After that, we bind this Observable to the UITableView to see the result of the requests:

searchResults
  .bind(to: tableView.rx.item`s(cellIdentifier: "CellExample")) {
    (index, repository: Repository, cell) in
    cell.textLabel?.text = repository.name
    cell.detailTextLabel?.text = repository.url
  }
  .disposed(by: disposeBag)

The result will be:

Let's now take a closer look and consider this.

_ = Observable<String>
  .just("RxSwift Example") // - 2
  .subscribe(onNext: { element in // - 3
    print(element) // - 1
  })
 
observable // - 2
  .map { element in // - 4
    return "current element is \(element)"
  }
  .observeOn(ConcurrentDispatchQueueScheduler(qos: .background)) // - 4
  .subscribe(onNext: { element in // - 3
    print(element) //1
  })
  .disposed(by: disposeBag) // - 5

A big plus in RxSwift is the linear recording of asynchronous operations. The examples above follow approximately the same recorded scheme:

  • We have a block of code doing useful work;
  • There is the Observable himself;
  • There is a subscribe method that binds an Observable to a specific Observer;
  • Optionally, there may be a block for transforming the source material Observable;

The method is free from memory leaks, for now we accept it as a mandatory or stylistic feature of RxSwift.

If you look at other platforms from Apple, then you can come across a large set of different APIs that Apple uses. Data source and delegate for tables, target action for handling button clicks, subscriptions to Notification Center, KVO. Trying to use all of these APIs in one file can make the file difficult to read and maintain.

In RxSwift, with its uniform writing structure, this is a little easier. But do not forget that in RxSwift you can write a chain in such a way that only the one who wrote it can figure it out. But on average, the same type of recording in the steps described above simplifies the maintenance of the source. A very big plus is the fact that RxSwift is a port of ReactiveX, all knowledge can be used in other languages. This means that when creating logic using Rx, you can easily make it cross-platform and use.
Another important argument is that switching between threads is very simple, switching a heavy block of code to a background thread is easy. The main methods for managing streams are .observeOn (...) and .subscribeOn (...).

Now compared to when the Rx polarity boom just started. On the Internet, there are many examples and extensions that work well. And this is an important aspect. In my opinion, one of the most popular architectures is now MVVM. RxSwift is perfectly compatible with MVVM. Without this framework, you would have to use third-party tools or write your own to link the View and ViewModel, and there are many ready-made solutions for this.
But even with all this, nothing is perfect and px also has big disadvantages such as redundancy. In a nutshell, RxSwift is quite redundant, you can write an application without it. In some situations it is very easy to write code with RxSwift, but the number of objects in memory, the stack of the number of method calls increases significantly. RxSwift, alas, is not for newbies, not getting through the first minute of acquaintance without errors and redundant transformations to write the code you conceived. Depending on the intensity, more or less confident use of the framework will take more than two weeks. But the most difficult and hindering development will be problems with debug.

Conclusion

Even now, after all this time since the start, Rx does not lose its popularity and still remains relevant. But you need to take into account that it is advisable to take px only when you have enough time for the project and there is no impending deadline since it will take time to sort out something new for you. RxSwift helps when you need to combine complex asynchronous chains. RxSwift also has types such as Subject, a kind of bridge between the imperative and declarative worlds. The subject can act as an Observable, and at the same time, it can be an Observer, i.e. accept objects and issue events. And it will also be relevant if you use the MVVM architecture. But unless you have any experience with px or a mentor who can help you in case you get stuck. And there is no time to figure it out ourselves, then you should think before you start implementing it into the project and remember that RxSwift is not a panacea, any task can be solved without it.

Your IT success partner
Intersog has been recognized as a leading IT solutions provider in the United States and beyond. The company has been providing tech consulting, staffing and software development services for Fortune-500 companies, tech startups, and SaaS enterprises for more than fourteen years.

Leave a comment