Coordinator 패턴에 대해 학습했으니
이번 글에서는 appDelegate에서의 AppFlowCoordinator의 start() 함수 안에서 벌어지는 일을 학습해보겠습니다.
아직 Clean-Architecture를 나눈 부분 까지도 진도가 못나가고있네요 ㅜ_ㅜ
AppDelegate에서 AppFlowContainer객체에
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
AppAppearance.setupAppearance()
window = UIWindow(frame: UIScreen.main.bounds)
let navigationController = UINavigationController()
window?.rootViewController = navigationController
appFlowCoordinator = AppFlowCoordinator(navigationController: navigationController,
appDIContainer: appDIContainer)
appFlowCoordinator?.start()
window?.makeKeyAndVisible()
return true
}
appFlowCoordinator는 one-high-level Coordinator로, 모든 ViewController를 감독합니다.
이 프로젝트에서 Coordinator는
앱에서 일어나는 모든 Flow 로직을 MoviesSearchFlowCoordinator에서 처리하고 있습니다.
이 프로젝트의 ViewController는 3개 있습니다.
MovieListVC를 Push하기 위해 start() 함수에서는 MoviesSearchFlowCoordinator 객체를 생성하고, start() 시킵니다.
func start() {
// In App Flow we can check if user needs to login, if yes we would run login flow
let moviesSceneDIContainer = appDIContainer.makeMoviesSceneDIContainer()
let flow = moviesSceneDIContainer.makeMoviesSearchFlowCoordinator(navigationController: navigationController)
flow.start()
}
AppDelegate에서와 마찬가지로 FlowCoordinator에 DI객체를 주입시키고 start()를 호출합니다.
잠시 MovieSceneDIContainer를 살펴보면...
MovieSceneDIContainer에는
1️⃣ (Clean Architecture - Data Layer)
를 생성하는 코드가 들어있습니다.
이 글에서 집중할 곳은 3번, FlowCoordinator를 생성하는 부분입니다.
// MARK: - Flow Coordinators
func makeMoviesSearchFlowCoordinator(navigationController: UINavigationController) -> MoviesSearchFlowCoordinator {
return MoviesSearchFlowCoordinator(navigationController: navigationController,
dependencies: self)
}
저 dependency는 MoviesSearchFlowCoordinatorDependencies형 입니다.
protocol MoviesSearchFlowCoordinatorDependencies {
func makeMoviesListViewController(actions: MoviesListViewModelActions) -> MoviesListViewController
func makeMoviesDetailsViewController(movie: Movie) -> UIViewController
func makeMoviesQueriesSuggestionsListViewController(didSelect: @escaping MoviesQueryListViewModelDidSelectAction) -> UIViewController
}
Coordinator에서 Flow 로직을 타는 객체를 DIContainer에서 주입받을 수 있도록 protocol을 선언하여 직접 리턴하는 부분은 DIContainer에서 구현하도록 되어있습니다.
우리가 필요한 MoviesListController를 생성하는 부분은
// MARK: - Movies List
func makeMoviesListViewController(actions: MoviesListViewModelActions) -> MoviesListViewController {
return MoviesListViewController.create(with: makeMoviesListViewModel(actions: actions),
posterImagesRepository: makePosterImagesRepository())
}
func makeMoviesListViewModel(actions: MoviesListViewModelActions) -> MoviesListViewModel {
return DefaultMoviesListViewModel(searchMoviesUseCase: makeSearchMoviesUseCase(),
actions: actions)
}
ViewModel에
ViewController에
나머지는 Clean-Architecture과 관련있는것 같은데, 저 action은 무엇일까요?
struct MoviesListViewModelActions {
/// Note: if you would need to edit movie inside Details screen and update this Movies List screen with updated movie then you would need this closure:
/// showMovieDetails: (Movie, @escaping (_ updated: Movie) -> Void) -> Void
let showMovieDetails: (Movie) -> Void
let showMovieQueriesSuggestions: (@escaping (_ didSelect: MovieQuery) -> Void) -> Void
let closeMovieQueriesSuggestions: () -> Void
}
ViewModel에 주입되는 Action은
ViewController의 액션 -> ViewModel에서 필요한 데이터 세팅 -> Coordinator에서 Flow 로직 수행
되는 경우를 정의해놓은 struct 입니다.
showMovieDetail을 예시로 들면,
// 1. ViewController에서 Cell 클릭
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
viewModel.didSelectItem(at: indexPath.row)
}
// 2. ViewModel에서 데이터 연결하여 action 호출
func didSelectItem(at index: Int) {
actions?.showMovieDetails(pages.movies[index])
}
// 3. 액션의 구현부 in MovieSceneCoordinator
private func showMovieDetails(movie: Movie) {
let vc = dependencies.makeMoviesDetailsViewController(movie: movie)
navigationController?.pushViewController(vc, animated: true)
}
MoviesSearchFlowCoordinator의 init()과 start()에 들어있는 정보는 얼추 살펴보았습니다.
그럼 start() 함수를 볼까요?
func start() {
// Note: here we keep strong reference with actions, this way this flow do not need to be strong referenced
let actions = MoviesListViewModelActions(showMovieDetails: showMovieDetails,
showMovieQueriesSuggestions: showMovieQueriesSuggestions,
closeMovieQueriesSuggestions: closeMovieQueriesSuggestions)
let vc = dependencies.makeMoviesListViewController(actions: actions)
navigationController?.pushViewController(vc, animated: false)
moviesListVC = vc
}
생성해서 네비게이션에 Push까지 필요한 Flow 로직이 완성되었습니다.
Coordinator 패턴을 학습하고 코드를 다시 보니 저번에는 이해 안갔던 코드가 이해가 되서 좋았습니다.
정말 객체를 만들 때 그 객체의 역할 컨셉에 확실하다는 것을 DI의 코드를 보면서 깨달았습니다.
내일은 드디어 MovieListVC를 살펴보며 Clean-Architecture 다운 글을 포스팅하겠습니다.