In iOS development, we have to deal with asynchronous events almost all the time. For example, button clicking, data saving, audio background playing, interactive animation display. These events do not have a specific chronology, and they may even occur simultaneously.

While Apple provides asynchronous mechanisms for notifications, proxies, GCDS, closures, etc., these mechanisms lack a unified abstract representation. In addition, these mechanisms are not clear and concise when dealing with shared mutable data or state. Of course, this is not to say that writing elegant asynchronous code is unrealistic. After all, iOS is very asynchronous compared to other platforms.

Fortunately, we can handle asynchronous code elegantly with RxSwift.

See the documentation for the benefits of RxSwift and why you should use it. I’ll cut the crap here.

RxSwift profile

Responsive programming is not a new concept, but it has received more attention from developers in recent years. It was first proposed by Giant hardware, and its main purpose is to deal with complex UI asynchronous events and application real-time response. The community already has responsive programming implementations in various languages, including RxJS, RxKotlin, Rx.NET, RxScala, and RxSwift. These libraries differ only in the way they are implemented, so there is no barrier to communication when discussing application logic.

RxSwift as Swift language responsive programming implementation, it in the traditional imperative programming and pure functional programming found a good balance. By defining asynchronous processing inputs using immutable code, RxSwift responds to events in a definable composable form.

In general, RxSwift consists of three main components: Observable, Operator and Scheduler. Let’s introduce them one by one.

Observable

The Observable class is the cornerstone of the RxSwift framework. It can asynchronously trigger a stream of events and carry immutable state variables. In simple terms, it allows an instance of a class to observe the value of another instance object over a period of time. For example, an observer can capture events triggered by all observable objects, enabling real-time UI updates or real-time data processing.

The ObservableType protocol is followed by the Observable class. In addition, an Observable can trigger only three events:

  1. nextEvent: This event, when fired, passes the latest value of the observable to the observer.
  2. completedEvent: This event means that events will not continue to be emitted when the observable’s life cycle ends normally.
  3. errorEvent: This event indicates that an observable error caused its life cycle to end abnormally.

For an observable integer variable, the events it fires in an asynchronous context can be represented on a timeline as a sequence of events like this:

The 2017-09-02-01

In addition, we can combine these three events to achieve more complex business logic. At the same time, we can use this mechanism to easily decouple code and transfer data between multiple objects without writing proxy or closure code.

The 2017-09-02-02

Here, we have another point worth noting. That there are actually two types of observable sequences.

Finite Observable sequences

This sequence refers to the observable that culminates in the final lifecycle of a completed or error event. The most typical example is a network request made through an API:

  1. Start a data request and prepare for data reception.
  2. Receiving the server response Starts to receive data.
  3. If the server or network fails, the request is closed and error handling is triggered.
  4. If all is well, the requested data is processed and analyzed.

Here is the Rx paradigm code for a file download request:

API.download(file: "http://www...")
    .subscribe( onNext: { data in
                        append data to temporary file }, 
                onError: { error in
                        display error to user }, 
                onCompleted: {
                        use downloaded file })Copy the code

The api.download (file:) function creates an Observable instance and fires next events throughout the data receiving process. We then save the fragment data to a temporary file in the Next event. If there is an error in this process, we display the error message to the user. If all goes well we will save the temporary files to the device. Finally, after the download is completed, we can proceed to the next logical processing on Completed.

Infinite Observable sequences

Unlike network tasks, UI and interaction events are infinite sequences of observations. They do not have a clear life cycle endpoint. For example, we need to make layout changes in real time for possible device rotation. The directional rotation of the device itself is random and has the same life cycle as the application itself. So Rx also needs a mechanism to support this infinite sequence of observations.

In RxSwift we can handle this with the following code:

UIDevice.rx.orientation.subscribe(onNext: { current in 
    switch current { 
        case .landscape:
            re-arrange UI for landscape 
        case .portrait:
            re-arrange UI for portrait 
    } 
})Copy the code

The operator

Both ObservableType and Observable implementations contain a number of asynchronous processing methods, also known as operators. Because these operators do only asynchronous input processing and produce corresponding output, they do not have unwanted side effects on the application. In addition, because of the high degree of decoupling between operators, it is easy to combine them to achieve complex functions.

For example, for the device orientation rotation above, we can filter all the cases and then further process some of the values.

UIDevice.rx.orientation
    .filter { value in
        returnvalue ! = .landscape } .map { _in
        return "Portrait is the best!" 
    } 
    .subscribe( onNext: { string in 
        showAlert(text: string) 
    })Copy the code

In the code above, we first filter out all the.landscape directions without doing anything. We then convert the remaining portrait to the string portrait is the best! . The whole processing process is roughly as follows:

The 2017-09-02-03

This kind of functional operator gives us the flexibility to combine more powerful functions.

Scheduler

Schedulers is a parallel concept to GCD, but the former is easier to use. The predefined Schedulers in RxSwift are sufficient for most programming scenarios.

Type, for example, we can use the string sequence SerialDispatchQueueScheduler to deal with the next event, through ConcurrentDispatchQueueScheduler run parallel file download task, Run an NSOperationQueue operation queue with OperationQueueScheduler. You can even use different Schedulers types for different tasks on the same observer, as shown below:

The 2017-09-02-04

We differentiated the tasks on the left with different colors, and then the tasks on the right were divided into different steps and placed in different Schedulers. For example, The Network Subscription task is split into three steps and placed into the Custom NSOperation Scheduler, Background Concurrent Scheduler, and Main Thred Serial The Scheduler.

supplement

It is important to note that RxSwift does not specify the client application architecture. This means that we can introduce RxSwift into our existing projects for responsive programming practices. Of course, there must be one framework out there that works best for RxSwift, and it’s MVVM. Because in MVVM we can bind some of the attributes in the VM directly to the UI.

The 2017-09-02-05

In addition, RxSwift is not enough for iOS programming. RxSwift is just a responsive implementation of the Swift language, and we need a Cocoa level implementation. Fortunately, there is RxCocoa as a responsive complement to UIKit. The previous device orientation code UIDevice. Rx. Orientation is an application of RxCocoa.

conclusion

To start the series, this article introduces some of the basic concepts and components of RxSwift, and more will follow.

The original address