📜 ⬆️ ⬇️

Implement MVVM in iOS using RxSwift

There are countless articles about the MVVM pattern in iOS, but a little about RxSwift, and few people focus on how the MVVM pattern looks in practice and how to implement it.

ReactiveX

ReactiveX is a library for creating asynchronous and event-based programs using the observed sequence. - reactivex.io
')
RxSwift is a relatively young framework that allows " reactive programming ." If you don’t know anything about it, then look around because functional reactive programming (FRP) is gaining momentum and is not going to stop.

So, what does MVVM look like in iOS?


The way to connect the model with the ViewController when using the MVC pattern often looks like a kind of hack. You typically call something like the updateUI () function in the controller when you think the model has changed. This can lead to inconsistencies between the model and the ViewController, unnecessary updates and incomprehensible errors.

We need a ViewController that will show the true state of the model anyway. In fact, we need a ViewController, which will be a simple proxy server that will display data on the screen according to the current state of the model.

Of course, in most applications it will be useless if they only display a model on the screen. We need to get data from the model and prepare it for display. That is why we present the ViewModel class. ViewModel prepares all the data that you want to display on the screen.

But the most interesting: ViewModel knows nothing about the ViewController. It never refers to it and does not have a property directly. Instead, ViewController constantly monitors any changes to the ViewModel, and as soon as the changes occur, they will immediately be displayed on the screen. It should be borne in mind that the ViewController displays each property on the screen with the ViewModel individually, i.e. if you want to load a row and an image one by one, you can display the row right away because the data is already there, and you don’t have to wait until the image is loaded to display it together.

However, ViewController displays not only data on the screen, it also receives user input. Since our ViewController is just a proxy server, it cannot use this data, so all it has to do is transfer it to the ViewModel, and the ViewModel will do the rest.

image

This is, in a sense, one-way communication between ViewController and ViewModel. The ViewController can see and access the ViewModel, but the ViewModel has no idea what the ViewController is. This means that you can completely remove the ViewController from your application, and all your logic will work as intended!

It all sounds great, but how do we do it?

MVVM in conjunction with RxSwift


Let's make a simple application that displays the weather forecast in a city that a user enters.

This article assumes that you are familiar with RxSwift. If you do not know anything about him, then boldly read on, but I suggest learning more about ReactiveX .

post_images

We have a UITextField for entering the city name and a couple of UILabels to display the current temperature.

Note: for this application, I will receive weather data from the OpenWeatherMap .

So, our model will be a Weather class with several name and degrees properties and an initializer that accepts a JSON object, which it analyzes and sets properties.

class Weather { var name:String? var degrees:Double? init(json: AnyObject) { let data = JSON(json) self.name = data["name"].stringValue self.degrees = data["main"]["temp"].doubleValue } } 

Note: SwiftyJSON is required to parse JSON into Swift.

Now we need to allow the ViewModel to control the model through the public (public) property of searchText, to which the ViewController will later have access.

 class ViewModel { private struct Constants { static let URLPrefix = "http://api.openweathermap.org/data/2.5/weather?q=" static let URLPostfix = "/* my openweathermap APPID */" } let disposeBag = DisposeBag() var searchText = PublishSubject<String?>() 

Our searchText is a PublishSubject object. Subjects are both Observable and Observer. In other words, you can send them elements that they can regenerate.

PublishSubjects are unique because when data is sent to the PublishSubject, it sends it to all subscribers who are currently subscribed to it. We need this, because in MVVM, depending on the life cycle of the application, Observable in different classes can sometimes receive elements before you subscribe to them. As soon as the ViewController subscribes to the ViewModel property, it should see that everything is in order with the last element in order to display it, and vice versa.

Now we have to declare a property in the ViewModel for each UI element that you want to change programmatically.

 var cityName = PublishSubject<String>() var degrees = PublishSubject<String>() 

Let's also set a property for our model and change the properties each time our model changes. We do this by combining the 'old-fashioned' method (Swift Property Observers) using Rx. We will send the properties of the Weather object to our PublishSubjects so they can generate values ​​in the model.

 var weather:Weather? { didSet { if let name = weather?.name { dispatch_async(dispatch_get_main_queue()) { self.cityName.onNext(name) } } if let temp = weather?.degrees { dispatch_async(dispatch_get_main_queue()) { self.degrees.onNext("\(temp)°F") } } } } 

Note: We need to make sure that this is done in the main thread, since the onNext () method runs in another thread! (the onNext method sends an Observer element).

Now let's attach our model to the searchText property that we declared above. We will do this by creating an NSURLRequest every time changes are made to searchText, and then we will sign our model to that request. We do this in the init () method, because we know that all our properties are set when the init () method is called.

 init() { let jsonRequest = searchText .map { text in return NSURLSession.sharedSession().rx_JSON(self.getURLForString(text)!) } .switchLatest() jsonRequest .subscribeNext { json in self.weather = Weather(json: json) } .addDisposableTo(disposeBag) } 

Thus, each time the searchText is changed, jsonRequest changes itself to the corresponding NSURLRequest. Every time it changes, our model receives the data we received from NSURLRequest.

Note: The rx_JSON () method is actually an observable sequence. Thus, jsonRequest is actually Observable from Observable. This is why we use .switchLatest (), which ensures that jsonRequest only returns a new sequence. Also keep in mind that the request will not begin sampling until you subscribe to it.

Now it remains to connect the ViewController with the ViewModel. We will do this by binding PublishSubjects in the ViewModel to the outlet in the Controller.

 class ViewController: UIViewController { let viewModel = ViewModel() let disposeBag = DisposeBag() @IBOutlet weak var nameTextField: UITextField! @IBOutlet weak var degreesLabel: UILabel! @IBOutlet weak var cityNameLabel: UILabel! override func viewDidLoad() { super.viewDidLoad() //Binding the UI viewModel.cityName.bindTo(cityNameLabel.rx_text) .addDisposableTo(disposeBag) viewModel.degrees.bindTo(degreesLabel.rx_text) .addDisposableTo(disposeBag) } } 

Don't forget that we also need to make sure that our ViewModel knows what the user entered in the text field! We can do this by sending the nameTextField value, each time it changes, to the searchText property of the ViewModel. So we just add this in the viewDidLoad () method:

 nameTextField.rx_text.subscribeNext { text in self.viewModel.searchText.onNext(text) } .addDisposableTo(disposeBag) 

image

Like this! Now the application receives weather data, while the user enters the name of the city, and no matter what the user sees, the true state of the application remains hidden.

If you are interested in the enhanced version of this app, see my example of a weather app on Github .

Source: https://habr.com/ru/post/273455/


All Articles