[iOS] iOS-Clean-Architecture and MVVM 학습 - 2

Hyunndy·2023년 2월 5일
0

🐸

Coordinator 패턴에 대해 학습했으니
이번 글에서는 appDelegate에서의 AppFlowCoordinator의 start() 함수 안에서 벌어지는 일을 학습해보겠습니다.
아직 Clean-Architecture를 나눈 부분 까지도 진도가 못나가고있네요 ㅜ_ㅜ


AppFlowContainer

AppDelegate에서 AppFlowContainer객체에

  • NavigationController
  • AppDIContainer
    를 주입해주고,
    appFlowCoordinator는 start()를 호출합니다.
    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는

  • appFlowCoordinator
  • MoviesSearchFlowCoordinator
    두 가지 입니다.

앱에서 일어나는 모든 Flow 로직을 MoviesSearchFlowCoordinator에서 처리하고 있습니다.


MoviesSearchFlowCoordinator

이 프로젝트의 ViewController는 3개 있습니다.

  • MovieList
  • MovieDetails
  • MoviesQueriesList
    앱을 실행하자마자 실행되는 VC는 MovieList 입니다.
    따라서 appFlowCoordinator의 start() 함수에서는 결과적으로 MovieList VC를 Push하는 코드가 들어갑니다!

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)

  • 통신을 위한 DataTransferService
  • Persistent Storage
  • Repository
    2️⃣ (Clean Architecture - Use Cases)
  • ViewModel에 주입되는 Use Case 객체
    3️⃣ FlowCoordinator

를 생성하는 코드가 들어있습니다.

이 글에서 집중할 곳은 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에

  • UseCase
  • action(?)
    을 주입하고

ViewController에

  • ViewModel
  • ImageRepositiory (이미지 통신 객체)
    를 주입해서 생성하고 있습니다.

나머지는 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
    }
  • VC -> ViewModel -> Coordinator 액션
  • ViewController, ViewModel 생성
    - Use Case, Repository 주입
  • VC Push

생성해서 네비게이션에 Push까지 필요한 Flow 로직이 완성되었습니다.


느낀점

Coordinator 패턴을 학습하고 코드를 다시 보니 저번에는 이해 안갔던 코드가 이해가 되서 좋았습니다.
정말 객체를 만들 때 그 객체의 역할 컨셉에 확실하다는 것을 DI의 코드를 보면서 깨달았습니다.
내일은 드디어 MovieListVC를 살펴보며 Clean-Architecture 다운 글을 포스팅하겠습니다.

profile
https://hyunndyblog.tistory.com/163 티스토리에서 이사 중

0개의 댓글