iOS에서는 화면 전환을 담당하는 컨트롤러인 UINavigationController
가 있다.
Stack 방식으로 새로운 화면을 push하고, 이전화면으로 돌아가가기 위해 pop한다. 가장 첫 화면을 기준으로 새로운 화면으로 넘어갈때 마다 순서대로 쌓이고, 뒤로가기 버튼을 통해 이전에 방문했던 화면들을 순서대로 꺼낼 수 있다.
navigationController?.pushviewController(nextViewController, animated:true)
제공되는 push, pop을 이용하여 쉽고 간단하게 화면을 전환 할 수 있지만, 단점이 존재한다.
앱이 점차 커지고 화면이 많아진다면 사용하기가 버거워 진다는 것이다. 화면을 전환하는 코드가 사용하는 view controller를 의존하고, 이 경우 hard-coding 되어있는 것과 다르지 않다. 때문에 관리하기가 점점 힘들어지고 이를 해결하기 위해서 coordinator 패턴을 도입하게 된 것이다.
view controller로 부터 화면 전환의 부담을 줄여주고, 화면전환을 보다 더 관리하기 쉽도록 도와주기 위한 패턴이다.
coordinator 패턴을 사용하므로써, view controller 사이에 결합도를 낮춰 준다. 각 view controller는 이전에 어떤 컨트롤러가 있었는지, 다음에 어떤 컨트롤러가 오는지 알 필요가 없다. 대신에 이러한 flow는 coordinator가 관리한다. 오르지 coordinator만이 이것을 알고 관리한다.
결과적으로, 어떠한 순서로든 컨트롤러 전환이 가능하고, 재사용 까지도 가능하다. hard-coding을 피할 수 있게 된다.
예를 들자면 다음과 같다. 화면 전환을 위한 coordinator를 만들고, extension하여 화면전환에 필요한 작업을 구현한다.
protocol Coordinator: AnyObject {
func pushToDetail(_ navigationController: UINavigationController, productId: String)
}
extension Coordinator {
func pushToDetail(_ navigationController: UINavigationController, productId: String) {
let vc = DetailViewController()
vc.setNavigationTitle("상세화면")
vc.productId = productId
vc.coordinator = self
navigationController.pushViewController(vc, animated: true)
}
}
상세 화면으로 전환해야하는 view controller에 coordinator를 담당하는 delegate를 만들고, 상세 화면으로 전환해야 하는 시점에 coordinator를 통해서 화면을 전환하도록 한다.
class ListViewController: UIViewController {
weak var coordinator: Coordinator?
...
func productTapped(_ productId: String) {
coordinator?.pushToDetail(navigationController, productId: String)
}
...
}
간단하게 coordinator를 사용하는 방법에 대해 살펴 보았다.
앱이 커지는 경우 child coordinator(or subcoordinator)를 사용할 수 있다. 예를 들어 하위 코디네이터를 사용하여 계정 생성 흐름을 제어하고, 다른 하위 코디네이터를 사용하여 제품을 구매하는 흐름을 제어할 수 있다.
하위 코디네이터 흐름을 살펴보자.
간략하게 정리하자면 5가지 단계로 구성된다.
각 흐름을 구체적으로 살펴보자.
먼저 위에서 정의한 coordinator 프로토콜을 조금 수정하고 넘어가자.
protocol Coordinator: AnyObject {
var childCoordinators: [Coordinator] { get set }
var navigationController: UINavigationController { get set }
func start()
}
start()
메소드를 가진다.그럼 이제 정의한 프로토콜을 채택하는 ParentCoordinator 클래스를 생성한다.
class ParentCoordinator: Coordinator {
var childCoordinators = [Coordinator]()
var navigationController: UINavigationController
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func start(){
let vc = ViewController.instantiate()
vc.coordinator = self
navigationController.pushViewController(vc, animated: false)
}
}
class ChildCoordinator: Coordinator {
// parentCoordinator와 동일
}
그런데, Parent에서 생성된 child는 어떻게 parentCoordinator에 "나 할일 다했어, 이제 지워줘" 메시지를 전달 할 수 있을까?
Parent와 Child사이에 메시지를 주고 받기 위해서 child는 부모를 기억하고 있어야 한다.
때문에 ChildCoordinator는 부모 코디네이터를 프로퍼티로 가진다.
class ChildCoordinator: Coordinator {
var parentCoordinator: MainCoordinator?
}
여기서 주의 할 점이 있다. 부모 코디네이터는 자식 코디네이터를 알고, 자식 코디네이터가 부모 코디네이터를 알고 있기 때때문에 메모리 참조 순환 문제가 발생한다. 이를 해결하기 위해서는 weak
키워드를 붙여주어야 한다. (자세한 건 여기 참조)
class ChildCoordinator: Coordinator {
weak var parentCoordinator: MainCoordinator?
}
iOS13 버전 이하라면 AppDelegate에서, 이상이라면 SceneDelegate에서 ParentCoordinator를 생성한다.
coordinator = ParentCoordinator(navigationController: navController)
coordinator?.start()
class ParentCoordinator: Coordinator {
//생략
func buySubscription(){
let child = ChildCoordinator(navigationController: navigationController)
child.parentCoordinator = self
childCoordinators.append(child)
child.start()
}
}
ChildCoordinator를 생성하고, start 메소드를 호출 하였으므로 화면전환이 정상적으로 수행된다.
class ChildCoordinator: Coordinator {
func pushToDetail(productId: String) {
let vc = DetailViewController.instantiate()
vc.setNavigationTitle("상세화면")
vc.productId = productId
vc.coordinator = self
navigationController.pushViewController(vc, animated: true)
}
}
ChildCoordinator는 일을 다하고, 부모에게 자신이 종료 되었음을 알린다.
func didFinishBuying(){
parentCoordinator?.childDidFinish(self)
}
부모 코디네이터네서는 파라미터로 넘어온 자식 코디네이터를 찾아서 제거한다.
func childDidFinish(_ child: Coordinator?){
for (index, coordinator) in childCoordinators.enumerated() {
if coordinator === child {
childCoordinators.remove(at: index)
break
}
}
}
https://www.hackingwithswift.com/articles/175/advanced-coordinator-pattern-tutorial-ios
http://labs.brandi.co.kr/2020/06/16/kimjh.html
https://khanlou.com/2015/01/the-coordinator/
안녕하세요 엘리. 잘 보고가요!