RxSwift and Reactive Programming
What is RxSwift?
RxSwift is a library for composing asynchronous and event-based code by using observable sequences and functional style operators, allowing for parameterized execution via schedulers. RxSwift in its essence simplifies developing asynchronous programs by allowing code to react to new data and process it in a sequential and isolated manner.
It may seem intimidating to understand the concepts behind reactive programming at first but when you get familiar with reactive programming and RxSwift, it will help you to write better apps. Especially when writing asynchronous and event-based code, RxSwift will help you to maintain codebase with minimum effort.
Introduction to asynchronous programming
Asynchronous programming is a means of parallel programming in which a unit of work runs separately from the main application thread and notifies the calling thread of its completion, failure or progress. You may be wondering when you should use asynchronous programming and what are its benefits and problem points.
The main benefits one can gain from using asynchronous programming are improved application performance and responsiveness. One particularly well suited application for the asynchronous pattern is providing a responsive UI in a client application while running a computationally or resource expensive operation.
To explain asynchronous programming in a simple way, let’s give an example from an iOS app. In the iOS app, at any moment, we might be doing any of the following things;
Reacting to button taps
Animating the UIView transitions
Downloading bits of data from remote source
Playing audio or video
Saving bits of data to disk
All things seemingly happen at the same time. Whenever the views are animates, the audio in your app doesn’t pause until animation finished. All different part of your program don’t block each other’s execution. There are various kinds of APIs that allow to perform different pieces of work on different threads, across different execution contexts and perform them across the different cores of the device’s CPU.
To write code that truly runs in parallel is complex, especially when different bits of code need to work with the same pieces of data. It’s hard to know which piece of code updates the data first or which code read the latest value.
Cocoa and UIKit asynchronous APIs Apple has always provided numerous APIs in the iOS SDK that help you write asynchronous code. In fact the best practices on how to write asynchronous code on the platform have evolved many times over the years.
You’ve probably used many of these in your projects and probably haven’t given them a second thought because they are so fundamental to writing mobile apps.
To mention few, you have a choice of:
NotificationCenter: To execute a piece of code any time an event of interest happens, such as the user changing the orientation of the device or the software keyboard showing or hiding on the screen.
The delegate pattern: Lets you define an object that acts on behalf, or in coordination with, another object.
Grand Central Dispatch: To help you abstract the execution of pieces of work. You can schedule blocks of code to be executed sequentially, concurrently, or after a given delay.
Closures: To create detached pieces of code that you can pass around in your code, and finally Foundation of RxSwift Reactive programming has been around for a fairly long time, but its core concepts have made noticeable over last years. In this years apps became more complex and need to managing complex asynchronous UIs. On the server side, reactive systems have become a necessity.
Microsoft offered a new client and server side framework called Reactive Extensions for .NET (Rx) to solve the problems of asynchronous, scalable development. Rx for .NET has been open source since 2012 permitting other languages and platforms to reimplement same functionality which turned Rx into a cross-platform standard.
Today we have RxJs, RxKotlin, Rx.Net, RxScale, RxSwift and more. All try to implement the same behavior and similar APIs, based on the Reactive Extensions specification.
RxSwift works with all the concepts like the original Rx. It tackles mutable state, allows to compose event sequences and improves on architecturel concepts such as code isolation, reusability and decoupling.
Note: You can find more info about Rx implementations at http://reactivex.io
Main building blocks of Rx
Main building blocks of Rx code are observables, operators and schedulers.
Observable
Rx is a generic abstraction of computation expressed through Observable<Element> interface that broadcast and subscribe to values and other events from an Observable stream. The ObservableType protocol (to which Observable conforms) is extremely simple. An Observable can emit (and observers can receive) only three types of events:
A next event: An event that “carries” the latest (or “next”) data value. This is the way observers “receive” values. An Observable may emit an indefinite amount of these values, until a terminating event is emitted.
A completed event: This event terminates the event sequence with success. It means the Observable completed its lifecycle successfully and won’t emit additional events.
An error event: The Observable terminates with an error and will not emit additional events.
This workflow accurately describes the lifecycle of a typical observable. Take a look at the related code below:
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
}
)
API.download(file:) returns an Observable<Data> instance, which emits Datavalues as chunks of data fetched over the network.
You subscribe for next events by providing the onNext closure. In the downloading example, you append the data to a temporary file stored on disk.
You subscribe for an error by providing the onError closure. In this closure, you can display the error.localizedDescription in an alert box or otherwise handle your error.
Finally, to handle a completed event, you provide the onCompleted closure, where you can push a new view controller to display the downloaded file or anything else your app logic dictates.
Operators Observable Type and the implementation of the Observable class include plenty of methods that abstract discrete pieces of asynchronous work and event manipulations, which can be composed together to implement more complex logic. Because they are highly decoupled and composable, these methods are most often referred to as operators.
For example, consider the code you need to react to device orientation changes in your app:
You add your class as an observer to UIDeviceOrientationDidChangenotifications from NotificationCenter.
You then need to provide a method callback to handle orientation changes. It needs to grab the current orientation from UIDevice and react accordingly to the latest value.
This sequence of orientation changes does not have a natural end. As long as there is a device, there is a possible sequence of orientation changes. Further, since the sequence is virtually infinite and stateful, you always have an initial value at the time you start observing it.
It may happen that the user never rotates their device, but that doesn’t mean the sequence of events is terminated. It just means there were no events emitted.
Here’s the example about observing orientation changes with using some common Rx operators:
UIDevice.rx.orientation
.filter { $0 != .landscape }
.map { _ in "Portrait is the best!" }
.subscribe(onNext: { string in
showAlert(text: string)
})
Each time UIDevice.rx.orientation produces either a .landscape or .portraitvalue, RxSwift will apply filter and map to that emitted piece of data.
First, filter will only let through values that are not .landscape. If the device is in landscape mode, the subscription code will not get executed because filter will suppress these events.
In case of .portrait values, the map operator will take the Orientation type input and convert it to a String output — the text "Portrait is the best!"
Finally, with subscribe, you subscribe for the resulting next event, this time carrying a String value, and you call a method to display an alert with that text onscreen.
Schedulers
Schedulers are the Rx equivalent of dispatch queues or operation queues — just on steroids and much easier to use. They let you define the execution context of a specific piece of work.
RxSwift comes with a number of predefined schedulers, which cover 99% of use cases and hopefully means you will never have to go about creating your own scheduler.
RxSwift will act as a dispatcher between your subscriptions (on the left-hand side below) and the schedulers (on the right-hand side), sending the pieces of work to the correct context and seamlessly allowing them to work with each other’s output.
To read this diagram, follow the colored pieces of work in the sequence they were scheduled (1, 2, 3, ...) across the different schedulers. For example:
The blue network subscription starts with a piece of code (1) that runs on a custom OperationQueue-based scheduler.
The data output by this block serves as the input of the next block (2), which runs on a different scheduler, which is on a concurrent background GCD queue.
Finally, the last piece of blue code (3) is scheduled on the Main thread scheduler in order to update the UI with the new data.
App architecture
It’s worth mentioning that RxSwift doesn’t alter your app’s architecture in any way; it mostly deals with events, asynchronous data sequences and a universal communication contract.
It’s also important to note that you definitely do not have to start a project from scratch to make it a reactive app; you can iteratively refactor pieces of an exiting project or simply use RxSwift when building new features for your app. You can create apps with Rx by implementing a Model-View-Controller architecture, Model-View-Presenter, or Model-View-ViewModel (MVVM), or any other pattern that makes your life easier.
We can create apps with Rx by implementing a Model-View-Controller architecture, Model-View-Presenter, or Model-View-ViewModel (MVVM), or any other pattern that makes your life easier. RxSwift and MVVM generally specifically fits each other.
RxCocoa
RxSwift is the implementation of the common, platform-agnostic, Rx specification. It doesnt know anything about any Cocoa or UIKit-specific classes.
RxCocoa is RxSwift’s companion library holding all classes that specifically aid development for UIKit and Cocoa. It features some advanced classes, RxCocoa adds reactive extensions to many UI components so you can sunbscrive to various UI events out of the box.
For example it’s very easy to use RxCocoa to subscribe to the state changes of a UISwitch, like this:
toggleSwitch.rx.isOn
.subscribe(onNext: { isOn in
print(isOn ? "It's ON" : "It's OFF")
})
RxCocoa adds the rx.isOn property to the UISwift class so we can subscribe to useful events as reactive Observable sequences.
Reactive vs Imperative Programming
Why RxSwift?
If your application reacts a lot to user integrations, RxSwift is here to clean up your massive codebase. Let’s take a look at an example of what does it look like with RxSwift.
Example:
Let’s say you have a block of code inside updateUI function. You only want to execute it once the value of isLiked has changed.
Without RxSwift(Imperative Programming):
var isLiked: Bool = false {
didSet {
if isLiked != oldValue {
updateUI()
}
}
}
With RxSwift:
let isLiked = Variable(false)isLiked.asObservable()
.distinctUntilChanged()
.subscribe(onNext: {
updateUI()
})
Example:
To prevent the feature’s abuse, you may not want to respond after a certain amount of user integrations. For example, you may only want to respond to the first five changes.
Without RxSwift:
var likedCount = 0
var isLiked: Bool = false {
didSet {
if isLiked != oldValue && likedCount < 5 {
updateUI()
likedCount += 1
}
}
}
With RxSwift:
let isLiked = Variable(false)isLiked.asObservable()
.distinctUntilChanged()
.take(5)
.subscribe(onNext: {
updateUI()
})
Example:
Let’s say you don’t want to respond if the value is updating too fast. For example, you don’t want to take action if the value of isLiked has been updated more than once within one second.
Without RxSwift:
var likedCount = 0
var isLikedEnabled = true
var isLiked: Bool = false {
didSet {
if isLiked != oldValue && likedCount < 5 && isLikedEnabled {
updateUI()
likedCount += 1
isLikedEnabled = false Timer.scheduledTimer(withTimeInterval: 1000, repeats: false) { _ in isLikedEnabled = true
}
}
}
}
With RxSwift:
let isLiked = Variable(false)isLiked.asObservable()
.debounce(1)
.distinctUntilChanged()
.take(5)
.subscribe(onNext: {
updateUI()
})
Conclusion
Reactive Programming is actually way more powerful than what you’ve seen. Even though I did not showcase everything Rx can do, but I think the outcome is very obvious. As you can see from the above examples, as the app becomes more complex, the benefits of using RxSwift will become more outstanding.
Reactive applications are solid, easier to test and more agile about their user experience. With data bindings, your apps UI is always up to date and allow you to craft complex app logic with minimum effort.
References:
https://www.raywenderlich.com/books/rxswift-reactive-programming-with-swift/v4.0/chapters/1-hello-rxswift
https://medium.com/ios-os-x-development/learn-and-master-%EF%B8%8F-the-basics-of-rxswift-in-10-minutes-818ea6e0a05b
- Geri Dön
- 18 dk okuma
-
Securvent