꿈돌이랜드

SeeMeet 개발후기 - 1, Coordinator 패턴 도입 본문

Programming/개발 후기

SeeMeet 개발후기 - 1, Coordinator 패턴 도입

loinsir 2023. 5. 1. 22:01
반응형

29기 SOPT 앱잼을 통해 개발했던 SeeMeet에 대한 후기를 이제서야 올린다. 개발은 올해 1월부터 했지만, 본인의 삽질에 대해 오랜 시간이 지나며 사이드 프로젝트화 되어 오랜 기간이 소요되었다 ㅠ

개발을 하면서 어려운 점에 대해 정리하여 앞으로 서술하고자 한다.

화면 전환의 분리 (코디네이터 패턴의 적용)

  • 본래 본 프로젝트는 MVC 아키텍처로 시작되었다. (정확하게는 Apple MVC)
  • Apple MVC 아키텍처는 ViewController가 데이터 가공과 뷰의 역할을 모두 짊어지는 아키텍처로 Massive View Controller의 문제가 발생한다.
  • 그래서 엄청난 길이의 ViewController가 발생하기도 했다.

엄청난 수의 ViewController...

  • 결정적으로 코디네이터 패턴을 도입하기로 마음먹게 된 부분은 바로 이부분이다.
  • 왼쪽 뷰의 친구 목록 각 셀의 메세지 버튼을 누르면 약속 신청 뷰가 켜짐과 동시에, 약속 신청할 친구에 추가되어야한다.
  • 데모데이에는 구현이 안되어 있던 부분이기도 하고, delegate 패턴을 통해 구현을 할 수도 있었지만, 저 두 뷰들은 앱 뷰의 계층상 홈 뷰컨의 자식들로, 형제관계이다. 그래서 화면 전환하기가 좀 까다로웠다. (navigation pop, navigation push를 해야하는 상황) 화면 전환을 하면서 viewController를 해제하게 되면 해제 이후의 코드가 실행되지 않을 수 있기 때문이었다.
  • 그래서 화면전환을 담당하는 Coordinator 패턴을 도입하여 ViewController로부터 화면전환 로직을 위임받고, Massive View Controller 문제도 약간은 해소함과 동시에 저 뷰컨트롤러 전환 간의 데이터 전달 등도 구현하고자 했다.

코디네이터 패턴

  • 코디네이터 패턴은 Soroush Khanlou 라는 분이 제안한 것으로 요약하자면 View Controller가 화면전환을 직접하는건 뷰로서의 역할을 벗어나는 행위라는 것이다.
  • 그래서 화면전환을 담당하는 Coordinator 를 따로 정의하여 뷰 컨트롤러부터 화면전환의 역할을 분리하자는 것이다.

여러 자료를 탐독해 본 결과 구현은 여러가지 방법이 있는듯 했다. Rx를 이용해 하는 RxFlow도 있었고 delegate를 통해 상위 코디네이터에 화면 전환을 요청하는 방법등이 있었는데… 아직 이 프로젝트에 Rx를 도입할 생각까지는 없었었다…(근데 나중에 일부 화면에 도입하게 된다…ㅋㅋ)

결국 여러 블로그를 탐독해서 다음과 같이 구현하기로 했다.

  1. 코디네이터 프로토콜을 다음과 같이 정의
    protocol Coordinator: AnyObject {
        var coordinators: [Coordinator] { get set } // 하위 코디네이터들을 관리하는 프로퍼티
        func start() // 해당 코디네이터의 root VC를 띄우는 메서드를 지정한다.
    }
    

    즉 코디네이터들은 계층 구조를 띄게 된다.

  1. 루트, 마스터 코디네이터인 AppCoordinator를 다음과 같이 정의. 이 앱 코디네이터는 탭바 코디네이터의 역할 또한 맡게 된다. 우리 앱은 시작하면 탭바로부터 시작되므로.
    class AppCoordinator: Coordinator {
    
        var coordinators: [Coordinator] = []
    
        let window: UIWindow?
    
        var navigationController = UINavigationController().then {
            $0.modalTransitionStyle = .crossDissolve
            $0.modalPresentationStyle = .overFullScreen
        }
    
        private let disposeBag = DisposeBag()
    
        init(_ window: UIWindow?) { // SceneDelegate에서 UIWindow의 의존성 주입받는다.
            self.window = window
            window?.makeKeyAndVisible()
        }
    //.... 생략
    
  1. 상위 Coordinator에서 정의한 navigationController는 새로운 자식 뷰 플로우가 시작될 때, 즉 새로운 코디네이터가 생성될 때 주입시킨다(탭바에 묶여있는 ViewController 2가지는 제외, 이들은 생성자에서 생성한다). 이렇게 해서 전체 앱에서 네비게이션 컨트롤러는 오직 1개만 존재시킬 수 있다. 굳이 이렇게 까지 할 필요가 있나 싶지만… 메모리를 약간이라도 아낄 수 있으면서 복잡한 계층을 회피할 수 있지 않을까 하는 생각에서였다.
class PlansCoordinator: Coordinator {
    weak var parentCoordinator: Coordinator?
    var coordinators: [Coordinator] = []
    var navigationController: UINavigationController

    init(navigationController: UINavigationController) { // HomeCoordinator로부터 의존성을 주입받는다.
        self.navigationController = navigationController
    }
// ...생략
  1. 여차저차 Coordinator를 각 뷰 Flow마다 생성하고 각 뷰에서 화면전환이 일어나는 delegate들을 구현한다.
extension RegisterCoordinator: EmailRegisterVCDelegate{
    func backButtonDidTap() {
        navigationController.popViewController(animated: true)
    }

    func closeButtonDidTap() {
        self.navigationController.presentingViewController?.dismiss(animated: true)
        self.navigationController.viewControllers.removeAll()
        parentCoordinator?.start()
        parentCoordinator?.coordinators.removeAll(where: { $0 === self })
    }

    func nextButtonDidTap(accessToken: String, refreshToken: String, email: String) {
        startProfileRegisterVC(accessToken: accessToken, refreshToken: refreshToken, email: email)
    }
}

이렇게 코디네이터 패턴을 도입했는데… 장점은 화면전환을 코디네이터 패턴에서 관리하게 되어 편하고 Massive View Controller를 어느 정도 해소했다는 점이었고, 단점은 Coordinator객체 또한 생성, 삭제를 관리해야 하기에 조금 더 앱 측면에서 보기에 복잡해졌다는 것이다. 일례로 잘못해서 뷰 흐름을 종료할때 부모 Coordinator에서 본인을 찾아 remove 해주어야 하는데 명확히 그렇지 못했다면 데이터가 그대로 남아서 다시 재호출 할때 문제가 되기도 했었고, 잘못된 remove시 크래시가 발생하기도 했었다. 여차저차 우여곡절이 많은 적용이었다.

그리고 Coordinator 계층 끼리의 소통도 delegate 패턴을 사용하게 되어 생각보다 코드의 양이 많많치 않았고, 실수로 부모, 자식 간 delegate 지정을 하지 못하였을 경우 작동하지 않아 디버깅 하기 까다로웠다. 이 부분은 비단 코디네이터 에서 뿐만 아니라 delegate 패턴 자체의 단점인듯. 만약 다음에 Coordinator 패턴을 또 사용하게 된다면 RxFlow라는, Rx를 도입한 Coordinator를 사용해볼것 같다. Rx는 잘만 사용한다면 코드의 양을 줄일 수 있으니….


Uploaded by N2T

반응형

'Programming > 개발 후기' 카테고리의 다른 글

Runway 개발 후기 - 아키텍처  (0) 2023.07.07
SeeMeet 개발후기 - 2, Rx 도입  (0) 2023.05.01