본문 바로가기
🍎 iOS

[Swift] MVVMC의 Coordinator 알아보기

by 틴디 2022. 6. 14.
728x90
반응형

Pixabay  님의 사진, 출처:  Pexels

기존 MVVM에서 화면 전환 로직 즉, navigation 로직을 맡아하는

Coordinator가 MVVMC에서 핵심이지 않을까 싶습니다

그래서 이번 포스팅에서 Coordinator가 무엇이고

Child Coordinator는 왜 사용하는지 알아보겠습니다!

혹시 더 좋은 방법이 있다면 공유 부탁드려요!!

 

만약 MVVMC가 처음이라면 

아래 글을 먼저 보고 오시면 도움이 될거 같아요!

2022.06.11 - [🍎 iOS/Architecture Pattern] - [Swift] MVVMC 간단하게 알아보기

 


⭐️ 화면 전환을 담당하는 Coordinator


  우선 Coordinator가 무슨 역활을 하는지 알아봅시다! MVC 는 Massive View Controller라 불릴 만큼 ViewController의 역활이 아주 컸습니다. UI를 그리고 화면 전환을 하고, 비즈니스 로직을 처리하는 등 많은 코드와 로직들을 가지고 있었어요!

  이때 등장한 개념이 바로 MVVM!

  MVVM은 Model, View, ViewModel로 분리하여 UI 로직은 View가 비즈니스 로직은 ViewModel이 가지고 있었습니다. 이때 화면 전환과 관련된 로직은 ViewModel이 처리하더라도 

  다음에 어떤 화면이 와야 하는지, 그 화면에 대한 ViewController나 관련된 정보는 View 가 가지고 있어야 했습니다 🥲

MVVMC에서 C인 Coordinator는 화면 전환과 화면 전환을 위해 필요한 dependency를 가지고 있고 이러한 로직들을 처리합니다.   ViewController의 역활을 Coordinator가 가져갔을 뿐만 아니라 MVVM보다 더 단일 책임 원칙에 알맞게 되었습니다. 

따라서 테스트도 용이하고 화면 전환 처리도 이전에 비해 쉬워지게 됩니다. 

 

 

⭐️ Child Coordinator


  Coordinator를 공부하다보니 개발자 마다 Coordinator를 구현하는 방식이 다 달랐습니다. 딱 정해져 있다기 보다는 화면전환을 담당하는 Coordinator라는 개념 아래에 개발자 별로 필요한 기능과 구현 방법이 다른것 같습니다. (템플릿이나 라이브러리를 사용하면 비슷해 지겠지만...) 그래도 꼭 등장하는 것 중 하나가 Child Coordinator 였는데요, 한번 알아보겠습니다

( ु ´͈ ᵕ `͈ )ु

 

MVVMC에서 Coordinator 클래스가 한 개라면 모든 화면 이동 로직을 하나의 클래스에서 관리해야 하기 때문에 무거워지고 많은 량의 코드가 생길거에요! 그래서 하나의 Coordinator가 아닌 여러개의 Coordinator가 있고 이를 관리해 주어야 합니다 🤟

 

이런 케이스가 있다고 가정해 봅시다

화면 이동의 흐름을 그려 봤습니다! 처음 앱에 접속하면 랜딩 화면이 나타나고, 이때 유저는 로그인과 회원가입 중 하나를 선택할 수 있습니다. 로그인을 눌렀다면 아이디와 비밀번호를 입력한 뒤 홈으로 이동합니다. 만약 회원가입을 눌렀다면 회원가입 플로우를 끝내고 로그인 화면으로 이동합니다. 

이때 특징별로, 혹은 유즈케이스 혹은 플로우 별로 묶어 줄 수 있습니다. 

회원가입 플로우 혹은 화면들은 Register라는 하나의 그룹으로 묶을 수 있는데 이때 이 그룹에 관련된 화면이동을 담당하는 것이 RegisterCoordnator의 역활입니다. 즉 최상위 Coordinator에 LandingCoordinator가 있고 이 LandingCoordinator는 LoginCoordinator와 RegisterCoordinator을 가지고 있는 것입니다!

여기서 프로토콜과 델리게이트를 사용한다면 덜 종속적인 화면 이동을 구현할 수 있게 되죠! 그래서 이번에는 Coordinator protocol을 살펴 보겠습니다

 

 

⭐️ Coordinator Protocol


protocol Coordinator: AnyObject {
    var parentCoordinator: Coordinator? { get set }
    var childCoordinators: [Coordinator] { get set }
    
    func start()
    func finish()
    func addChildCoordinator(_ coordinator: Coordinator)
    func removeChildCoordinator(_ coordinator: Coordinator)
}

기본 뼈대가 되는 Coordinator를 class로 작성하기도 하고 protocol로 작성하기도 합니다! 좀더 구현이 편하고 관리하기 좋은 걸로 선택하셔서 구현하시면 될 거 같습니다

Coordinator에 기본적으로 들어가는 사항들도 옵션입니다! 구현하셔도 되고 필요할 때만 넣기 위해서 protocol에서 제외하셔도 됩니다

위에서 알아본 RegisterCoordinator를 기준으로 parentCoordinator는 LandingCoordinator가 되고 childCoordinator에는 LoginCoordinator가 담기게 됩니다! child와 parent coordinator와 함께 화면 이동을 구현하게 되는데요 이때 Coordinator을 생성하고 처음으로 호출하는 함수가 start()입니다

start에는 Coordinator의 처음 화면을 보여주는 로직이 주로 담기게 됩니다!

만약에 해당 플로우가 다 끝났다 이전 플로우로 돌아가야 한다? 그렇다면 finish를 호출합니다! 이때 removeChildCoordinator을 호출해서 childCoordinator에 추가 되어 있던 reference를 지워 줍니다

 

⭐️ 최상위 Coordinator


class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?
    var appCoordinator: AppCoordinator?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        
        guard let windowScene = (scene as? UIWindowScene) else { return }
        window = UIWindow(windowScene: windowScene)
        appCoordinator = AppCoordinator(window: window)
        appCoordinator?.start()
    }

}

Storyboard를 사용하지 않았을 때를 가정하고 SceneDelegate에서 시작하겠습니다!!

  최상위 Coordinator의 이름을 AppCoordinator로 정해주었는데 다른 이름으로 지어도 무관합니다! 만약 우리팀은 RootCoordinator로 하자! 했다면 RootCoordinator로 하시면 됩니다

  SeneDelegate에서 window의 rootViewController를 직접 설정해 주어도 된다고 생각합니다! 하지만 이 코드에서는 그렇게 하지 않았는데요,

아래 참고 사이트에 작성해 둔 사이트의 아티클을 보면서 rootViewController를 navigationController로 설정하고 navigationController의 rootViewController를 설정하는 것도 일종의 Coordinator의 역활이라고 생각해서 저는 최상위 Coordinator인 AppCoordinator의 생성자로 window 받아보았습니다. 

 

    lazy var navigationController: UINavigationController = {
        let rootViewController = UIViewController()
        let navigationController = UINavigationController(rootViewController: rootViewController)
        return navigationController
    }()

AppCoordinator에서 UINavigationController 타입의 프로퍼티를 생성해 줍니다. 

    func start() {
        guard let window = window else {
            return
        }
        window.rootViewController = navigationController
        window.makeKeyAndVisible()
        
        goToLandingPage()
    }

Coordinator을 생성하고 해당 Coordinator의 첫화면을 화면에 보이기 위해서 start() 함수를 호출하게 됩니다. 

위 SceneDelegate 코드에서 AppCoordinator 에 window를 넘기고 start를 호출하는 것을 볼 수 있습니다. 

그 후 AppCoordinator의 start가 호출되면 window의 rootViewController를 설정합니다. 

여기서 goToLandingPage()의 역활은 무엇일까요?

 

⭐️ Child Coordinator로 이동하기!


goToLandingPage() 는 LadingCoordinator를 생성하고 LandingCoordinator의 start() 함수를 호출해서 LandingCoordinator의 처음 화면을 보여주게 됩니다. 

    func goToLandingPage() {
        let landingCoordinator = LandingCoordinator(navigationController: navigationController)
        landingCoordinator.parentCoordinator = self
        addChildCoordinator(landingCoordinator)
        landingCoordinator.start()
    }

LandingCoordinator의 parentCoordinator인 AppCoordinator을 설정하고, AppCoordinator의 childCoordinaotor에 LandingCoordinator을 추가합니다.

    func start() {
        guard let window = window else {
            return
        }
        window.rootViewController = navigationController
        window.makeKeyAndVisible()
        
        if autoLogin {
            goToHomePage()
        } else {
            goToLandingPage()
        }
        
    }

만약에 자동로그인에 성공한 경우 Home으로, 자동 로그인 정보가 없다면 Landing페이지로 이동하는 로직을 AppCoordinator의 start()에 넣는 방식으로 구현하면 좋을거 같아요!

 

 

⭐️ Coordinator는 화면 이동 정보를 가지고 있다


    func start() {
        let landingViewModel = LandingViewModel()
        landingViewController.landingViewModel = landingViewModel
        landingViewController.viewDelegate = self
        navigationController.pushViewController(landingViewController, animated: false)
    }

위 코드는 LandingCoordinator의 start 함수 구현 부분입니다.

  LandingViewController를 화면에 띄우기 위해 LandingViewController와 LandingViewModel dependency를 설정해 주고 있는걸 확인할 수 있습니다. 

  따라서 Coordinator는 화면 이동에 필요한 ViewController 뿐만 아니라 이 ViewController에 필요한 dependency에 대한 정보까지 가지고 있습니다

(만약 dependency가 뭔지 잘 모르겠다면 👉🏻2022.04.29 - [Swift Advanced] - [Swift] 의존성 주입 왜 필요할까? DI 알아보기)

  따라서 ViewController는 화면 이동 처리를 Coordinator에 맡김과 동시에 다음에 어떤 화면이 나와야 하는지 그 화면에 필요한 데이터는 무엇인지 아무것도 몰라도 되는 것이죠!!! 이렇게 하나의 클래스가 하나의 일을 하는 것을 단일 책임 원칙이라고 하는데 ViewController는 완전히 화면 이동을 Coordinator에게 맡김으로서 ViewController, Coordinator 각각 자신의 일을 하게 됩니다

 


이렇게 Coordinator에 대해 알아보았는데 복잡한 화면이동을 구현할 수 있고, 테스트 하기 쉽게 좀 더 분리되었다는 장점이 있는거 같아요! 무엇보다도 코드가 분리되니 좀더 깔끔한것..(같지만 더 많은 프로토콜과 클래스와 파일들과...) 

다음에는 튜토리얼을 포스팅 해볼까 합니당! 모두들 즐코딩 되세요 🦑

 

 

참고 사이트

 

iOS Architecture: MVVM-C, Coordinators (3/6)

What is a Coordinator?

medium.com

 

이상하거나 잘못된 점이 있다면 언제든 댓글로 알려주세요! 🙌🏻

728x90
반응형

댓글