본문 바로가기
iOS 🍎/iOS

MVVM-C

by yongmin.Lee 2022. 3. 28.

MVVM-C?

  • MVVM-C 는 MVVM에서 뷰컨트롤러 계층을 관리하는 Coordinator를 따로 분리 하는 것!
  • Coordinator 의 가장 중요한 역활은 viewcontroller로 부터 화면전환 로직을 가져가는 것이다.
  • 장점
    • ViewController로 하여금 뷰모델과 UI바인딩, 그리고 UI액션을 처리하는 일부 기능이외의 책임을 줄일 수 있다
    • ViewController가 독립적이므로 재사용하기 용이하다.
    • 의존성을 외부에서 주입(DI)할 수 있다
    • ViewController의 계층, 화면 간의 연결 플로우를 Coordinato에서 모아 관리 할 수 있다.

 

MVVM-C 적용하기

기존 코드

func didLogin() {
    let mainVC = MainViewController(with: object)
    self.navigationController?.present(mainVC, animated: true, completion: nil)
}

문제점

  • 1. ViewController가 화면전환로직 다음 단계를 직접 결정
    • let mainVC = MainViewController(with: object)
  • 2. ViewController가 부모 ViewController에게 해야할 일에 대한 메세지를 보내고 있다.
    • self.navigationController?.present(mainVC, animated: true, completion: nil)
  • 3. 위와 같은 화면전환로직 처리가 여러 ViewController에 분산 되어 있다

해결방안

  • 전체 앱에서 ViewController관리하고 이들 간에 흐름 로직을 처리하는 높은 수준의 객체가 필요 → Coordinators

 

코드 리팩토링

다음과 같은 구조로 리팩토링
  1. Coordinator 프로토콜을 작성
protocol Coordinator : class {
    var childCoordinators : [Coordinator] { get set }
    func start()
}

2. App Coordinator 구현

  • isLoggedIn이 true면 Main화면을, 아니라면 Login화면을 보여준다
class AppCoordinator: Coordinator {

    var childCoordinators: [Coordinator] = []
    private var navigationController: UINavigationController!
    
    var isLoggedIn: Bool = false
    
    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }
    
    func start() {
        if self.isLoggedIn {
            self.showMainViewController()
        } else {
            self.showLoginViewController()
        }
    }
    
    private func showLoginViewController() {
        //
    }
    
    private func showMainViewController() {
        // 
    }
}

3. LoginCoordinator구현

class LoginCoordinator: Coordinator {

    var childCoordinators: [Coordinator] = []
    
    private var navigationController: UINavigationController!
    
    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }
    
    func start() {
        let viewController = LoginViewController()        
        self.navigationController.viewControllers = [viewController]
    }
}

4-1. LoginViewController에서 로그인이 되면 LoginCoordinator에게 메시지를 전달하도록 LoginViewControllerDelegate 구현

protocol LoginViewControllerDelegate {
    func login()
}

class LoginViewController: UIViewController {

    var delegate: LoginViewControllerDelegate?
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    // 생략..
    
   func didLogin() {
      self.delegate?.login()
    }
}

4-2. LoginCoordinator는 LoginViewControllerDelegate 준수 및 로그인이 되었음을 AppCoordinator에게 전달하도록 LoginCoordinatorDelegate 구현

protocol LoginCoordinatorDelegate {
    func didLoggedIn(_ coordinator: LoginCoordinator)
}

class LoginCoordinator: Coordinator, LoginViewControllerDelegate {

    var childCoordinators: [Coordinator] = []
    var delegate: LoginCoordinatorDelegate?
    
    private var navigationController: UINavigationController!
    
    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }
    
    func start() {
        let viewController = LoginViewController()
        viewController.delegate = self // 1.LoginViewControllerDelegate Conform
        self.navigationController.viewControllers = [viewController]
    }
    
    func login() { // 1.LoginViewControllerDelegate Conform
        self.delegate?.didLoggedIn(self) // 2. AppCoordinator에게 메세지 전달
    }
}

5-1. AppCoordinator는 showLoginViewController()에서 LoginCoordinator의 delegate를 자신으로 설정함으로써LoginCoordinatorDelegate를 준수한다

class AppCoordinator: Coordinator, LoginCoordinatorDelegate {

    var childCoordinators: [Coordinator] = []
    private var navigationController: UINavigationController!
    
    var isLoggedIn: Bool = false
    
    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }
    
    func start() {
        if self.isLoggedIn {
            self.showMainViewController()
        } else {
            self.showLoginViewController()
        }
    }
    
    private func showMainViewController() {
        //
    }
    
    private func showLoginViewController() {
        let coordinator = LoginCoordinator(navigationController: self.navigationController)
        coordinator.delegate = self // 1-1. LoginCoordinator의 delegate를 자신으로 설정
        coordinator.start()
        self.childCoordinators.append(coordinator)
    }
    
    // 1-2.LoginCoordinatorDelegate를 준수
    func didLoggedIn(_ coordinator: LoginCoordinator) {
        self.childCoordinators = self.childCoordinators.filter { $0 !== coordinator }
        self.showMainViewController()
    }
}

5-2. MainViewController와 MainCooridnator도 Login 과 같이 구현함으로써 AppCoordinator에서 MainViewContoller로 이동할 수 있다

class AppCoordinator: Coordinator, LoginCoordinatorDelegate {

    var childCoordinators: [Coordinator] = []
    private var navigationController: UINavigationController!
    
    var isLoggedIn: Bool = false
    
    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }
    
    func start() {
        if self.isLoggedIn {
            self.showMainViewController()
        } else {
            self.showLoginViewController()
        }
    }
    
    private func showMainViewController() {
        //  MainViewController로 이동 !
        let coordinator = MainCoordinator(navigationController: self.navigationController)
        coordinator.delegate = self 
        coordinator.start()
        self.childCoordinators.append(coordinator)
    }
    
    private func showLoginViewController() {
        let coordinator = LoginCoordinator(navigationController: self.navigationController)
        coordinator.delegate = self 
        coordinator.start()
        self.childCoordinators.append(coordinator)
    }
    
    func didLoggedIn(_ coordinator: LoginCoordinator) {
        self.childCoordinators = self.childCoordinators.filter { $0 !== coordinator }
        self.showMainViewController()
    }
}

 

 

 

참고자료

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

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

'iOS 🍎 > iOS' 카테고리의 다른 글

iOS FireBase Crashlytics 적용 : CocoaPods, SPM  (0) 2022.03.29
SPM, Swift Package Manager  (0) 2022.03.29
Xcode 13, No StoryBoard Settings without SceneDelegate  (0) 2022.03.22
Animation 2 - Core Animation  (0) 2022.03.21
CALayer vs UIView  (0) 2022.03.18