ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [iOS] Coordinator 패턴
    ios/ios 2024. 2. 5. 18:26

     

    Coordinator패턴?

    앱을 제작할 때 다양한 아키택처 패턴을 자주 사용합니다.

     

    그중 MVVM 아키텍처 패턴을 사용하는 이유 중 하나는 "ViewController가 하는 일을 줄이자"입니다.

     

    기존 MVC 패턴에서는 ViewController가 너무 많은 일을 하기 때문에 그중 비즈니스 로직을 viewModel 계층에 넘겨서 ViewController 가 하는 일을 줄이게 되었습니다.

     

    MVVM-C 패턴도 마찬가지로 ViewController가 하는 일을 줄이기 위해 만들어진 패턴입니다.

     

    뷰를 이동하는 코드를 작성할 때 ViewController에서 push/pop, present/dismiss를 구현해서 사용했습니다. 

     

    뷰가 간단하면 관리가 쉽겠지만 복잡해진다면 관리가 어려워집니다.

     

    따라서 MVVM-C 패턴은 뷰 이동 로직을 Coordinator에게 맡겨서 ViewController의 일을 줄이고, 뷰의 관리를 조금 더 용이하게 만들기 위해 만들어졌습니다.

     

    그림처럼 코디네이터가 앱의 흐름과 뷰의 이동을 관리 patent Coordinator는 child Coordinator를 생성하고 child Coordinator는 화면을 전환하는 역할을 가지고 있습니다.

     

     

    사용예시

    Coordinator 프로토콜을 만들어 뷰의 흐름을 관리하는 기본적인 행동을 정의해 줍니다.

    • child Coordinators : child Coordinator들을 저장하는 배열입니다. 이 배열을 통해 코디네이터 관계를 추적할 수 있습니다.
    • navigationController: 현재 코디네이터에 연결된 UINavigationController입니다.
    • start() : 코디네이터의 시작 지점을 정의하는 메서드입니다.

     

    BaseCoordinator는 Coordinator 패턴에서 모든 Codinator가 공통으로 사용하는 기본 동작을 구현하는 추상 클래스입니다. 여러 Coordinator 클래스에서 중복되는 코드를 최소화할 수 있습니다. 이 클래스에서는 Coordinator 프로토콜을 구현하면서 Coordinator들이 필요한 공통 속성과 메서드를 정의합니다.

    protocol Coordinator {
        var childCoordinators: [Coordinator] { get set }
        var navigationController: UINavigationController { get set }
        func start()
    }
    
    class BaseCoordinator: Coordinator {
        
        var childCoordinators: [Coordinator] = []
        var navigationController: UINavigationController
        
        init(navigationController: UINavigationController) {
            self.navigationController = navigationController
        }
    
        func start() {}
    }

     

     

    각 뷰컨트롤러는 child Coordinator에게 화면을 전환하겠다는 것을 알려야 합니다. 그러기 위해서 delegate패턴을 사용하여 coordinator에게 알립니다. 

     

    import UIKit
    
    protocol MainViewControllerDelegate {
        func pushToSecondViewController()
        func presentToSecondViewController()
    }
    
    class MainViewController: UIViewController {
        
        var delegate: MainViewControllerDelegate?
        
        // UI 선언 코드 생략
        
        // layout 코드 생략
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
        }
        
        @objc
        func tapPresentSecondButton() {
            delegate?.presentToSecondViewController()
        }
        
        @objc
        func tapPushSecondButton() {
            delegate?.pushToSecondViewController()
        }
    }

     

     

    viewController에서 child Coordinator에게 뷰의 이동을 알리면 child Coordinator에서는 App Coordinator에게 뷰의 이동을 알립니다. 이때도 delegate패턴을 사용합니다. 각 Coordinator들은 BaseCoordinator를 기본으로 채택합니다.

    protocol MainCoordinatorDelegate {
        func pushToSecondViewController()
        func presentToSecondViewController()
    }
    
    class MainCoordinator: BaseCoordinator, MainViewControllerDelegate {
    
        var delegate: MainCoordinatorDelegate?
        
        override func start() {
            let mainVC = MainViewController()
            mainVC.delegate = self
            
            navigationController.viewControllers = [mainVC]
        }
        
        func pushToSecondViewController() {
            delegate?.pushToSecondViewController()
        }
        
        func presentToSecondViewController() {
            delegate?.presentToSecondViewController()
        }
    }

     

     

    이제 App Coordinator에서 뷰의 이동 관련된 코드를 구현해 줍니다. 이때 Coordinator delegate를 채택하여 구현합니다.

    class AppCoordinator: BaseCoordinator, MainCoordinatorDelegate, SecondCoordinatorDelegate {
        
        override func start() {
            showMainViewController()
        }
        
        func showMainViewController() {
            let coordinator = MainCoordinator(navigationController: navigationController)
            coordinator.delegate = self
            coordinator.start()
            childCoordinators.append(coordinator)
            print(childCoordinators)
        }
        
        func pushToSecondViewController() {
            let coordinator = SecondCoordinator(navigationController: navigationController)
            coordinator.delegate = self
            coordinator.start()
            childCoordinators.append(coordinator)
            print(childCoordinators)
        }
        
        func presentToSecondViewController() {
            let coordinator = SecondCoordinator(navigationController: navigationController)
            coordinator.delegate = self
            coordinator.start()
            childCoordinators.append(coordinator)
        }
        
        func dismissViewController() {
            navigationController.dismiss(animated: true)
            childCoordinators.popLast()
            print(childCoordinators)
        }
        
        func popViewController() {
            navigationController.popViewController(animated: true)
            childCoordinators.popLast()
            print(childCoordinators)
        }
    }

     

     

    참고

    https://zeddios.medium.com/coordinator-pattern-bf4a1bc46930

     

    Coordinator Pattern

    Coordinator의 시작부터 간단한 사용까지

    zeddios.medium.com

    https://khanlou.com/2015/01/the-coordinator/

     

    Khanlou | The Coordinator

    January 20, 2015 The Coordinator One of the biggest problems with the big view controllers is that they entangle your flow logic, view logic, and business logic. When a table cell is selected, that delegate method typically looks like this: - (void)tableVi

    khanlou.com

     

     

     

Designed by Tistory.