Table of Contents
Preface
In this article, I will share my experience how to refactor the codebase from MVC to MVVM-C. I will start with a simple example application, and do the refactoring step by step slowly to show you every key points of architecture pattern in iOS development.
Introduction of the Demo application
I will create an application which is cloned from some applications about cryptocurrency market on Appstore. The application doesn’t touch any buying or selling actions, it just show the marketing data from Open APIs, so you don’t worry about your wallet :)
It is a very simple example, including only four main pages.
The “Prices” show the current marketing prices, you can input the name of cryptocurrency which you interested in to filter the table list, and you can also sort the order by click the name, price and change label on the section header.

Swipe the table items in the price list, you can save these items into your favorite list. In the “My Favorites”, you can swipe the item to remove it from your favorite list.



Click the item in the Price list, the item will be expanded to show a kline diagram.

At the right cover of the price page, there is a setting button which will pop a setting page to let you choice the data source of the kline. You have two options, one is from Huobi, the other is from Crypto Compare. You can also click the switch to save your favorites then they will be reloaded when you open the application next time.

That’s all. Though it is an really simple example, it still cover lots of knowledges in iOS development. But this article only focuses on some topics listed below:
- the architecture patterns:
From MVC, MVVM, MVVM+RxSwift to MVVM+RxSwift+Coordinator, so many acronyms, which architecture is the best? I think that none of them is the “Best” one. There is “No Silver Bullet”. Each one can suit better a specific scenario. But we must follow some principles. In this article, I will share my experience and list the pros and the cons of each pattern, and talk about how to make choice when I design an application. - how to implement the user interface:
The first thing in writing an application is implementing the user interface. But sometime we need essential data to help us to design the UI. Where these data come from? Do we need to write the networking layer first and fetch data from the backend? Or just grab some data through branch of http requests and save these data as an asset file in JSON format?
But there is another good idea that we can use some data mock platforms to generate these data automatically.- Mockaroo

- Easy Mock(大搜车)
This is also an open source solution, you can build your own data mock platform on your computer.
- Mockaroo
- how to build a networking layer
After we finish our UI design, we must create service modules to connect the real world to get the data and present these data onto our UI components. If you are lazy, you can import a third party solution, the popular one is Alamofire. It is very easy to use and very powerful. But in this article I will build my networking layer from scratch. You can reference this paper Writing a Network Layer in Swift: Protocol-Oriented Approach. Malcolm Kumwenda is a good guy to help you create you own networking layer step by step.
Why do I need to write networking layer by myself? One principle is we must implement the core module by ourselves and if these module are not very difficult to implement. Be careful to depend on a third party source, particularly when lots of other modules depend on it. You will take high risk to rely on these third party solutions. - how to test your modules.
The deliver quality is based on how we dedicate on testing. And all outputs of refactoring the architecture are let us more easy to test our code. ViewModel separated from ViewController is more helpful to test the business logic and the presentational logic. - immigrate from procedural programming to functional Reactive Programming
Immigrating from procedural programming to declarative programming is just like people who immigrate to a new country where the language is totally different.
the life is hard, but it is still going on. If you estimate that the business logic and the state of your application will become more complicated as time goes by.
The functional programming paradigm was explicitly created to support a pure functional approach to problem solving. Functional programming is a form of declarative programming. In contrast, most mainstream languages, including object-oriented programming (OOP) languages such as C#, C++, and Java, were designed to primarily support imperative (procedural) programming.
Design Our UI
Understanding how and when a view updates requires a deeper understanding of the UIKit framework. This article focuses on the architecture patterns, so we will not talk more about the UI staffs. But the topics I discuss below are very important to you to build an application as quickly as possible.
Storyboard or Programmatic is a problem
Here is the whole picture of our pages in the storyboard.

There are still lots of debates about whether to use storyboard or write UI programmatically. You can find a lot of articles to summarize the pros and cons on both sides(Why I Don’t Use Storyboard and Storyboard vs Programmatically in Swift). These are guidelines when I need to make a choice at the crossroads.
| Storyboard | Programmatic |
|---|---|
| Ease to use | Reusability |
| Visualization | Merge Conflict |
In a big project, the choice is depended on your team and the consensus made in your team. I prefer using storyboard to create static view objects and doing it programmatically when I create visual objects dynamically.
When I teach, I don’t wanna make my audience fall asleep. So, why not make it a little more interesting since seeing is believing.🤔
Because this is a demo application, and the UI is very simple, so using storyboard is good choice.
How many view controllers do we need?
UX/UI designer create the wireframes by using Sketch. Sometimes we just map the pages in the sketch file to view controllers, they are almost one-on-one mapping. But be careful! Before apply other architecture paradigm, we start from MVC, and view controllers in MVC mode are so heavy, so discuss the features with your UX/UI designers or product designers first if you don’t want to mess up. Sometimes we must separate some of features in one page and move them into other view controllers. These view controllers are embedded in the “host” view controller as child view controllers. In our demo application, when we click on the item in the tableview, the cell will be expanded and show the kline of selected cryptocurrency, the expanded view in the cell view need a child view controller to coordinate the kline data service and the view who show the kline. So I dynamically create a view controller named “ExpandViewcontroller” as a child view controller embedded in “CoinListViewController”.
Expand the cell view and add the “ExpandViewcontroller” as a child view controller,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15expandViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ExpandViewController") as! ExpandViewController
self.addChildViewController(expandViewController)
cell.expandView.addSubview(expandViewController.view)
expandViewController.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
expandViewController.view.leadingAnchor.constraint(equalTo: cell.expandView.leadingAnchor),
expandViewController.view.trailingAnchor.constraint(equalTo: cell.expandView.trailingAnchor),
expandViewController.view.topAnchor.constraint(equalTo: cell.expandView.topAnchor),
expandViewController.view.bottomAnchor.constraint(equalTo: cell.expandView.bottomAnchor)
])
expandViewController.didMove(toParentViewController: self)
Fold the cell view when we click the cell again,
1
2
3expandViewController?.view.removeFromSuperview()
expandViewController?.removeFromParentViewController()
expandViewController = nil
AutoLayout and constraint
Being familiar with UIKit is very important for developing your user interface. And you must be prepare to move beyond the basics, and dive into the UI framework. When you do start writing iOS apps, you’ll have a solid and rigorous
understanding of what you are doing and where you are heading.
There are three stages before views can be displayed on the screen. The first step is “updating constrains”, which is form subviews to their superviews(bottom-up). It will prepare the information needed for the second step called “layout”. In this step, frames(centers and bounds) of views will be set depending on the constrain system. Finally the “display” pass renders the views on the screen.
Before you take control of layout, you need to know how to trigger the override functions in these three steps, and when to trigger these functions. The table below lists all relative functions in these steps.
| Method purposes | Constraints | Layout | Display |
|---|---|---|---|
| Implement updates (override, don’t call explicitly) | updateConstraints | layoutSubviews | draw |
| Explicitly mark view as needing update on next update cycle | setNeedsUpdateConstraints invalidateIntrinsicContentSize | setNeedsLayout | setNeedsDisplay |
| Update immediately if view is marked as “dirty” | updateConstraintsIfNeeded | layoutIfNeeded | |
| Actions that implicitly cause views to be updated | Activate/deactivate constraints Change constraint’s value or priority Remove view from view hierarchy |
addSubview Resizing a view setFrame that changes a view’s bounds (not just a translation) User scrolls a UIScrollView User rotates device |
Changes in a view’s bounds |
A good example of changing the local constraints
1 | // Create a new property to hold the aspect ratio constraint of an imageView |
Change constraints with animations
If you want to replace the default behaviors of constraints animation, you must call the layoutIfNeeded function in the animation block, otherwise the change of the constraint will be executed in the update cycle of the run loop, so the animation block you defined will be useless.
1 | func collapseHeader() { |
In the updateConstraints() function, you can’t invalidate any constraints, otherwise you will get a crash because your program has already been in the layout cycle.
You can change the properties which are relative with the constraints, then invalidate constraints and trigger the layout process in the event process stage of the main run loop, then the system will call the callback function updateConstraints you override in the update cycle . In this function, you can change/remove/add constraints which depending on the properties changing before. This guideline can also apply to the process of layout and display.
There are some scenarios about how to fine-tune the frames of your views by overriding the layoutSubviews function. The first one is not necessary, because you can add a constraint, but it is an example.
1 | - layoutSubviews |
There is a trick. Because the layout process is top-down, you can get the frame of the current view before you call the super function layoutSubviews. After the super function layoutSubviews being called, the subviews of this view also get their frames, so you can get the sizes and positions of its’ subviews. In this case, we compare the width of the first subview, and change the layoutRows property, then call its’ super layoutSubviews to fire the autolayout process from the updating constraints step again.
You can also subclass your first subview(in this case), and override the layoutSubviews function in its’ subclass, and get its’ frame like the next example which fine-tune the width of a multi-line label
1 | @implementation MyLabel |
Or you can make this adjustment at the view controller level, put this logic into the viewDidLayoutSubviews function. Because the autolayout process is finished, so you must re-fire this process by calling layoutIfNeeded
1 | - (void)viewDidLayoutSubviews |
StackView with Fill Distribution
Core Animation’s models, classes and blocks
Animation in iOS is huge topic, but the animation process can be as simple as changing properties during a given time. It includes two main animation systems. One is based on “View Animation”, and the other is called “Core Animation”. I only list some key issues about Animation.
View Animation
There are three stages for “View Animation” because of the historical evolution of iOS. I just give some snippet codes below for each of these stages.
Begin and commit
1
2
3
4UIView.beginAnimations(nil, context: nil)
UIView.setAnimationDuration(1)
self.v.backgroundColor = .red
UIView.commitAnimations()Block-based animation
1
2
3UIView.animate(withDuration:1) {
self.v.backgroundColor = .red
}Property animator
1
2
3
4let anim = UIViewPropertyAnimator(duration: 1, curve: .linear) {
self.v.backgroundColor = .red
}
anim.startAnimation()
Implicit Layer Animation
Before we jump to “Core Animation”, we also can change properties on CALayer. It is called “Implicit Layer Animation”. Just set layer properties, and your layers animate in the default way. You can not control the animation except you use a “CATransaction” with “Implicit Layer Animation”. And remember, there is always an implicit transaction surrounding your code, and you can operate on this implicit transaction without any begin and commit.
1 | // can be omitted |
Core Animation
“Core Animation” is also called “Explicit Layer Animation”. To specify a property using a keyPath, we can create an CABasicAnimation object or its inheritance, then we add this object onto the layer of a view. There are two problems when we use “Core Animation”. One is setting a property on a layer will fire the “Implicit Layer Animation”, We can prevent this side effect by disabling actions:
1 | CATransaction.begin() |
The other problem is if we use “Core Animation”, the property will not change to the value when the animation is end, because “Core Animation” create a new layer to present the animation, called “presentation layer”. So we need it set the new value into the property of its “model layer” in the completion block.
If you use “Implicit Layer Animation” or “View Animation”, you don’t need to care about these problems, because the Animation framework will handle these for you. But when you use the “Core Animation” which is the fundamental underlying iOS animation technology, you get more powerful, so you need more responsibility.
Transitions
Another concept called “Transitions” is very confused, if you are not a native English speaker. Usually we use “Transitions” in two situations. We can change the content of a view such as the image of a UIImageView using “transition(with:duration:options:animations:completion:)” function:
1
2
3
4let opts : UIViewAnimationOptions = .transitionFlipFromLeft
UIView.transition(with:self.iv, duration: 0.8, options: opts, animations: {
self.iv.image = UIImage(named:"Smiley")
})
Or we can replace the first view with the second view by using “transition(from:to:duration:options:completion:)” function.
1
2
3
4
5
6
7 let lab2 = UILabel(frame:self.lab.frame)
lab2.text = self.lab.text == "Hello" ? "Howdy" : "Hello"
lab2.sizeToFit()
UIView.transition(from:self.lab, to: lab2,
duration: 0.8, options: .transitionFlipFromLeft) { _ in
self.lab = lab2
}
Custom UIViewController Transitions
The transitioning API includes several components which conform a collection of protocols. The diagram below shows these relative components:

Here is the 7 steps involved in a presentation transition:
Before You trigger the transition either programmatically or via a segue, you need to set the transitioningDelegate property in your “to” view controller. In this case, the transitioningDelegate is the “from” view controller. The “from” view controller in here is also the presenting view controller. When you trigger the transition via a segue, the override function prepare(for:)_ in the presenting view controller will be called, in this function, you can get the “to” view controller(also called “ presented “) form the segue destination. Then you can set the transitioningDelegate property of the destination to itself.
1
2
3
4
5
6
7
8
9override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
segue.destination.transitioningDelegate = self
if let navigationController = segue.destination as? SettingsNavigationController,
let settingsViewController = navigationController.viewControllers.first as? SettingsViewController {
settingsViewController.delegate = self
}
}UIKit asks the “presented” view controller (the view controller to be shown) for its transitioning delegate. If it doesn’t have one, UIKIt uses the standard, built-in transition. In here the delegate is the presenting view controller. The transition delegate conforms two methods of UIViewControllerTransitioningDelegate protocol . One is for presenting, the other is for dismissing.
- UIKit then asks the transitioning delegate for an animation controller via animationController(forPresented:presenting:source:). If this returns nil, the transition will use the default animation. The transition delegate create and return an animation controller who confirms the UIViewControllerAnimatedTransitioning protocol
- UIKit constructs the transitioning context. Be careful, if you want to see the presenting view controller behind the presented view controller, you need to set the presentation style to .overFullScreen or .overCurrentContext. If you set this property to .fullScreen, the view of the presenting view controller will be replaced
- UIKit asks the animation controller for the duration of its animation by calling transitionDuration(using:).
- UIKit invokes animateTransition(using:) on the the animation controller to perform the animation for the transition.
- Finally, the animation controller calls completeTransition(_:) on the transitioning context to indicate that the animation is complete.
And the steps for a dismissing transition are nearly identical.
When you tigger the reserves process, UIKit asks the transitioning delegate for an animation controller animationController(forDismissed dismissed:), then repeat the steps from 4 to 7.
Here is the animation controller code who play the animation role:
1 | class PresentTransitionController: NSObject, UIViewControllerAnimatedTransitioning { |
Add Gesture On the Section header
Connect with internet
We already have got the “Views” in MVC pattern, it is time to build the “Models” and create networking service to connect with our backend. There are lots of approaches to build the “Models” and networking service.
The most formal way is introducing some mocks and stubs before creating real data models and connecting the endpoints. The frontend developing will not depend on the progress of the backend developing. As I mentioned before you can use the data mock platform to mock the data we need, some of these platform are open source. You can setup your own data mock platform on your laptop, and you also can use their online service to create data, then download and save to a JSON file as a resource embedded into your application bundle. The other benefit is you can use these mock data to test your business logics and application logics. You will not stuck in debugging with backend engineers and complain each other, and the testers who get theirs test sets can help you test your application and business logics.
If the backend service is ready and stable. You can skip the step to build the mock data and stubs. Importing a third party module is also a good choice. Alamofire is one of the famous open sources. Using a heavy library sometimes you must be careful, if you don’t want to relay on this one.
There are an assumption that building a networking framework is a big challenge. Implementing the basic feature I think is not difficult, you can reference this topic “Writing a Network Layer in Swift: Protocol-Oriented Approach
“ from Malcolm Kumwenda. This guy write a good article to teach you how to build your own networking layer step by step.
First I wrote a http request using the native API URLSession, extend each model with a JSON initializer, handle the nested objects. When I get the data from a http request, I map the JSON format into a corresponding object.
1 | extension Ticket { |
1 | let url = URL(string: "https://api.coinmarketcap.com/v2/ticker/")! |
It is not a bad idea, if you write a simple example, but it will turn to a disaster quickly when you write a real application. But I don’t want to use the Alamofire right now. So I followed Malcolm Kumwenda to create my own networking library, base on his codebase, I make a little change to support multi data sources.
The diagram is showed below, there three main roles in this diagram
Different endpoints in every data source are defined in different enums(Huobi API, CoinMarketAPI and CryptoCompareAPI), these enums all conform EndPointType protocol, you can get http parameters, paths and methods from these variables in this protocol, the return values depend on the values of the enums.
Router is a generic class. Giving different EndPoints, we can create different routers. The responsibility of this Router is prepare the parameters depending on the given endpoints, then trigger the real url sessions.
Our application involve three websites: Huobi, CoinMarket and CryptoCompare. So we create three NetworkManagers for each site. These NetworkManagers are all singleton, creating a router using an endpoint to replace the placeholder is their only jobs, then inject this router into themselves. These NetworkManagers all inherit form NetworkManager who handle the errors during the networking requests, and parse the JSON result form backend. It aslo managers all tasks created by the router. You can cancel these tasks by unique id.
NetworkEnvironment is a singleton class which distinguishs different running environment:
1
2
3
4
5enum NetworkEnvironment {
case qa
case product
case staging
}
In Swift 4 they provide Decodable protocol, I use this to convert my JSON objects to an equivalent Struct or Class. Sometimes you don’t need to write a single line, but if the names of the properties in our Classes are different from the keys returning from backend, we need to build the mapping.
1 | //MARK: Listings |
The Bridge - Controllers
Now we have the two important elements “Views” and “Models” in MVC pattern. We will finish our business logics and application logics in our “Controllers” to connect our “Views” with our “Models”.
The main feature of this demo application is fetch data from three websites who provide real time exchange infos of cryptocurrencies, and show these data in a tableview. Thee is no business logic at the front end, and this application only contains some simple application logics:
- Filter the result got from the network by typing some keywords at the top of the tableview.
- Choice whether or not to list tokens by clicking a switch of selecting cryptocurrency type.
- Sort the list in a descending or ascending order by name or price or change rate.
- You can swipe the tableview item in the price view controller to add this cryptocurrency into your favorite list. You can also remove it from your favorite list. This favorite list will be saved, and will be reload when this application is being launched.
- When you click the item, the tableview cell will be expanded, and show the kline of the selected cryptocurrency in a chart.
- As a demo application, I add a feature that you can select different data sources of this kline in the setting view controller.
- It will pop a webview to present the detail information of the selected cryptocurrency, when you tap the “eye” on the right top of the expanded chart view.
Writing these application logics is not a big challenge. After I added a few lines of code, I can filter this list by some keywords and type. But when I was adding the sorting logic, I was aware that I had already messed around the view controllers.
1 | let ticker = showCoinOnly ? (whichHeader == .coin ? sortTickers(_tickers.filter({ !$0.isToken }))[indexPath.row] : _tickers.filter({ !$0.isToken })[indexPath.row]) |
But when I wanted to display the result on the tableview, Only three conditions I needed to apply on the result, this expression made me crazy, and I put this snippet in every place in the tableview controller. I realized that this code must be used and can be readable. All of the functions in the above code is filtering and sorting when some conditions is changed.

This diagram is more like some figures in reactive programming article, this is the main motivation why we need to change from functional programming paradigm to imperative mode. Let me finish the MVC part, because we still meet other problems. So I create an extension of Array where its element must be “Ticker”. Then I moved this logic into the “milter” function in this extension.
1 | extension Array where Element == Ticker { |
In the tableview delegator or datasource, I replaced the old ones with1
2
3let _tickers = tickers.milter(filterBy: self.lowercasedSearchText,
separatedBy: Section(section: indexPath.section),
sortedBy: _sorted)
I cleaned my room, but the story wasn’t end. The sorting logic start from the user clicking on the section header. There are three section headers, one is for coin type, one is for tokens and the other is for my favorite list. And each of these section has three types of sorting. To keep this sorting statuses, I also introduced lots of “Variables” to present the status of sorting logic. These code snippet are scattering into several classes. If I didn’t check the code for some days and reviewed it again, I would almost forgot their relationship with the sorting logic.
The kline chart is embedded in the ExpandViewController, and the ExpandViewController is appeared in two view controllers, one is the price view controller, the other is the Favorite viewcontroller, so the ExpandViewController has two instances. And these instances have different life cycle. We change the kline datasource from the Setting view controller, this change can’t be conveyed to every one. There is no good method to cache this change, except using a global singleton object to store this status. That is why I created a class called “KLineSource”. Too many global object means disaster will come. But the MVC model, I have no choice, if I don’t want to keep this status in a lot of objects, and transfers this status from one object to the other one. It will be more uglily than the method before.
1 | class KLineSource { |
This is a simple demo application. So I only met these two main barriers. One is how to maintain relative statuses, and connect them effectively, some statuses are changed, some one will follow. Another is how to keep some statuses that we can access them convenience. I work around these issues, the codebase looks uglily now. And I have no idea how to test these code. So far I have finished the application under MVC pattern. You can check the code out here.
I think the MVVM pattern only brings us branch of new classes, if we don’t introduce the reactive framework. If we separate some logics from the massive view controllers, it will help us to test our application logic, but I am not sure about it is useful. Our real problem is how to synchronize these statuses distributed in different objects. The RxSwift give us a better solution to connect the statuses together. So I will jump to the MVVM w/ RxSwift in the next section.
MVC-RxSwift

When I separate our application into different objects with MVC pattern, we need some methods to exchange informations betwen these objects. In Apple’s framework, there are seveval ways we can choice, from delegates, callback blocks, notifications, KVO, to target/action event observers. It is convenient for us to glue these objects together. But it is also confused to us. Sometimes how to choice what kind of technology is a challenge. The big problem is we aslo need to connnect these different asynchronization methods in an appropriate way. In this process, we might create more additional varables to keep the status of the applcation.
It’s time to introduce the RxSwift. In RxSwift, an Obseravble can emit a sequences of event. And this event contains some kind of value. RxSwift also provide lots of operators to convert and combine these events whatever you want. The different kinds of Subjects are powerful tools we can use to bond these sequences to create reactive chains. When we subscribe these sequences, we will get the responses automatically.
Though RxSwift is a powerful tool, but it seems like a totally different language. We need to change our minds to master the new language. And the learning curve of Swift is higher than learning Swift as an Objective-C programmer. Swift and Objective-C are different dialects in a same language. But when we use RxSwift, we just like talking with Alias. There is no shortcut to master the new language. Just use try and use it.
How to encapsulate networking API using RxSwift
The first thing to me is encapsulating callback methods in networking API with RxSwift Observable. I added a new function called getDataFromEndPointRx to convert the callback into an Observable. And the Observable is a disposable object, I put the cancel logic in a callback function when the disposable object is created. So when the Obseravble object is released, It will cancel the http request automatically.
1 | func getDataFromEndPointRx<T: Decodable>(_ endPoint: EndPoint, |
In our application, we use Filesystem APIs to save our favorite cryptocurrencies. The Filesystem APIs are more like our networking APIs. We can use the same methodology to convert the Filesystem APIs into RxSwift Observables. But in this application, I didn’t do that, the main reason is our saved data is very small, and we can treat these APIs as synchronized function calls. So I think is not necessary for us to encapsulate them. I just subscrible the actions, and call these functions when the actions take place.
1 | _selectRemoveMyFavorites.subscribe(onNext: { |
How to handle the error events emmited from networking API, Materialize and Dematerialize
Our networking APIs have not the ability to handle the errors, they just conduct these errors from the bottom to the applcation layer. How to handle these errors is depended on uour application logic. Without RxSwift, every thing is ok. But when we convert the callback function into Observable onject, the chain will be broken, because I send the errors through the onError event. After the onError event is sent, the Observable object will be terminated, this is not we want, but we still want to get these error messages in the application layer. How can we get them? In RxSwift, they provide a mechanism called Materialize. It add a shell on the onError event, then this event turns into a normal event, and the error event become a value inside the normal event. Then we can use a filter operator to catch the error event when it happans. If it is not a error event, then the filter will dematerialize the event, we will get the original data again.
1 | huobiReload.withLatestFrom(symbol.asObservable()) |
The Houbi API need me to provide correct symbol name, but I get the symbol name of cryptocurrency from CoinMarket. Sometimes the names of these symbols are not One-to-one correspondence. In this demo application, I don’t want to handle this situation, So i will get an error return from the Huobi API, when I switch from CryptoCompare to the Huobi API.
Use RxSwift Extensions: RxDataSource
Now we get the data, we can bind these data to views. RxCocoa has already encapsulated lots of controls in UIKit, but it is not enough when we encount some complex controls like TableView or CollectionView. At this situation, we need to use the extension framework from RxSwift community. RxDataSource makes us work more easy. First we create a datasource which is comfirmed SectionModelType protocol. This datasource also define a callback how to config tableview cell when we get the datasource item.
1 | dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String, Ticker>>( |
After we get the data from Observable of the networking API, we map the event from the [Ticker] to [SectionModel<String, Ticker>], and bind this sections into the tableview. The RxDataSource solved our multi sections problem.
1 | Observable.combineLatest(_coins, __tokens) { |
1 | extension PricesViewController { |
Connect all Observables and Observers together
Distinguishing from the MVC pattern, we connect all of the Observables and Observers together and gather all application logics like filtering, sorting and separating, etc in one place. I drew a whole diagram of these logics to show how different it is between using RxSwift and the MVC model.
In same cases, I use filter operator to activate different path in the reactive chain. And using withLatestFrom operator to tigger one event by the other event. Changing Kline datasource is an event emitted from a segment control in the Setting ViewController, and showing the Kline need another event which is changing the symbol name. The changing datasource event will trigger the changing symbol name event.

Interaction between view controllers from delegate to obserable and observer
After I established the reactive chains to replace the delegates in the view controllers, I deleted some of properties who refer to the data models in the Price View Controler and Favorite View Controller. And I don’t need to keep the intermedia statuses for the appplication logics, but It doesn’t mean we can remove all statuses, these statuses are useful in exchanging informations between view controllers. If the object owned by the view controller, I think you can use the Observable in the object through the property directly. But most of time, I need to create a stub in the target view controller like the Price View Controller, and bind this stub with the Observable in the source view controller like the Setting ViewController. When the SettingViewController is removed from the memory, the Variable showCoinOnly will be disconnected from its source and sleep. When the SettingViewController is created again, the connection will be rebuilt, and the showCoinOnly will resend the event from the SettingViewController to the filtering logic chain as a router.

In this diagram, you can see I create a two way bind in the SettingViewCOntroller, the showCoinOnly in the PriceViewController will keep the status, when the SettingViewController is created again, the Switch will get the status again. But how to write a 2-way bind is confusing me, I just wrote the code below, but I don’t know why it didn’t cause the infinitive problem.
1 | showCoinOnlySwitch.rx.isOn.changed.debug() |
And if you want to use 2-way bind in other place, you can create a generic funtion to handle this.
1 | infix operator <-> |
The Problems I met
The scope of a status
When I implement the application logic, I realize that there are three kinds of status I need to mantain.
- Local Status
To be honest there is no local status when we use the RxSwift, the RxSwift doesn’t keep the status, it just trigger events from the Observables and consume these events by the Observers. I use this terminology in order to distingush the other two statuses. - Status as a property
Just like I discuss in the last section, the showCoinOnly in the PriceViewCOntroller keep the status transfered from the SettingViewController. This Variable will share information between two view controllers, one will be released, the other is alway keeping in the memory. In this case I keep the status in the PriceViewcontroller, because, the PriceVIewController will not be released in the application whole life time. - Global status
But sometimes the two controllers who share inforamtion will all be released. So how can I keep the status like the KLineSource in this application. The KLineSource is produced from the SeetingViewController when a user select the datasource on the segment control, and the KLineSource si consumed by the ExpandViewController to determine which APIs will be called. But these two view controllers will be release at runtime. So where can I keep the status. In this demo example I create a singleton global object to keep this status called KLineSource.
1 |
|
How to initialize the RxSwift reactive chain when some of these view controllers are created dynamically
Because some of these view controllers will be created dynamically, it bring
another problem what is the appropriate time to create a view controller, and when to bind the reaction chain. When we create a view controller and send a event to its Observer, it won’t get this event if it hasn’t band the the Observer first.
1 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { |
When the seague is triggered, the SettingViewControler will be created, and I will send the ShowCoinOnly status to the SettingViewController. But if I put the binding code in the ViewDidLoad() in the SettingViewController, this event will be discarded because when the event is being sent, the chain hasn’t been setup yet. So I need to move the setup logic into awakeFromNib(). And I also need to load the view objects first compulsively like the code below.
1 | override func awakeFromNib() { |
We don’t have the coordinator yet, so the SettingViewController is still responsable for creating it’s view model and connecting the inputs and outputs of the view model to some view controls and inputs and outputs of other view controllers.
If we take a picture of the SettingViewModel, we can get the diagram below:

In this diagram, you can see the main logic on the client side is included in the PriceViewModel. This view model is just one class, and rely on any other views and view controllers. So we can write a test for it easily.
The Fininal Step, Build a coordinator tree.
I call it as a coordinator tree, because, all of our coordinators which corresponds their view controllers, and have parent-children relationship like view controllers.
The top node of this tree is the AppCoordinator, it contains two coordinators, one is PriceCoordinator and the other FavoriteCoordinator.
All coordinators are inherited from BaseCoordinator. The main job of a coordinator is
- create a view model
- ceeate a view controller
- inject the view model into the view controller
- maintain the navigation between view controllers.
The fourth one can be separated some sub-steps
- creat a target coordinator as an observable object
- flatmap some of source coordinator obserable into the target observable, this process is most like calling a networking service.
- bind the source coordinator input onto the target coordinator, so we can get the result from the target coordinator.
The whole process looks like calling a asynchronization function, when we start a new coordinator. Every coordinator must comfirm the start() function. In this funtion the coordinator returns an Observable object for it’s source coordinator.
1 | override func start() -> Observable<Void> { |
The relationship between these objects is showed in the figure below:

So far we have finished our demo example. It is not a real project, but it is also not very simple. I show you how to refactor the code from MVC pattern to MVVM-Coordinator pattern. And this example also includes some key points to write a iOS application, such as networking service, autolayout, …
I hope it is helpful for the newbs in iOS develepment.