ios-clean-architecture
ios-clean-architecture copied to clipboard
MVVM-Coordinator + RxSwift and Clean Architecture
ios-clean-architecture
This project contains documents and example of clean architecture of iOS application based on MVVM-C and RxSwift
Table of Contents
- MVVM-Coordinator and Clean Architecture
- Overview
- Application
- Service
- Example
- Prerequisites
- Installing
- Contributing
- Versioning
- Authors
- License
- Acknowledgments
MVVM-Coordinator and Clean Architecture
Overview

Application
Application is responsible for delivering information to the user and handling user input. This application implemented with MVVM-C. This is place for your Views, ViewModels, Models and Coordinators.
In the current example, Application is implemented with MVVM-C and use RxSwift.
First of all, Model defines Entity and UseCase of the application.
final class Task {
let id: String
var name: String
var startAt: Date
var createdAt: Date
var updatedAt: Date
var isFinish: Bool
}
protocol TaskUseCase {
func add(_ task: Task) -> Observable<Task>
func update(_ task: Task) -> Observable<Task>
func today() -> Observable<[Task]>
func find(by id: String) -> Observable<Task?>
func delete(_ task: Task) -> Observable<Void>
}
View contains ViewModel and ViewModel performs pure transformation of a user Input to the Output
protocol View {
associatedtype ViewModelType: ViewModel
var viewModel: ViewModelType! { get set }
}
protocol ViewModel {
associatedtype Input
associatedtype Output
associatedtype CoordinatorType: Coordinate
var coordinator: CoordinatorType? { get set }
func transform(input: Input) -> Output
}
A Model (Entity, UseCase) can be injected/initializer into a ViewModel and a ViewModel also can be injected into a ViewController (View) via property injection or initializer. This is done by Coordinator.
protocol Coordinate {
associatedtype Screen
associatedtype View: UIViewController
var viewController: View? { get set }
}
Coordinator also contains navigation logic for describing which screens are shown in which order.
// View
final class TodayViewController: ViewController, View {
var viewModel: TodayViewModel!
override func bindViewModel() {
super.bindViewModel()
// Magic here
}
}
// ViewModel
final class TodayViewModel: ViewModel {
struct Input {
let loadTrigger: Driver<Void>
let addTrigger: Driver<Void>
let updateTrigger: Driver<TaskCellViewModel>
let deleteTrigger: Driver<IndexPath>
}
struct Output {
let taskViewModels: Driver<[TaskCellViewModel]>
let addTask: Driver<Void>
let updateTask: Driver<Void>
let progress: Driver<Float>
let delete: Driver<Void>
}
var coordinator: TodayCoordinator?
private let useCase: TaskUseCase
init(useCase: TaskUseCase, coordinator: TodayCoordinator) {
self.useCase = useCase
self.coordinator = coordinator
}
func transform(input: Input) -> Output {
// Magic here
}
}
As I mentioned above, we will implement injections for models, viewmodels, views in Coordinator
final class TabBarCoordinator: Coordinate {
weak var viewController: TabBarController?
func showScreen(_ screen: TabBarCoordinator.Screen) {}
private func todayNavi() -> UINavigationController {
// View
let controller = TodayViewController()
// Init UseCase for inject into ViewModel
let repository = RealmRepository<Task>()
let useCase = RealmTask(repository: repository)
// Coordinator
let coordinator = TodayCoordinator()
coordinator.viewController = controller
// ViewModel
let viewModel = TodayViewModel(useCase: useCase, coordinator: coordinator)
controller.viewModel = viewModel
//
let navigationController = NavigationController(rootViewController: controller)
navigationController.tabBarItem = UITabBarItem(title: App.String.today,
image: App.Image.today,
tag: 0)
return navigationController
}
// Magic here
}
Service
The Service is a concrete implementation of Model in a specific service. It does hide all implementation details. For example, database implementation whether it is Realm, CoreData, etc.
Sometime, because of framework requirements (e.g. Realm, CoreData), we can't use Model's Entity to implement
final class RTask: Object {
@objc dynamic var id: String = ""
@objc dynamic var name: String = ""
@objc dynamic var startAt: Date = Date()
@objc dynamic var createdAt: Date = Date()
@objc dynamic var updatedAt: Date = Date()
@objc dynamic var isFinish: Bool = false
}
The Service also contains concrete implementations of Model's UseCase, Repositories or Any Services that are defined in Model.
final class RealmTask<R: Repository>: TaskUseCase where R.Entity == Task {
private let repository: R
init(repository: R) {
self.repository = repository
}
func add(_ task: Task) -> Observable<Task> {
return repository.save(task)
}
func update(_ task: Task) -> Observable<Task> {
return repository.save(task)
}
// Another magic here
}
After finish implement the Service, we will inject Service~UseCase(Model) into ViewModel by Coordinator
Example
The example application will show how I implemented Clean Architecture with MVVM-C
The example application is Task Todo App which uses Realm and Network as a proof of concept that the Application is not dependant on the Service implementation detail.
![]() |
![]() |
![]() |
Prerequisites
- Xcode 10+
- Swift 4.2
- Ruby 2.5.1 (rbenv)
rbenv install 2.5.1
gem install bundler
Installing
After you install ruby-2.5.1 and bundler
Run this command to install cocoapods
bundle install
Then, install dependencies in this project via Cocoapods
bundle exec pod install
Now, run your project with Xcode and see the demo app
Contributing
Contributions are welcome 🎉🎊
When contributing to this repository, please first discuss the change you wish to make via issue before making a change.
You can also opening a PR if you want to fix bugs or improve something.
Versioning
For the versions available, see the release on this repository.
We use SemVer for versioning
Authors
See also the list of contributors who participated in this project.
License
This project is licensed under the MIT License - see the LICENSE.md file for details


