-
[iOS] Coordinator with Clean Architecture DiContaineriOS 2025. 3. 6. 15:44
저번에 Tuist로 clean architecture를 적용시키다가 coordinator에 대해 미숙한채로 적용하다 보니 따로 공부 하기 위해 여기 작성해본다.
우선 coordinator는 직역하자면 조정하다 뭐 이런 뜻이고 화면 이동에 관한 것들을 관리하는 디자인 패턴이다.
ViewController에서 Push나 Pop을 해주면 ViewController자체에서 하는일이 너무 많아 지고 그것은 결국 전체적인 코드 파악에 영향을 준다.
우선 먼저 Coordinator에 기본적인 부분부터 예시를 따라해보겠다.
Coordinator를 여러가지로 살펴보다 보니 Delegate와 거의 동일 했다 ViewController에서는 Coordinator로 이벤트 전달하고 Coordinator는 다시 전체 AppCoordinator로 이벤트를 전달해서 다시 AppCoordinator에서 정해준 ViewController로 이동하게끔 되는 패턴이다.
Coordinator 구조 간단히 그려보면 위 그림과 같다.
코드로 살펴보면 다음과 같습니다.
디렉토리 구조 우선 파일 구성은 위 그림과 같습니다.
먼저 아래와 같이 Coordinator를 작성해 줍니다.
Coordinator MainViewController 위 코드는 MainViewController에 관한 코드이고 MainViewController에 "로그아웃" 버튼을 누르게 되면 LoginViewController로 이동하게 되는 로직 입니다. 우선 Protocol을 만들어서 MainCoordinator에게 "나 로그아웃 버튼을 눌렀어" 라는 이벤트를 전달해야 합니다.
MainCoordinator MainCoordinator에서는 MainViewController가 전달해준 이벤트를 다시 Protocol을 통해서 AppCoordinator로 "로그아웃 버튼을 눌렀는데 어디로 이동해야해?" 라는 메세지를 전달해 줍니다.
AppCoordinator AppCoordinator에서는 MainCoordinator에서 만든 protocol MainCoordinatorDelegate를 채택하여 이동에 관련된 로직을 작성해 줍니다. "로그아웃 이벤트는 showLoginViewController로 이동해" 와 같이 말이죠 여기서 childCoordinator는 Login, Main과 같이 해당 관련된 Coordinator만 들고 있게끔 해주고 그것을 이용해서 이동시켜 줍니다.
위 Coordinator는 Zedd님껄 참고 했습니다.
https://zeddios.medium.com/coordinator-pattern-bf4a1bc46930
Coordinator Pattern
Coordinator의 시작부터 간단한 사용까지
zeddios.medium.com
위처럼 아주 기본적인 Coordinator 패턴을 간단하게나마 봤다면 이제 제가 하고 싶은것은 Clean Architecture에서 위 방법과 유사하게 작성해 보는 것입니다. DICotainer를 활용해서요. 구조는 아래와 같습니다.
Coordinator with DICotainer 위와 같이 간단하게 다시 만들어 보겠습니다.
디렉토리 구조 우선 이런식으로 파일구조를 잡았습니다.
Clean Architecture에서는 결국 UseCase에서 비즈니스 로직에 대한 프로토콜을 만들고 그 해당 프로토콜을 DataLayer 즉 Repository에서 채택하여 로직을 작성하고 그로직을 결국 Presentation Layer - ViewController에 표시 합니다. 위 프로젝트에서는 로그인 성공시 메인으로 이동하는 아주 간단한 로직을 구현했습니다.
위에서 보면 Coordinator하나로 작성하였기 떄문에 Coordinator.swift에서는 childCoordinator를 따로 작성해 주진 않았습니다.
Coordinator 그리고 아래는 이번 프로젝트의 핵심 DIContainer입니다. DIContainer는 결국 UseCase를 이용해서 Repository를 빼내오는 그런 로직입니다. register 함수로 Key와 Class를 넣어서 등록하고 resolve를 사용하여 Repository를 주입한 UseCase를 반환하는 내용입니다.
DIContainer 먼저 작성은 가장먼저 Clean Architecture의 가장 안쪽 Domain Layer에서부터 작성해 보겠습니다.
UseCase 다음은 DataLayer Repository
Repository 여러 Coordinator 예시를 보니깐 Coordinator는 ViewController에서 시작됩니다. 저는 처음에 ViewModel에서 작성했는데 잘못된거 같아서 찾아봤습니다..ㅎㅎ 그래서 PresentationLayer에 ViewModel을 보면 아래와 같습니다. 성공 여부에 따라 화면 이동을 결정지을것이기 때문에 아래와 같이 작성했습니다.
ViewModel 아래는 ViewController입니다.
import UIKit class LoginViewController: UIViewController { private var viewModel: LoginViewModel weak var coordinator: LoginCoordinator? private let emailTextField: UITextField = { let textField = UITextField() textField.placeholder = "Email" textField.borderStyle = .roundedRect return textField }() private let passwordTextField: UITextField = { let textField = UITextField() textField.placeholder = "Password" textField.borderStyle = .roundedRect textField.isSecureTextEntry = true return textField }() private let loginButton: UIButton = { let button = UIButton() button.setTitle("Login", for: .normal) button.backgroundColor = .blue button.layer.cornerRadius = 10 return button }() init(viewModel: LoginViewModel) { self.viewModel = viewModel super.init(nibName: nil, bundle: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() self.setUpUI() viewModel.onLoginSuccess = { [weak self] in self?.coordinator?.showMainScreen() } viewModel.onLoginFailure = { [weak self] in let alert = UIAlertController(title: "Error", message: "Fail To Login", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) self?.present(alert, animated: true, completion: nil) } loginButton.addTarget(self, action: #selector (loginButtonTapped), for: .touchUpInside) } func setUpUI(){ self.view.backgroundColor = .white let stackView = UIStackView(arrangedSubviews: [emailTextField, passwordTextField, loginButton]) stackView.axis = .vertical stackView.spacing = 10 stackView.translatesAutoresizingMaskIntoConstraints = false self.view.addSubview(stackView) NSLayoutConstraint.activate([ stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor), stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor), stackView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), stackView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20) ]) } @objc func loginButtonTapped(){ guard let email = emailTextField.text, !email.isEmpty else { return } guard let password = passwordTextField.text, !password.isEmpty else { return } viewModel.login(email: email, password: password) } }
아이디 비번을 위 DataLayer로직에 맞게 작성하면 Success로 Main으로 이동하게 됩니다.
LoginCoordinator 그래서 성공하게 되면 ShowMainScreen으로 이동하게 되는거죠. 실질적으로 UseCase와 Repository를 사용하는곳은 LoginViewController이기 때문에 처음 띄우는 로직 start 함수에 보면 DIContainer로 DI가 이뤄지고 register는
AppDelegate 앱 진입지점에서 이뤄지게 됩니다.
이렇게 간단하게 작성해봤는데요 이제 이걸 저희 프로젝트에 tuist와 같이 사용해서 작성해 봐야겠습니다 ㅎㅎ
'iOS' 카테고리의 다른 글
Tuist Clean Architecture 적용기 - 2 (0) 2025.02.25 Tuist Clean Architecture 적용기 - 1 (0) 2025.02.24