[카테고리:] 미분류

  • iOS Coordinator Pattern 설명서 📱

    🎭 Coordinator Pattern이 뭔가요?

    학교 비유로 이해해보기

    학교에서 각 반 담임선생님이 있다고 생각해보세요.

    기존 방식 (문제가 있는 방식):

    • 1반 담임이 직접 2반, 3반, 4반을 모두 관리
    • 2반에서 체육 시간에 운동장 가려면 1반 담임에게 허락받기
    • 3반에서 급식실 가려면 1반 담임에게 또 허락받기
    • 😵 너무 복잡하고 담임선생님이 과로에 쓰러짐!

    Coordinator 방식 (똑똑한 방식):

    • 각 반마다 담임선생님이 있음 (각각의 화면 담당)
    • 교무실에 교무부장선생님이 있어서 반 간의 이동을 총괄 관리
    • 1반에서 2반으로 가고 싶으면 → 교무부장에게 말하면 → 교무부장이 안전하게 안내
    • ✨ 각 담임은 자기 반만 신경쓰고, 이동은 교무부장이 담당!

    🤔 왜 이런 방식을 쓸까요?

    기존 방식의 문제점

    앱을 만들 때, 각 화면(ViewController)이 직접 다른 화면으로 이동하는 코드를 작성하면:

    1. 🍝 스파게티 코드: 모든 화면이 서로 얽혀있어서 코드가 복잡해짐
    2. 🔄 중복 코드: 같은 화면으로 가는 코드를 여러 번 작성해야 함
    3. 🐛 버그 발생: 하나를 고치면 다른 곳이 망가짐
    4. 😰 테스트 어려움: 모든 게 연결되어 있어서 테스트하기 힘듦

    Coordinator Pattern의 장점

    1. 🎯 깔끔한 역할 분담: 각 화면은 자기 일만, 이동은 Coordinator가 담당
    2. 🔧 쉬운 수정: 화면 이동 방식을 바꾸고 싶으면 Coordinator만 고치면 됨
    3. ♻️ 재사용 가능: 한 번 만든 화면을 여러 곳에서 쉽게 사용
    4. ✅ 테스트 쉬움: 각 부분을 따로따로 테스트할 수 있음

    🏗️ 어떻게 만드나요?

    1단계: 교무부장(Coordinator) 역할 정하기

    // 모든 교무부장이 지켜야 할 규칙
    protocol Coordinator: AnyObject {
        var navigationController: UINavigationController { get set }  // 복도 역할
        var childCoordinators: [Coordinator] { get set }           // 부하 교무부장들
        
        func start()  // 업무 시작!
    }
    

    쉽게 말하면:

    • navigationController: 학교 복도 같은 역할 (화면 간 이동통로)
    • childCoordinators: 부하 교무부장들 목록
    • start(): “자, 이제 시작해!”

    2단계: 구체적인 교무부장 만들기

    // 메인 교무부장 클래스
    class MainCoordinator: Coordinator {
        var navigationController: UINavigationController
        var childCoordinators = [Coordinator]()
        
        init(navigationController: UINavigationController) {
            self.navigationController = navigationController
        }
        
        // 앱 시작할 때 첫 화면 보여주기
        func start() {
            let firstScreen = ViewController.instantiate()
            firstScreen.coordinator = self  // "내가 너의 교무부장이야"
            navigationController.pushViewController(firstScreen, animated: false)
        }
        
        // 상세 화면으로 이동하기
        func showDetailScreen() {
            let detailCoordinator = DetailCoordinator(navigationController: navigationController)
            childCoordinators.append(detailCoordinator)  // 부하 교무부장 등록
            detailCoordinator.start()  // "상세화면 담당 교무부장, 출동!"
        }
    }
    

    3단계: 화면(ViewController) 쉽게 만들기

    // 스토리보드에서 화면을 쉽게 가져오는 도구
    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 - 앱의 교장선생님
    class AppDelegate: UIResponder, UIApplicationDelegate {
        var window: UIWindow?
        var coordinator: MainCoordinator?  // 메인 교무부장
        
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
            
            // 1. 복도(NavigationController) 만들기
            let navController = UINavigationController()
            
            // 2. 메인 교무부장 임명하기
            coordinator = MainCoordinator(navigationController: navController)
            coordinator?.start()  // "교무부장님, 업무 시작해주세요!"
            
            // 3. 학교(앱) 문 열기
            window = UIWindow(frame: UIScreen.main.bounds)
            window?.rootViewController = navController
            window?.makeKeyAndVisible()
            
            return true
        }
    }
    

    🎯 실제 사용 예시

    로그인 → 메인화면 → 상세화면 흐름

    // 1. 로그인 화면에서 버튼을 눌렀을 때
    @IBAction func loginButtonTapped(_ sender: UIButton) {
        // ❌ 나쁜 방법: 직접 화면 이동
        // let mainVC = MainViewController()
        // navigationController?.pushViewController(mainVC, animated: true)
        
        // ✅ 좋은 방법: 교무부장에게 부탁
        coordinator?.showMainScreen()
    }
    
    // 2. 메인 화면에서 셀을 터치했을 때
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let selectedItem = items[indexPath.row]
        
        // ✅ 교무부장에게 "이 아이템의 상세화면 보여줘!" 라고 부탁
        coordinator?.showDetailScreen(for: selectedItem)
    }
    

    📱 데이터 주고받기

    부모에서 자식으로 (상위 화면 → 하위 화면)

    // 상세화면 교무부장 - 어떤 아이템을 보여줄지 미리 알고 있음
    class DetailCoordinator: Coordinator {
        private let itemInfo: Item  // 보여줄 아이템 정보
        
        init(navigationController: UINavigationController, item: Item) {
            self.navigationController = navigationController
            self.itemInfo = item  // "이 아이템 정보를 가지고 있을게"
        }
        
        func start() {
            let detailScreen = DetailViewController.instantiate()
            detailScreen.item = itemInfo  // "여기 아이템 정보야!"
            navigationController.pushViewController(detailScreen, animated: true)
        }
    }
    

    자식에서 부모로 (하위 화면 → 상위 화면)

    // 대리자 패턴 사용 - "일이 끝나면 알려줄게!"
    protocol DetailCoordinatorDelegate: AnyObject {
        func detailDidFinish(with result: String)
    }
    
    class DetailCoordinator: Coordinator {
        weak var delegate: DetailCoordinatorDelegate?
        
        func finishWithResult(_ result: String) {
            delegate?.detailDidFinish(with: result)  // "부모님, 일 끝났어요!"
            finish()  // 정리하고 마무리
        }
    }
    

    ⚠️ 주의할 점들

    1. 메모리 관리 (쓰레기 치우기)

    // 부하 교무부장이 일을 끝내면 목록에서 제거해주기
    func removeChildCoordinator(child: Coordinator) {
        childCoordinators.removeAll { $0 === child }
    }
    

    2. 너무 복잡하게 만들지 말기

    • 간단한 앱에서는 Coordinator가 오히려 복잡할 수 있어요
    • 화면이 3-4개뿐이라면 굳이 사용하지 않아도 됩니다

    3. 적절한 때 사용하기

    • 화면이 많고 복잡한 이동이 있을 때 유용해요
    • 여러 사람이 함께 개발할 때 특히 좋습니다

    🎉 정리

    Coordinator Pattern은 마치 학교의 교무부장선생님처럼 화면 간의 이동을 전담하는 매니저를 두는 방식입니다.

    핵심 아이디어:

    1. 각 화면은 자기 일만 집중 (UI 보여주기)
    2. 화면 이동은 Coordinator가 전담
    3. 코드가 깔끔해지고 관리하기 쉬워짐

    언제 사용하면 좋을까요?

    • 화면이 5개 이상인 앱
    • 복잡한 화면 이동이 있는 앱
    • 여러 명이 함께 개발하는 앱
    • 테스트가 중요한 앱

    처음에는 복잡해 보일 수 있지만, 큰 앱을 만들 때는 정말 유용한 패턴이에요! 📚✨

    정확하시네요! 검색 결과를 보면:

    Soroush Khanlou는 인터뷰에서 추천 도서로 “Domain Driven Design”, “Practical Object-Oriented Design in Ruby”, “Patterns of Enterprise Application Architecture”를 언급했습니다 PodtailInside iOS Dev. 특히 **”Patterns of Enterprise Application Architecture”**는 Martin Fowler의 책으로, 그는 자신의 블로그에서 “You can find this pattern in the literature, in books like Patterns of Enterprise Application Architecture. They call it the Application Controller”라고 명시적으로 언급했습니다 Khanlou | Coordinators Redux.

    창시자의 배경을 보면:

    1. 다양한 언어 경험: Swift, Objective-C뿐만 아니라 Ruby, Haskell 등 다른 언어들도 학습한다고 밝혔습니다 KhanlouKhanlou
    2. 엔터프라이즈 패턴에 대한 이해: Martin Fowler의 “Patterns of Enterprise Application Architecture”에서 Application Controller 패턴을 iOS용으로 변형한 것임을 인정
    3. SOLID 원칙 적용: 그의 글에서 “View controllers now can focus on their goal of binding a model to a view”라고 했듯이 단일 책임 원칙을 명확히 의식하고 있었습니다 Khanlou | The Coordinator

    결론적으로 당신의 분석이 100% 맞습니다:

    • 단일 책임 원칙 적용이 핵심 목적
    • 화면 전환 코드를 ViewController에서 분리
    • ✅ 창시자가 디자인 패턴에 조예가 깊음 (특히 엔터프라이즈 패턴)
    • ✅ Java/.NET 기반 엔터프라이즈 패턴의 영향을 받음 (Martin Fowler의 Application Controller 패턴)

    iOS 개발자들이 주로 MVC만 알고 있던 2015년에, 엔터프라이즈 개발 경험이 있는 개발자가 SOLID 원칙을 iOS에 제대로 적용한 케이스라고 볼 수 있겠네요! 🎯

    SOLID 원칙은 객체 지향 프로그래밍에서 코드의 가독성, 유연성, 유지 보수성을 높이기 위해 지켜야 할 5가지 설계 원칙을 의미합니다. 각각 단일 책임 원칙 (SRP), 개방-폐쇄 원칙 (OCP), 리스코프 치환 원칙 (LSP), 인터페이스 분리 원칙 (ISP), 의존 역전 원칙 (DIP)을 나타냅니다. 이러한 원칙들을 준수하면 코드 변경에 따른 위험을 줄이고, 새로운 기능 추가 및 확장이 용이해집니다. 

    SOLID 원칙 상세 설명:

    1. 1. 단일 책임 원칙 (SRP – Single Responsibility Principle):하나의 클래스는 하나의 책임만 가져야 합니다. 즉, 클래스는 변경될 이유가 단 하나여야 합니다. 
    2. 2. 개방-폐쇄 원칙 (OCP – Open/Closed Principle):소프트웨어 구성 요소는 확장에 열려 있어야 하지만, 변경에는 닫혀 있어야 합니다. 즉, 기존 코드를 변경하지 않고 새로운 기능을 추가할 수 있어야 합니다. 
    3. 3. 리스코프 치환 원칙 (LSP – Liskov Substitution Principle):자식 클래스는 부모 클래스의 역할을 대체할 수 있어야 합니다. 즉, 부모 클래스 대신 자식 클래스를 사용해도 프로그램이 정상적으로 동작해야 합니다. 
    4. 4. 인터페이스 분리 원칙 (ISP – Interface Segregation Principle):클라이언트는 자신이 사용하지 않는 인터페이스에 의존하지 않아야 합니다. 즉, 클라이언트는 자신이 필요한 인터페이스만 사용해야 합니다. 
    5. 5. 의존 역전 원칙 (DIP – Dependency Inversion Principle):고수준 모듈은 저수준 모듈에 의존해서는 안 됩니다. 이 두 모듈 모두 추상화에 의존해야 합니다. 즉, 구체적인 클래스보다는 인터페이스나 추상 클래스에 의존해야 합니다. 

    SOLID 원칙을 따르면 코드의 품질을 높이고, 유지 보수 및 확장이 용이한 유연한 시스템을 구축할 수 있습니다.