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

엄청난 수의 ViewController...


여러 자료를 탐독해 본 결과 구현은 여러가지 방법이 있는듯 했다. Rx를 이용해 하는 RxFlow도 있었고 delegate를 통해 상위 코디네이터에 화면 전환을 요청하는 방법등이 있었는데… 아직 이 프로젝트에 Rx를 도입할 생각까지는 없었었다…(근데 나중에 일부 화면에 도입하게 된다…ㅋㅋ)
결국 여러 블로그를 탐독해서 다음과 같이 구현하기로 했다.
코디네이터 프로토콜을 다음과 같이 정의
protocol Coordinator: AnyObject {
var coordinators: [Coordinator] { get set } // 하위 코디네이터들을 관리하는 프로퍼티
func start() // 해당 코디네이터의 root VC를 띄우는 메서드를 지정한다.
}
즉 코디네이터들은 계층 구조를 띄게 된다.
루트, 마스터 코디네이터인 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()
}
//.... 생략
상위 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
}
// ...생략
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는 잘만 사용한다면 코드의 양을 줄일 수 있으니….