{"id":19070921,"url":"https://github.com/kyoheig3/plan","last_synced_at":"2025-04-28T15:13:36.016Z","repository":{"id":62450822,"uuid":"272612243","full_name":"KyoheiG3/Plan","owner":"KyoheiG3","description":"The Plan.framework helps to keep your iOS application design clean.","archived":false,"fork":false,"pushed_at":"2023-09-05T07:43:54.000Z","size":120,"stargazers_count":36,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-10T15:45:34.839Z","etag":null,"topics":["clean-architecture","design-pattern","plan","swift"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"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/KyoheiG3.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":"Support Files/Plan.xcconfig","governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-06-16T04:49:54.000Z","updated_at":"2024-01-12T14:35:28.000Z","dependencies_parsed_at":"2024-11-09T01:22:57.290Z","dependency_job_id":"1b276690-ef93-496f-bdc6-004edaa9e22b","html_url":"https://github.com/KyoheiG3/Plan","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KyoheiG3%2FPlan","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KyoheiG3%2FPlan/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KyoheiG3%2FPlan/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/KyoheiG3%2FPlan/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/KyoheiG3","download_url":"https://codeload.github.com/KyoheiG3/Plan/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249436755,"owners_count":21271934,"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","design-pattern","plan","swift"],"created_at":"2024-11-09T01:21:18.865Z","updated_at":"2025-04-18T05:32:00.598Z","avatar_url":"https://github.com/KyoheiG3.png","language":"Swift","readme":"# Plan\n\n[![Build](https://github.com/KyoheiG3/Plan/workflows/Build/badge.svg)](https://github.com/KyoheiG3/Plan/actions)\n[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-blue.svg?style=flat)](https://github.com/Carthage/Carthage)\n[![codecov](https://codecov.io/gh/KyoheiG3/Plan/branch/master/graph/badge.svg)](https://codecov.io/gh/KyoheiG3/Plan)\n[![Version](https://img.shields.io/cocoapods/v/Plan.svg?style=flat)](http://cocoadocs.org/docsets/Plan)\n[![Platform](https://img.shields.io/cocoapods/p/Plan.svg?style=flat)](http://cocoadocs.org/docsets/Plan)\n[![License](https://img.shields.io/cocoapods/l/Plan.svg?style=flat)](http://cocoadocs.org/docsets/Plan)\n\nThe `Plan.framework` helps to keep your iOS application design clean. Think of it as a clean architecture. Read about clean architecture [here](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html).\n\n## overview\n\nThe purpose of this design is to clear the processing flow and dependencies.\n\nThe ideal processing flow is as follows.\n\n\u003cimg src=\"https://user-images.githubusercontent.com/5707132/85978649-1c018880-ba1a-11ea-89b6-7dfc8e4d503d.png\" width=\"600\"\u003e\n\n\nIf it is difficult to accept processing in `Controller`, you may prepare `Adapter` instead of `Controller` and `Adapter` may interact with `Controller`. However, the `Adapter` should not do more than necessary.\n\n## Details\n\n`Plan` offers five main types, but in many cases a framework that allows reactive programming is helpful.\n\n### Interactor\n\nInput from `Controller` is processed.\nData I/O and API calls are also performed here.\nWhen the processing is completed, execute the action of `Dispatcher`.\nIt is included in the `Domain Layer`.\n\n```swift\nenum LoginUseCaseAction {\n    case loading\n    case login(Result\u003cUser, Error\u003e)\n}\n\nprotocol LoginUseCase {\n    func login(userName: String, password: String)\n}\n\nclass LoginInteractor: Interactor\u003cLoginUseCaseAction\u003e, LoginUseCase {\n    let disposeBag = DisposeBag()\n\n    func login(userName: String, password: String) {\n        dispatcher.dispatch(.loading)\n        userRepository.login(userName: userName, password: password)\n            .subscribe(onNext: { [weak self] user in\n                self?.dispatcher.dispatch(.login(.success(user)))\n            }, onError: { [weak self] error in\n                self?.dispatcher.dispatch(.login(.failure(error)))\n            })\n            .disposed(by: disposeBag)\n    }\n}\n```\n\n### Dispatcher\n\nPass the Output by `Interactor` to `Translator`.\n\n### Store\n\nStore the state of `View`.\nThe status is changed by the `Translator`.\nIt is included in the `Presentation Layer`.\n\n```swift\nclass LoginStore: Store {\n    let viewModel = BehaviorRelay(value: LoginViewModel())\n}\n```\n\n### Translator\n\nThe data received from the `Interactor` is converted into the data for configuring the `View` and the state of the `Store` is updated.\nIt is included in the `Presentation Layer`.\n\n```swift\nstruct LoginTranslator: Translator {\n    func translate(action: LoginUseCaseAction, store: LoginStore) {\n        switch action {\n        case .loading:\n            store.viewModel.modefy { viewModel in\n                viewModel.loginProgress = .loading\n            }\n\n        case .login(.success(let user)):\n            store.viewModel.modefy { viewModel in\n                viewModel.user = user\n                viewModel.loginProgress = .loadSucceeded\n            }\n\n        case .login(.failure(let error)):\n            print(\"login failed \\(error)\")\n            store.viewModel.modefy { viewModel in\n                viewModel.loginProgress = .loadFailed\n            }\n        }\n    }\n}\n```\n\n### Presenter\n\nConvert the stored state to the optimal form for `View` to use.\nIt is included in the `Presentation Layer`.\n\n```swift\nclass LoginPresenter: Presenter\u003cLoginTranslator\u003e, LoginPresenterProtocol {\n    var viewModel: Observable\u003cLoginViewModel\u003e {\n        store.viewModel.asObservable()\n    }\n}\n```\n\nWhen initializing the `Interactor`, pass the `Presenter` instance as the `Dispatcher`.\n\n```swift\nlet presenter = LoginPresenter(store: LoginStore(), translator: LoginTranslator())\nlet interactor = LoginInteractor(dispatcher: presenter.asDispatcher())\n```\n\nNormally, the `Action` that can be dispatched to the `Presenter` is limited to the one defined in the `Translator`, but if there are multiple `UseCase`, by overloading the `asDispatcher()` method, It is possible to dispatch to multiple `Interactor`.\n\n```swift\nclass LoginPresenter: Presenter\u003cLoginTranslator\u003e, LoginPresenterProtocol {\n    var viewModel: Observable\u003cLoginViewModel\u003e {\n        store.viewModel.asObservable()\n    }\n\n    func asDispatcher() -\u003e AnyDispatcher\u003cUserInfoUseCaseAction\u003e {\n        AnyDispatcher(self)\n            .map { (action: UserInfoUseCaseAction) in\n                // Convert UserInfoUseCaseAction to LoginTranslator.Action\n        }\n    }\n}\n\nlet presenter = LoginPresenter(store: LoginStore(), translator: LoginTranslator())\n\n// Can dispatch to one Presenter from multiple Interactors.\nlet loginInteractor = LoginInteractor(dispatcher: presenter.asDispatcher())\nlet userInfoInteractor = UserInfoInteractor(dispatcher: presenter.asDispatcher())\n```\n\n### More details\n\nSee [Examples](./Examples).\n\n## Requirements\n\n- Swift 5.0\n- iOS 10.0 or later\n- macOS 10.12 or later\n- tvOS 10.0 or later\n- watchOS 3.0 or later\n\n## Installation\n\n#### CocoaPods\n\nAdd the following to your `Podfile`:\n\n```Ruby\npod \"Plan\"\n```\n\n#### Carthage\n\nAdd the following to your `Cartfile`:\n\n```Ruby\ngithub \"KyoheiG3/Plan\"\n```\n\n## Acknowledgements\n\nI've always used [VueFlux](https://github.com/ra1028/VueFlux) inside the architecture, but I've created a `Plan` in an attempt to make it simpler and easier for module testing.\n\n## LICENSE\nUnder the MIT license. See [LICENSE](./LICENSE) file for details.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkyoheig3%2Fplan","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkyoheig3%2Fplan","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkyoheig3%2Fplan/lists"}