[카테고리:] 미분류

  • Swift에서 Coordinator Pattern으로 UI 구성하는 방법 총정리

    Coordinator Pattern이란?

    Coordinator Pattern은 2015년 Soroush Khanlou가 고안한 디자인 패턴으로, UIViewController의 화면 전환 책임을 분리하여 앱의 네비게이션 흐름을 관리하는 패턴입니다[1][2]. 이 패턴은 MVC-C, MVVM-C 등 아키텍처 패턴 뒤에 붙는 ‘C’로 표현되며, ViewController 간의 결합도를 낮추고 코드의 재사용성을 높이는 것을 목표로 합니다[2][3].

    왜 Coordinator Pattern을 사용하는가?

    기존 방식의 문제점

    기존의 ViewController에서 직접 화면 전환을 처리하는 방식은 여러 문제점을 가지고 있습니다[1][4]:

    • 중복 코드 발생: 동일한 ViewController로 전환하는 코드를 여러 곳에서 반복 작성해야 함
    • 강한 결합도: ViewController 간 서로 종속적인 관계로 인한 높은 결합도
    • 단일 책임 원칙 위반: ViewController가 UI 표시와 화면 전환이라는 두 가지 책임을 동시에 가짐
    • 테스트 어려움: 강한 결합으로 인해 단위 테스트 작성이 곤란함

    Coordinator Pattern의 장점

    Coordinator Pattern을 사용하면 다음과 같은 이점을 얻을 수 있습니다[2][5]:

    • 관심사 분리: ViewController는 UI 표시에만 집중하고, 화면 전환은 Coordinator가 담당
    • 느슨한 결합: ViewController 간 직접적인 의존성 제거
    • 재사용성 향상: 화면 전환 로직의 중앙 집중화로 재사용 용이
    • 테스트 용이성: 각 구성 요소를 독립적으로 테스트 가능
    • 유지보수성: 화면 전환 로직 변경 시 Coordinator만 수정하면 됨

    기본 구조 및 구현 방법

    1. Coordinator 프로토콜 정의

    먼저 모든 Coordinator가 준수해야 할 기본 프로토콜을 정의합니다[6][7]:

    protocol Coordinator: AnyObject {
        var navigationController: UINavigationController { get set }
        var childCoordinators: [Coordinator] { get set }
    
        init(navigationController: UINavigationController)
        func start()
    }

    주요 구성 요소:

    • navigationController: 화면 전환에 사용할 UINavigationController
    • childCoordinators: 하위 Coordinator들을 관리하는 배열
    • start(): Coordinator 시작 시점의 로직을 정의하는 메서드

    2. 구체적인 Coordinator 구현

    final class MainCoordinator: Coordinator {
        var navigationController: UINavigationController
        var childCoordinators = [Coordinator]()
    
        init(navigationController: UINavigationController) {
            self.navigationController = navigationController
        }
    
        func start() {
            let viewController = ViewController.instantiate()
            viewController.coordinator = self
            navigationController.pushViewController(viewController, animated: false)
        }
    
        func showDetailView() {
            let detailCoordinator = DetailCoordinator(navigationController: navigationController)
            childCoordinators.append(detailCoordinator)
            detailCoordinator.start()
        }
    }

    3. Storyboard 인스턴스화 지원

    ViewController를 쉽게 생성하기 위한 프로토콜을 정의합니다[8][4]:

    protocol Storyboarded {
        static func instantiate() -> Self
    }
    
    extension Storyboarded where Self: UIViewController {
        static func instantiate() -> Self {
            let fullName = NSStringFromClass(self)
            let className = fullName.components(separatedBy: ".")[1]
            let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
            return storyboard.instantiateViewController(withIdentifier: className) as! Self
        }
    }

    4. AppDelegate/SceneDelegate 설정

    앱의 진입점에서 최상위 Coordinator를 설정합니다[7][9]:

    // AppDelegate에서 설정
    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
        }
    }

    고급 구현 패턴

    1. 부모-자식 Coordinator 관계

    복잡한 앱에서는 여러 Coordinator를 계층적으로 구성할 수 있습니다[10][11]:

    protocol CoordinatorFinishDelegate: AnyObject {
        func coordinatorDidFinish(childCoordinator: Coordinator)
    }
    
    extension Coordinator {
        func finish() {
            childCoordinators.removeAll()
            finishDelegate?.coordinatorDidFinish(childCoordinator: self)
        }
    }

    2. 탭바 구성을 위한 Coordinator

    탭바 기반 앱에서는 각 탭마다 별도의 Coordinator를 구성할 수 있습니다[12][13]:

    final class TabBarCoordinator: Coordinator {
        var navigationController: UINavigationController
        var childCoordinators = [Coordinator]()
    
        func start() {
            let tabBarController = UITabBarController()
    
            // 각 탭에 대한 Coordinator 생성
            let homeCoordinator = HomeCoordinator(navigationController: UINavigationController())
            let profileCoordinator = ProfileCoordinator(navigationController: UINavigationController())
    
            childCoordinators.append(contentsOf: [homeCoordinator, profileCoordinator])
    
            tabBarController.viewControllers = [
                homeCoordinator.navigationController,
                profileCoordinator.navigationController
            ]
    
            navigationController.pushViewController(tabBarController, animated: false)
        }
    }

    3. MVVM과의 결합

    Coordinator Pattern은 MVVM 아키텍처와 함께 사용될 때 더욱 효과적입니다[14][12]:

    final class DetailCoordinator: Coordinator {
        var navigationController: UINavigationController
        var childCoordinators = [Coordinator]()
    
        func start() {
            let viewModel = DetailViewModel()
            let viewController = DetailViewController(viewModel: viewModel)
            viewController.coordinator = self
            navigationController.pushViewController(viewController, animated: true)
        }
    }

    데이터 전달 방법

    1. 부모에서 자식으로 데이터 전달

    초기화 시점에 필요한 데이터를 전달합니다[15]:

    final class DetailCoordinator: Coordinator {
        private let itemId: String
    
        init(navigationController: UINavigationController, itemId: String) {
            self.navigationController = navigationController
            self.itemId = itemId
        }
    
        func start() {
            let viewModel = DetailViewModel(itemId: itemId)
            let viewController = DetailViewController(viewModel: viewModel)
            navigationController.pushViewController(viewController, animated: true)
        }
    }

    2. 자식에서 부모로 데이터 전달

    Delegate 패턴이나 Closure를 활용하여 데이터를 전달합니다[15]:

    protocol DetailCoordinatorDelegate: AnyObject {
        func detailDidComplete(with result: String)
    }
    
    final class DetailCoordinator: Coordinator {
        weak var delegate: DetailCoordinatorDelegate?
    
        func completeWithResult(_ result: String) {
            delegate?.detailDidComplete(with: result)
            finish()
        }
    }

    SwiftUI에서의 Coordinator Pattern

    SwiftUI에서도 Coordinator Pattern을 적용할 수 있습니다[16]:

    enum AuthenticationFlow: CaseIterable {
        case login, signup, forgotPassword
    }
    
    class AuthenticationCoordinator: ObservableObject {
        @Published var currentView: AuthenticationFlow = .login
    
        func showLogin() {
            currentView = .login
        }
    
        func showSignup() {
            currentView = .signup
        }
    
        func showForgotPassword() {
            currentView = .forgotPassword
        }
    }

    주의사항 및 모범 사례

    메모리 관리

    Child Coordinator를 적절히 관리하여 메모리 누수를 방지해야 합니다[6][10]:

    func removeChildCoordinator(child: Coordinator) {
        childCoordinators.removeAll { $0 === child }
    }

    적절한 복잡도 유지

    간단한 앱에서는 Coordinator Pattern이 오히려 복잡성을 증가시킬 수 있으므로, 앱의 복잡도에 따라 적절히 적용해야 합니다[2].

    Navigation Controller Delegate 활용

    복잡한 네비게이션 흐름에서는 UINavigationControllerDelegate를 활용하여 뒤로 가기 이벤트를 처리할 수 있습니다[8].

    결론

    Coordinator Pattern은 iOS 앱의 네비게이션 로직을 체계적으로 관리할 수 있는 강력한 패턴입니다[17]. 특히 복잡한 화면 전환 흐름을 가진 앱에서 ViewController 간의 결합도를 낮추고, 코드의 재사용성과 테스트 용이성을 크게 향상시킬 수 있습니다[2][5].

    다만, 앱의 복잡도와 팀의 상황을 고려하여 적절히 적용하는 것이 중요하며, 초기 학습 비용과 추가적인 코드 작성이 필요하다는 점을 감안해야 합니다[2]. 올바르게 구현된 Coordinator Pattern은 유지보수가 용이하고 확장 가능한 iOS 앱 아키텍처의 기반이 될 수 있습니다.

    출처
    [1] 코디네이터 패턴(Coordinator Pattern) – 코딩하는 체대생 https://mini-min-dev.tistory.com/264
    [2] [디자인 패턴] Coordinator Pattern – velog https://velog.io/@jeon0976/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-Coordinator-Pattern
    [3] [iOS] Coordinator Pattern – 시원한 코드 기록 – 티스토리 https://siwon-code.tistory.com/38
    [4] [iOS] Coordinator Pattern – Clamp – 티스토리 https://clamp-coding.tistory.com/452
    [5] Swift Coordinator Pattern: Step-by-Step Guide for iOS App … https://all-win-solutions.com/swift-coordinator-pattern-ios/
    [6] [Swift] Coordinator Pattern https://jintaewoo.tistory.com/58
    [7] iOS Coordinator 패턴 (개념 ~ 템플릿 적용 방법) – sookim-1 블로그 https://sookim-1.tistory.com/entry/iOS-Coordinator-Pattern
    [8] hyun99999/CoordinatorTutorial-iOS: Coordinator 패턴의 유쾌한 반란 https://github.com/hyun99999/CoordinatorTutorial-iOS
    [9] iOS) Coordinator pattern 적용해보자 – Basic – 코드너리 https://www.codenary.co.kr/discoveries/1810
    [10] [새싹 iOS] 25주차_Coordinator Pattern 적용기 – velog https://velog.io/@s_sub/%EC%83%88%EC%8B%B9-iOS-25%EC%A3%BC%EC%B0%A8
    [11] 간단한 예제로 살펴보는 iOS Design/Architecture Pattern: Coordinator https://lena-chamna.netlify.app/post/ios_design_pattern_coordinator_advanced/
    [12] [Swift] Coordinator & Router & Factory Pattern을 사용한 리팩토링 (1) https://mini-min-dev.tistory.com/306
    [13] [Swift] Coordinator & Router & Factory Pattern을 사용한 리팩토링 (2) https://mini-min-dev.tistory.com/307
    [14] [Design Pattern] Coordinator 패턴 – seokyoung – 티스토리 https://seokyoungg.tistory.com/100
    [15] GitHub – guoyingtao/CoordinatorPatternExample: My practice of applying coordinator pattern in an iOS project https://github.com/guoyingtao/CoordinatorPatternExample
    [16] How to use Coordinator Pattern in SwiftUI – Swift Anytime https://www.swiftanytime.com/blog/coordinator-pattern-in-swiftui
    [17] Coordinator Pattern for Navigation Management in Swift https://softwarepatternslexicon.com/patterns-swift/7/5/
    [18] [iOS] Coordinator Pattern을 사용해보자 – @EddiOS – 티스토리 https://developer-eddy403.tistory.com/55
    [19] Swift Tutorial: How to use Coordinator Pattern with MVVM – YouTube https://www.youtube.com/watch?v=wpw3l_jTuOo
    [20] Coordinator Pattern – velog https://velog.io/@toma/Coordinator-Pattern
    [21] SwiftUI 프로젝트에 AppDelegate, SceneDelegate 만들기 https://emptyhead.oopy.io/7aee0f49-0455-4270-8e1f-8d605d85f3c8
    [22] Delegate in iOS https://intellipaat.com/blog/tutorial/ios-tutorial/delegate-ios/
    [23] The basics of using the delegation pattern https://dev.to/bornfightcompany/the-basics-of-using-the-delegation-pattern-41gh
    [24] Coordinator Pattern 도입 이유와 실제 도입기 (ft. 객체들의 책임과 unit … https://codingmon.tistory.com/64
    [25] iOS) Coordinator pattern 적용해보자 – Basic – hyun9iOS – 티스토리 https://gyuios.tistory.com/84
    [26] [iOS/Architecture] Coordinator Pattern – 제인로그 – 티스토리 https://janechoi.tistory.com/23