Jinsujin / swift-coordinator

0 stars 0 forks source link

UIKit 에서의 Coordinator 적용 #2

Open Jinsujin opened 1 year ago

Jinsujin commented 1 year ago

들어가며…

ViewController 는 View+Controller 의 역할을 하므로, 태생부터(?) 과중한 업무를 하게끔 되어있다. ViewController 가 Massive 하는데 영향을 주는 것들 중 하나는 화면 전환이라고 할 수 있다.

아래 코드는 버튼을 터치했을때

  1. 화면을 생성하고
  2. 화면 전환을 한다
// MARK: - ViewController
@objc func touchButton() {
    let detailViewController = DetailViewController()
    self.navigationController.push(detailViewController)
}

문제점: 유지보수의 어려움

어느 화면을 이동하느냐를 알기위해서 ViewController 의 모든 코드를 봐야 한다. 많은 코드가 있는 ViewController 에서 화면 전환 코드만 찾아내는 것은 작업시간을 늘릴여지가 다분하다.

또한 이동할 ViewController 의 인스턴스 생성을 함수 내부에서 하고 있다. 이는 화면간 관계를 파악하기에 어려워 지는 단점이 있다.


Coordinator 란?


구현

Coordinator Protocol 정의

protocol Coordinator: AnyObject {
    var childCoordinators: [Coordinator] { get set }
    var navigationController: UINavigationController { get set }
    var finishFlow: (() -> Void)? { get set }
    func start()
}

SceneDelegate 에서 연결

메모리에서 해제되는 것을 방지하기 위해, SceneDelegate 가 RootCoordinator(모든 Coordinator 의 최상단) 를 프로퍼티로 가진다.

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?
    var coordinator: RootCoordinator?

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

      let window = UIWindow(windowScene: windowScene)

      let coordinator = RootCoordinator()
      coordinator.start()
      self.coordinator = coordinator
      window.rootViewController = coordinator.getRootViewController()
      window.makeKeyAndVisible()
      self.window = window
  }

ViewController 에서 화면전환 이벤트 처리

2가지 방법이 있다:

  1. ViewController 가 Coordinator 를 알고있고, 직접 화면전환 요청
  2. ViewController 가 Coordinator 를 모르고, 간접적 화면전환 요청

    • 먼저, 1️⃣ ViewController 가 Coordinator 에게 직접 화면전환을 요청하는 방법이다. ViewController 은 coordinator 인스턴스를 내부에 가지고 있다:
    // ViewController
    private let coordinator: Coordinator
    
    @objc func touchedButton() {
        coordinator.pushSignUp()
    }

자식 Coordinator 의 흐름이 끝났을때 처리

  1. ViewController 에서 이벤트를 보낸다

    // MARK: - ViewController
    var onLogin: (() -> Void)?
    @objc func loginButtonDidTap() {
        self.onLogin?()
    }
  2. Coordinator 에서 이를 감지하여 자신의 실행흐름을 끝낼것인지를 결정한다: finishFlow?() 호출

    // MARK: - AuthCoordinator[이벤트가 발생한 자식]
    viewController.onLogin = { [unowned self] in
        self.finishFlow?()
    }
  3. 부모 코디네이터에서 이를 감지하여, 자신의 childCoordinator 배열에서 해당 child Coordinator 를 제거

    // MARK: - RootCoordinator [부모]
    coordinator.finishFlow = { [unowned self, unowned coordinator] in
        self.removeDependency(coordinator)
        self.isAutorized = true
        self.start()
    }
Jinsujin commented 1 year ago

참고 자료