{"id":21028585,"url":"https://github.com/suho/ios-clean-architecture","last_synced_at":"2025-05-15T11:31:46.707Z","repository":{"id":120301723,"uuid":"162013132","full_name":"suho/ios-clean-architecture","owner":"suho","description":"MVVM-Coordinator + RxSwift and Clean Architecture","archived":true,"fork":false,"pushed_at":"2021-05-22T09:01:24.000Z","size":3101,"stargazers_count":83,"open_issues_count":0,"forks_count":9,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-24T18:35:13.234Z","etag":null,"topics":["clean-architecture","coordinator","ios","mvvm","mvvm-architecture","mvvm-pattern","rxswift","swift"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":false,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/suho.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-12-16T15:12:52.000Z","updated_at":"2024-11-03T13:51:53.000Z","dependencies_parsed_at":null,"dependency_job_id":"a2a16cf0-7d5a-44e2-b55c-19b764f9a693","html_url":"https://github.com/suho/ios-clean-architecture","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suho%2Fios-clean-architecture","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suho%2Fios-clean-architecture/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suho%2Fios-clean-architecture/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suho%2Fios-clean-architecture/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/suho","download_url":"https://codeload.github.com/suho/ios-clean-architecture/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254330720,"owners_count":22053034,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clean-architecture","coordinator","ios","mvvm","mvvm-architecture","mvvm-pattern","rxswift","swift"],"created_at":"2024-11-19T11:58:15.771Z","updated_at":"2025-05-15T11:31:46.698Z","avatar_url":"https://github.com/suho.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ios-clean-architecture\n[![Build Status][travis-img]][travis-url]\n\nThis project contains documents and example of clean architecture of iOS application based on **MVVM-C** and **RxSwift**\n\n**Table of Contents**\n- [MVVM-Coordinator and Clean Architecture](#mvvm-coordinator-and-clean-architecture)\n  - [Overview](#overview)\n  - [Application](#application)\n  - [Service](#service)\n- [Example](#example)\n  - [Prerequisites](#prerequisites)\n  - [Installing](#installing)\n- [Contributing](#contributing)\n- [Versioning](#versioning)\n- [Authors](#authors)\n- [License](#license)\n- [Acknowledgments](#acknowledgments)\n\n## MVVM-Coordinator and Clean Architecture\n\n### Overview\n\n![Clean Architecture][image-4]\n\n### Application\n\n**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.\n\nIn the current example, **Application** is implemented with **MVVM-C** and use **RxSwift**.\n\nFirst of all, **Model** defines `Entity` and `UseCase` of the application.\n\n```swift\nfinal class Task {\n    let id: String\n    var name: String\n    var startAt: Date\n    var createdAt: Date\n    var updatedAt: Date\n    var isFinish: Bool\n}\n```\n\n```swift\nprotocol TaskUseCase {\n    func add(_ task: Task) -\u003e Observable\u003cTask\u003e\n    func update(_ task: Task) -\u003e Observable\u003cTask\u003e\n    func today() -\u003e Observable\u003c[Task]\u003e\n    func find(by id: String) -\u003e Observable\u003cTask?\u003e\n    func delete(_ task: Task) -\u003e Observable\u003cVoid\u003e\n}\n```\n\n`View` contains `ViewModel` and `ViewModel` performs pure transformation of a user `Input` to the `Output`\n\n```swift\nprotocol View {\n    associatedtype ViewModelType: ViewModel\n    var viewModel: ViewModelType! { get set }\n}\n\nprotocol ViewModel {\n    associatedtype Input\n    associatedtype Output\n    associatedtype CoordinatorType: Coordinate\n\n    var coordinator: CoordinatorType? { get set }\n\n    func transform(input: Input) -\u003e Output\n}\n```\n\nA `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`.\n\n```swift\nprotocol Coordinate {\n    associatedtype Screen\n    associatedtype View: UIViewController\n\n    var viewController: View? { get set }\n}\n```\n\n`Coordinator` also contains navigation logic for describing which screens are shown in which order.\n\n```swift\n// View\nfinal class TodayViewController: ViewController, View {\n  var viewModel: TodayViewModel!\n\n  override func bindViewModel() {\n        super.bindViewModel()\n        // Magic here\n    }\n}\n```\n\n```swift\n// ViewModel\nfinal class TodayViewModel: ViewModel {\n    struct Input {\n        let loadTrigger: Driver\u003cVoid\u003e\n        let addTrigger: Driver\u003cVoid\u003e\n        let updateTrigger: Driver\u003cTaskCellViewModel\u003e\n        let deleteTrigger: Driver\u003cIndexPath\u003e\n    }\n\n    struct Output {\n        let taskViewModels: Driver\u003c[TaskCellViewModel]\u003e\n        let addTask: Driver\u003cVoid\u003e\n        let updateTask: Driver\u003cVoid\u003e\n        let progress: Driver\u003cFloat\u003e\n        let delete: Driver\u003cVoid\u003e\n    }\n\n    var coordinator: TodayCoordinator?\n    private let useCase: TaskUseCase\n\n    init(useCase: TaskUseCase, coordinator: TodayCoordinator) {\n        self.useCase = useCase\n        self.coordinator = coordinator\n    }\n\n    func transform(input: Input) -\u003e Output {\n        // Magic here\n    }\n}\n```\n\nAs I mentioned above, we will implement injections for models, viewmodels, views in `Coordinator`\n\n```swift\nfinal class TabBarCoordinator: Coordinate {\n\n    weak var viewController: TabBarController?\n\n    func showScreen(_ screen: TabBarCoordinator.Screen) {}\n\n    private func todayNavi() -\u003e UINavigationController {\n        // View\n        let controller = TodayViewController()\n\n        // Init UseCase for inject into ViewModel\n        let repository = RealmRepository\u003cTask\u003e()\n        let useCase = RealmTask(repository: repository)\n\n        // Coordinator\n        let coordinator = TodayCoordinator()\n        coordinator.viewController = controller\n\n        // ViewModel\n        let viewModel = TodayViewModel(useCase: useCase, coordinator: coordinator)\n        controller.viewModel = viewModel\n\n        //\n        let navigationController = NavigationController(rootViewController: controller)\n        navigationController.tabBarItem = UITabBarItem(title: App.String.today,\n                                                       image: App.Image.today,\n                                                       tag: 0)\n        return navigationController\n    }\n\n    // Magic here\n}\n```\n\n### Service\n\nThe **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.\n\nSometime, because of framework requirements (e.g. Realm, CoreData), we can't use `Model's Entity` to implement\n\n```swift\nfinal class RTask: Object {\n    @objc dynamic var id: String = \"\"\n    @objc dynamic var name: String = \"\"\n    @objc dynamic var startAt: Date = Date()\n    @objc dynamic var createdAt: Date = Date()\n    @objc dynamic var updatedAt: Date = Date()\n    @objc dynamic var isFinish: Bool = false\n}\n```\n\nThe `Service` also contains concrete implementations of `Model's UseCase`, `Repositories` or `Any Services` that are defined in `Model`.\n\n```swift\nfinal class RealmTask\u003cR: Repository\u003e: TaskUseCase where R.Entity == Task {\n    private let repository: R\n\n    init(repository: R) {\n        self.repository = repository\n    }\n\n    func add(_ task: Task) -\u003e Observable\u003cTask\u003e {\n        return repository.save(task)\n    }\n\n    func update(_ task: Task) -\u003e Observable\u003cTask\u003e {\n        return repository.save(task)\n    }\n\n    // Another magic here\n}\n```\n\nAfter finish implement the `Service`, we will inject `Service`~`UseCase`(Model) into `ViewModel` by `Coordinator`\n\n## Example\n\nThe example application will show how I implemented Clean Architecture with MVVM-C\nThe 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.\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003cimg src='./img/ios/today_tasks_framed.png' /\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003cimg src='./img/ios/settings_framed.png' /\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003cimg src='./img/ios/add_task_framed.png' /\u003e\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n### Prerequisites\n\n- Xcode 10+\n- Swift 4.2\n- Ruby 2.5.1 ([rbenv][rbenv])\n\n```bash\nrbenv install 2.5.1\n```\n\n- [Bundler][bundler] (manage [cocoapods][cocoapods])\n\n```base\ngem install bundler\n```\n\n### Installing\n\nAfter you install `ruby-2.5.1` and `bundler`\n\nRun this command to install `cocoapods`\n\n```base\nbundle install\n```\n\nThen, install dependencies in this project via **Cocoapods**\n\n```base\nbundle exec pod install\n```\n\nNow, run your project with Xcode and see the demo app\n\n## Contributing\n\nContributions are welcome 🎉🎊\n\nWhen contributing to this repository, please first discuss the change you wish to make via issue before making a change.\n\nYou can also opening a PR if you want to fix bugs or improve something.\n\n## Versioning\n\nFor the versions available, see the [release on this repository][releases]. \n\n\u003e We use [SemVer](http://semver.org/) for versioning\n\n## Authors\n\n* [@suho][suho]\n\nSee also the list of [contributors][contributors] who participated in this project.\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE.md][license] file for details\n\n## Acknowledgments\n\n* Thanks to [@sergdort][sergdort] with his example about clean architecture\n* [RxSwift][rxswift]\n\n[releases]: https://github.com/suho/ios-clean-architecture/releases\n[suho]: https://github.com/suho\n[contributors]: https://github.com/suho/ios-clean-architecture/contributors\n[license]: ./LICENSE.md\n[bundler]: https://bundler.io/\n[rbenv]: https://github.com/rbenv/rbenv\n[cocoapods]: https://cocoapods.org/\n[fastlane]: https://fastlane.tools/\n[sergdort]: https://github.com/sergdort\n[rxswift]: https://github.com/ReactiveX/RxSwift\n[image-1]: ./img/ios/add_task_framed.png\n[image-2]: ./img/ios/settings_framed.png\n[image-3]: ./img/ios/today_tasks_framed.png\n[travis-img]: https://travis-ci.org/suho/ios-clean-architecture.svg?branch=master\n[travis-url]: https://travis-ci.org/suho/ios-clean-architecture\n[image-4]: ./img/draw/architecture.jpg\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsuho%2Fios-clean-architecture","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsuho%2Fios-clean-architecture","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsuho%2Fios-clean-architecture/lists"}