https://github.com/suho/ios-clean-architecture
MVVM-Coordinator + RxSwift and Clean Architecture
https://github.com/suho/ios-clean-architecture
clean-architecture coordinator ios mvvm mvvm-architecture mvvm-pattern rxswift swift
Last synced: 7 months ago
JSON representation
MVVM-Coordinator + RxSwift and Clean Architecture
- Host: GitHub
- URL: https://github.com/suho/ios-clean-architecture
- Owner: suho
- License: mit
- Archived: true
- Created: 2018-12-16T15:12:52.000Z (about 7 years ago)
- Default Branch: master
- Last Pushed: 2021-05-22T09:01:24.000Z (over 4 years ago)
- Last Synced: 2025-03-24T18:35:13.234Z (9 months ago)
- Topics: clean-architecture, coordinator, ios, mvvm, mvvm-architecture, mvvm-pattern, rxswift, swift
- Language: Swift
- Homepage:
- Size: 2.96 MB
- Stars: 83
- Watchers: 1
- Forks: 9
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# ios-clean-architecture
[![Build Status][travis-img]][travis-url]
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](#mvvm-coordinator-and-clean-architecture)
- [Overview](#overview)
- [Application](#application)
- [Service](#service)
- [Example](#example)
- [Prerequisites](#prerequisites)
- [Installing](#installing)
- [Contributing](#contributing)
- [Versioning](#versioning)
- [Authors](#authors)
- [License](#license)
- [Acknowledgments](#acknowledgments)
## MVVM-Coordinator and Clean Architecture
### Overview
![Clean Architecture][image-4]
### 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 **View**s, **ViewModel**s, **Model**s and **Coordinator**s.
In the current example, **Application** is implemented with **MVVM-C** and use **RxSwift**.
First of all, **Model** defines `Entity` and `UseCase` of the application.
```swift
final class Task {
let id: String
var name: String
var startAt: Date
var createdAt: Date
var updatedAt: Date
var isFinish: Bool
}
```
```swift
protocol TaskUseCase {
func add(_ task: Task) -> Observable
func update(_ task: Task) -> Observable
func today() -> Observable<[Task]>
func find(by id: String) -> Observable
func delete(_ task: Task) -> Observable
}
```
`View` contains `ViewModel` and `ViewModel` performs pure transformation of a user `Input` to the `Output`
```swift
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`.
```swift
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.
```swift
// View
final class TodayViewController: ViewController, View {
var viewModel: TodayViewModel!
override func bindViewModel() {
super.bindViewModel()
// Magic here
}
}
```
```swift
// ViewModel
final class TodayViewModel: ViewModel {
struct Input {
let loadTrigger: Driver
let addTrigger: Driver
let updateTrigger: Driver
let deleteTrigger: Driver
}
struct Output {
let taskViewModels: Driver<[TaskCellViewModel]>
let addTask: Driver
let updateTask: Driver
let progress: Driver
let delete: Driver
}
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`
```swift
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()
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
```swift
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`.
```swift
final class RealmTask: TaskUseCase where R.Entity == Task {
private let repository: R
init(repository: R) {
self.repository = repository
}
func add(_ task: Task) -> Observable {
return repository.save(task)
}
func update(_ task: Task) -> Observable {
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])
```bash
rbenv install 2.5.1
```
- [Bundler][bundler] (manage [cocoapods][cocoapods])
```base
gem install bundler
```
### Installing
After you install `ruby-2.5.1` and `bundler`
Run this command to install `cocoapods`
```base
bundle install
```
Then, install dependencies in this project via **Cocoapods**
```base
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][releases].
> We use [SemVer](http://semver.org/) for versioning
## Authors
* [@suho][suho]
See also the list of [contributors][contributors] who participated in this project.
## License
This project is licensed under the MIT License - see the [LICENSE.md][license] file for details
## Acknowledgments
* Thanks to [@sergdort][sergdort] with his example about clean architecture
* [RxSwift][rxswift]
[releases]: https://github.com/suho/ios-clean-architecture/releases
[suho]: https://github.com/suho
[contributors]: https://github.com/suho/ios-clean-architecture/contributors
[license]: ./LICENSE.md
[bundler]: https://bundler.io/
[rbenv]: https://github.com/rbenv/rbenv
[cocoapods]: https://cocoapods.org/
[fastlane]: https://fastlane.tools/
[sergdort]: https://github.com/sergdort
[rxswift]: https://github.com/ReactiveX/RxSwift
[image-1]: ./img/ios/add_task_framed.png
[image-2]: ./img/ios/settings_framed.png
[image-3]: ./img/ios/today_tasks_framed.png
[travis-img]: https://travis-ci.org/suho/ios-clean-architecture.svg?branch=master
[travis-url]: https://travis-ci.org/suho/ios-clean-architecture
[image-4]: ./img/draw/architecture.jpg