[iOS] - Coordinator Pattern - 1 (개념 및 사용방법)

수킴·2022년 8월 17일
1

iOS 

목록 보기
11/12
post-thumbnail

안녕하세요 화면전환로직을 관리하기 위해 코디네이터패턴을 적용하기로 하였고 관련 내용과 적용방법에 대해 설명하겠습니다.

⛴ 개념

1️⃣ 서론

Coordinator 의미 : 움직임을 조정하는 사람

Coordinator Pattern은 2015년, Soroush Khanlou가 The Coordinator라는 글을 쓰면서 소개됩니다.

Khanlou는 ViewController가 flow로직, view로직, business로직등 너무 많은 역할을 한다고 생각하였습니다.

따라서 flow로직을 담당하는 객체를 만들었고 이 객체를 Coordinator 또는 Directors라고 지칭합니다.

💡 Coordinator패턴은 ViewController의 flow logic(흐름 로직)을 분리하기 위한 목적

2️⃣ 장점

  • 화면이 많아지게 되면, 화면전환을 담당하는 UINavigationController 를 사용하기가 버거워집니다. 왜냐하면 화면전환을 담당하는 코드가 ViewController에 의존하기 때문입니다. 따라서 Coordinator로 의존성을 분리하면 각각의 ViewController는 이전, 다음 ViewController에 대해서 알 필요가 없어지게 됩니다.
  1. 재사용성이 증가합니다.
  2. flow로직을 관리하기가 용이합니다.

🚀 사용방법

Coordinator패턴을 적용시키는 방법은 두가지로 나뉠 수 있습니다.

첫번째로 기본적인 방법으로는 자식Coordinator를 관리하지 않는 방법과 두번째로 자식Coordinator를 함께 관리하는 방법이 있습니다.

1️⃣ 자식 Coordinator를 사용하지 않는 방법

  1. Coordinator 프로토콜 생성

    import UIKit
    
    protocol Coordinator: AnyObject {
        var navigationController: UINavigationController { get set }
    
        init(navigationController: UINavigationController)
        
        func start()
    }
  2. 객체 생성하기

    • 많은 ViewController에서 공유되기 때문에 클래스로 작성합니다.
    import UIKit
    
    final class MainCoordinator: Coordinator {
        var navigationController: UINavigationController
        
        init(navigationController: UINavigationController) {
            self.navigationController = navigationController
        }
        
        func start() {
            let vc = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ViewController")
            navigationController.pushViewController(vc, animated: false)
        }
        
    }
  3. 엔트리포인트에 Coordinator 연결하기

    • AppDelegate
      import UIKit
      
      @main
      class AppDelegate: UIResponder, UIApplicationDelegate {
      		var window: UIWindow?
      		var coordinator: MainCoordinator?
      
          func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
      		    let navController = UINavigationController()
      		    coordinator = MainCoordinator(navigationController: navController)
      		    coordinator?.start()
      		
      		    window = UIWindow(frame: UIScreen.main.bounds)
      		    window?.rootViewController = navController
      		    window?.makeKeyAndVisible()
      
      				return true
          }
      
          func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
              return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
          }
      
      }
    • SceneDelegate
      import UIKit
      
      class SceneDelegate: UIResponder, UIWindowSceneDelegate {
      
          var window: UIWindow?
          var coordinator: MainCoordinator?
      
          func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
              guard let windowScene = (scene as? UIWindowScene) else { return }
              let navController = UINavigationController()
              
              coordinator = MainCoordinator(navigationController: navController)
              coordinator?.start()
              
              window = UIWindow(windowScene: windowScene)
              window?.rootViewController = navController
              window?.makeKeyAndVisible()
          }
      
      }
  4. 화면전환

    • 예시) ViewController → SecondViewController 추가
      1. ViewController에 Coordinator 프로퍼티 추가
      2. Coordinator 프로토콜에 화면전환 메서드선언
      3. MainCoordinator에 화면전환 코드 작성

2️⃣ 자식 Coordinator를 함께 사용하는 방법

💡 AppDelegate(SceneDelegate)는 최상위 AppCoordinator를 유지, 모든 Coordinator에는 일련의 하위Coordinator가 있습니다.
  1. Coordinator 프로토콜 생성

    • 자식Coordinator들을 관리할 배열을 선언합니다.
    // MARK: - 기본 Coordinator 프로토콜
    protocol Coordinator: AnyObject {
        var finishDelegate: CoordinatorFinishDelegate? { get set }
        var navigationController: UINavigationController { get set }
        var childCoordinators: [Coordinator] { get set }
        var type: CoordinatorType { get }
        func start()
        func finish()
        func findCoordinator(type: CoordinatorType) -> Coordinator?
        
        init(_ navigationController: UINavigationController)
    }
    
    extension Coordinator {
        func finish() {
            childCoordinators.removeAll()
            finishDelegate?.coordinatorDidFinish(childCoordinator: self)
        }
        
        func findCoordinator(type: CoordinatorType) -> Coordinator? {
            var stack: [Coordinator] = [self]
            
            while !stack.isEmpty {
                let currentCoordinator = stack.removeLast()
                if currentCoordinator.type == type {
                    return currentCoordinator
                }
                currentCoordinator.childCoordinators.forEach({ child in
                    stack.append(child)
                })   
            }
            return nil
        }
    }
  2. 작업을 마친경우 작동할 프로토콜을 선언합니다.

    protocol CoordinatorFinishDelegate: AnyObject {
        func coordinatorDidFinish(childCoordinator: Coordinator)
    }
  3. 각각의 Coordinator의 Type을 지정할 enum을 선언합니다.

    enum CoordinatorType {
        case app, login, home
        case signUp, signIn
    }
  4. 각각의 Coordinator의 프로토콜을 선언합니다.

    protocol LoginCoordinatorProtocol: Coordinator {
        func openSignUpCoordinator() // 회원가입 과정 시작
        func openSignInCoord() // 로그인 과정 시작
    }
  5. 객체(Coordinator)를 작성합니다.

    final class LoginCoordinator: LoginCoordinatorProtocol {
        
        weak var finishDelegate: CoordinatorFinishDelegate?
        var navigationController: UINavigationController
        var loginViewController: LoginHomeViewController
        var childCoordinators: [Coordinator] = []
        var type: CoordinatorType = .login
        
        init(_ navigationController: UINavigationController) {
            self.navigationController = navigationController
            self.loginViewController = LoginHomeViewController.instantiate()
        }
        
        func start() {
            self.loginViewController.coordinator = self
            self.navigationController.viewControllers = [self.loginViewController]
        }
        
        func modalStart() {
            self.loginViewController.modalPresentationStyle = .fullScreen
            self.loginViewController.modalTransitionStyle = .crossDissolve
            self.navigationController.modalPresentationStyle = .fullScreen
            self.navigationController.modalTransitionStyle = .crossDissolve
            self.loginViewController.coordinator = self
            self.navigationController.viewControllers = [self.loginViewController]
        }
        
        func openSignUpCoordinator() {
            let signUpCoordinator = DefaultSignUpCoordinator(self.navigationController)
            signUpCoordinator.finishDelegate = self
            self.childCoordinators.append(signUpCoordinator)
            signUpCoordinator.start()
        }
        
        func openSignInCoord() {
            let signInCoordinator = DefaultSignInCoordinator(self.navigationController)
            signInCoordinator.finishDelegate = self
            self.childCoordinators.append(signInCoordinator)
            signInCoordinator.start()
        }
        
    }
    
    extension LoginCoordinator: CoordinatorFinishDelegate {
        func coordinatorDidFinish(childCoordinator: Coordinator) {
            self.childCoordinators.removeAll()
            self.finishDelegate?.coordinatorDidFinish(childCoordinator: self)
        }
    }

🔗 참고링크


profile
iOS 공부 중 🧑🏻‍💻

0개의 댓글