본문 바로가기
iOS/Swift

[Swift] Coordinator 패턴으로 화면 전환 +DIContainer(Swinject)

by 6cess 2024. 7. 13.

1. Coordinator 패턴의 목적

구현 방식을 이야기하기 전에 Coordinator 패턴이 고안된 이유를 살펴보면

ViewController(혹은 ViewModel)에서 화면 전환 로직을 포함한 다양한 로직들을 담게 되는 상황에서

비대해진 ViewController 혹은 ViewModel에서 화면 전환 로직을 분리하자는 목적으로 고안되었다고 한다.

 

기존의 일반적인 방식대로면 ViewController 내부에서 직접 어떤 화면으로 어떻게 화면을 전환할 것인지 로직을 작성했다.

하지만 이를 Coordinator라는 클래스에 따로 작성하여 ViewController 프로퍼티로 이 객체를 전달하여 화면 전환에 필요한 메서드만 호출하여 사용한다.

이런 방식은 ViewController에서 다음 화면이 어떤 ViewController인지 접근할 필요 없으며(결합도 낮아짐)

화면 전환에 대한 로직만 따로 분리되어 관리하기 용이해진다(응집도 높아짐)

 

2. 구현 방식

아래 유명한 리포를 참조했다.

https://github.com/kudoleh/iOS-Clean-Architecture-MVVM

 

Coordinator 기본 구성

// DIContainer 와 같이 ViewController들의 의존성 관리를 하는 곳에서 프로토콜을 채택하여 
// 의존성이 관리되는 ViewController를 받아온다.
protocol CoordinatorDependencies {
	func makeViewController(Params,Actions) -> UIViewController 
}

final class Coordinator {
	var childCoordinators: [Coordinator] = [] // 필요에 따라
	var navigationController: UINavigationController  
    private let dependencies: CoordinatorDependencies
    
    init(
        navigationController: UINavigationController,
        dependencies: CoordinatorDependencies
    ) {
        self.navigationController = navigationController
        self.dependencies = dependencies
    }
    
    func start() {
    	let vc = dependencies.makeViewController(Params, Actions)
        // let vc = dependencies.makeViewController(action: showMovieDetails)
        
        // VC의 프로퍼티로 coordinator를 전달할까? Action만 전달할까?
        viewController.coordinator = self 
       
        navigationController?.pushViewController(vc, animated: false)
    }
    
    // 화면 전환 로직을 여기서 구현 후 ViewController의 매개변수로 넘겨준다.
    // ViewController과 ViewModel 등에서는 화면전환 코드를 작성하지 않고 넘겨주는 액션 메서드 활용
    // private func showMovieDetails(movie: Movie) {
    //     let vc = dependencies.makeMoviesDetailsViewController(movie: movie)
    //     navigationController?.pushViewController(vc, animated: true)
    // }
}

 

위 코드에서 핵심은 화면전환 로직은 Coordinator 클래스 내부에서 모두 구현되고

이후 메서드 형태로 전달 받은 ViewController 쪽에서 이를 호출만 하면 된다는 점이다.

'화면전환 로직'이라는 관심사를 확실히 분리한다.

 

레퍼런스를 찾아보면 Coordinator를 ViewController 프로퍼티로 넘겨서 자유롭게 접근하는 예시도 있었고

결합도를 낮추기 위해 ViewController은 단순히 Action 메서드만 전달 받아서 호출하는 예시도 있었다 

어떤 것이 좋은지는 입맛에 맞게..

 

덤으로 연결되는 관심사 분리로 ViewController의 의존성 관리 및 생성은 CoordinatorDependencies 프로토콜을 통해 DIContainer로 위임한다.

 

class AppDIContainer {
    let container: Container

    init() {
        container = Container()
        
        // Register ViewControllers
        container.register(ViewController.self) { r in
            return ViewController()
        }
    }
}

extension AppDIContainer: CoordinatorDependencies {
    func makeViewController() -> ViewController {
        container.resolve(ViewController.self)!
    }
}

 

위 예시에서는 Coordinator 의 메서드로 start() 만 정의했지만 

popViewController

childDidFinish

coordinatorDidFinish

등을 정의해서 Viewcontroller에서 Coordinator에 접근하여 메서드를 실행하거나 ViewController의 생성자 프로퍼티로 넘겨주어 활용한다.

 

추가로 Coordinator나 ViewController이 사라질 때 메모리 관리를 위해 CleanUp 하는 메서드도 잘 구현해놓으면 좋다.

 

좀 더 구체적인 구현 예시는 아래 링크를 참조

https://medium.com/@maysam.shahsavari/a-comprehensive-guide-to-coordinator-pattern-in-swift-7e7647ecc525

 

A comprehensive guide to Coordinator Pattern in Swift

You can download the source code from maysamsh/coordinator. The article is available on my blog too.

medium.com