일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
- CI/CD
- 동시성프로그래밍
- gitlabci/cd
- CICD
- apple intelligence
- ai expo
- 동작과정
- rxswift
- cleanarchitecture
- RxCocoa
- gitlab
- Content Compression Resistance priority
- Content Hugging priority
- OperationQueue
- AI
- ReactiveX
- 오토레이아웃
- 알고리즘
- IOS
- Union-Find
- 클린아키텍처
- swift
- swift알고리즘
- 애플인텔리전스
- 자료구조
- Autolayout
- 백준
- RxSwift요약
- LLM
- mvvm
- Today
- Total
JosephCha의 개발일지
Coordinator 패턴 본문
ViewController에서의 화면전환 문제점은?
1. ViewController에서 화면 전환은 아래 코드처럼 간단히 구현할 수 있음
self.navigationController?.present(anotherViewController, animated: true)
2. 하지만 앱이 화면이 많아지고 복잡해짐에 따라, 동일한 화면으로 화면 전환하고 싶은 곳이 많아지거나 기존 같은 화면이 아닌 다른 화면으로 전환하고 싶게 된다면, 해당 코드들을 다 찾으면서 수정하는 번거로운 일이 발생함.
3. 이러한 화면 전환 코드는 작성된 ViewController가 담당하게 되고, 하드코딩되어 있어 관리하기 힘들어짐 (안그래도 많은 일을 담당하는데..)
4. View Controller간의 의존성도 생기게 됨
Coordinator 패턴이란?
1. Coordinator 패턴은 ViewController로 부터 화면 전환의 부담을 줄여주고, 화면전환을 보다 더 관리하기 쉽도록 도와주기 위한 패턴
2. Coordinator 패턴을 사용함으로써, ViewController 사이에 결합도를 낮춰줌. 각 ViewController는 이전에 어떤 컨트롤러가 있었는지, 다음에 어떤 컨트롤러가 오는지 알 필요가 없음. 대신에 이러한 flow는 Coordinator가 관리함. 오로지 Coordinator만이 이것을 알고 관리함
3. 결과적으로, 어떠한 순서로든 컨트롤러 전환이 가능하고, 재사용 까지도 가능함. hard-coding을 피할 수 있게 됨
4. ViewController은 데이터를 표시하는 법 외에는 아무것도 모르게됨. 따라서 재사용하기 용이해짐
5. 앱의 모든 작업과 하위 작업은 전용 캡슐화 방법을 제공
6. Coordinator는 display-binding을 side effects와 분리.
ViewController를 표시할 때 ViewController가 데이터를 엉망으로 만들지 여부에 대해 걱정할 필요가 없어짐. 읽기 및 디스플레이만 할 수 있으며 데이터를 쓰거나 손상시키지 않음.
7. Coordinator는 완전히 제어할 수 있는 객체
viewDidLoad가 호출 될 때 까지 기다리지 않고 전적으로 show를 제어할 수 있음. 호출을 받는 대신 호출을 시작함
기본 예시!
- MainCoordinator.swift
protocol Coordinator: AnyObject {
func start()
func pushDetailViewController(name: String)
}
class MainCoordinator: Coordinator {
var navigationController: UINavigationController
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func start() {
let mainVC = MainViewController()
mainVC.coordinator = self
navigationController.pushViewController(mainVC, animated: true)
}
func pushDetailViewController(name: String) {
let detailVC = DetailViewController()
detailVC.name = name
detailVC.navigationItem.title = "상세화면"
navigationController.pushViewController(detailVC, animated: false)
}
}
- MainViewController.swift
final class MainViewController: UIViewController {
weak var coordinator: Coordinator?
func tapButton() {
coordinator?.pushDetailViewController(name: "Joseph Cha")
}
}
- DetailViewController.swift
final class DetailViewController: UIViewController {
var name: String?
}
- SceneDelegate.swift
import UIKit
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
var coordinator: Coordinator?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = scene as? UIWindowScene else { return }
let navigationController = UINavigationController()
self.window = UIWindow(windowScene: windowScene)
self.window?.rootViewController = navigationController
self.window?.makeKeyAndVisible()
self.coordinator = MainCoordinator(navigationController: navigationController)
self.coordinator?.start()
}
}
- MainVC가 첫화면일 때, 해당 화면전환을 담당할 MainCoordinator를 생성함
- Appdelegate or SceneDelegate에서 MainCoordinator의 start 메소드를 통해 첫화면을 띄움
- MainVC에서 버튼을 눌러(tapButton) DetailVC로 이동하고자, MainVC에 coordinator를 담당하는 delegate를 만들고tapButton 메소드 호출 시 해당 delegate를 통해 MainCoordinator에서 DetailVC로 화면 전환 하도록 함
Sub-Coordinator
ParentCoordinator : childCoordinator를 생성하고 제거하는 역할
ChildCoordinator : 화면을 전환하는 역할
앱이 커지는 경우 Child coordinator(or subcoordinator)를 사용할 수 있다. 예를 들어 ChildCoordinator로 계정 생성 흐름을 제어하고, 다른 ChildCoordinator로 제품을 구매하는 흐름을 제어할 수 있다.
또한 ViewController들의 화면전환을 관리하는 Coordinator가 하나일 경우, 어떤 ViewController는 자신과 연관없는 다른 ViewController의 화면전환에 대한 메소드 정보를 Coordinator Delegate 사용 시 불필요하게 알게 될 수 있다. 하나의 Coordinator를 ChildCoordinator들로 쪼개면, 각각의 ViewController들은 자신과 연관된 화면 전환만 알게 될 수 있다.
하위 코디네이터 흐름은 간략히 다음과 같다.
ParentCoordinator 생성 -> ChildCoordinator 생성 -> ChildCoordinator 화면 전환 -> ChildCoordinator 제거
예시
Coordinator 프로토콜 정의
protocol Coordinator {
var childCoordinators: [Coordinator] { get set }
var navigationController: UINavigationController { get set }
func start()
func finish()
}
- Coordinator는 화면전환 시 기준이 되는 네비게이션 컨트롤러를 소유함
- 부모 자식 관계를 가지는 Coordinator 구조를 설계하므로, childCoordinator 프로퍼티를 추가함
- 해당 Coordinator의 화면 흐름 시작을 위한 start 메소드 추가함
- ViewController(or ViewModel)에서 해당 Coordinator 화면흐름을 끝내기 위한 finish메소드도 생성
ParentCoordinator 생성
final class ParentCoordinator: Coordinator {
var childCoordinators: [Coordinator] = []
var navigationController: UINavigationController
init(_ navigationController: UINavigationController) {
self.navigationController = navigationController
}
func start() {
let vc = ViewController()
vc.coordinator = self
navigationController.pushViewController(vc, animated: true)
}
}
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
var appCoordinator: Coordinator?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else { return }
let navigationController = UINavigationController()
self.window = UIWindow(windowScene: windowScene)
self.window?.rootViewController = navigationController
self.window?.makeKeyAndVisible()
self.appCoordinator = ParentCoordinator(navigationController)
self.appCoordinator?.start()
}
}
- Coordinator 프로토콜을 채택한 ParentCoordinator 생성
- Appdelegate나 SceneDelegate에서 ParentCoorinator생성 및 start메소드 호출
ChildCoordinator 정의
final class ChildCoordinator: Coordinator {
weak var finishDelegate: CoordinatorFinishDelegate?
// 생략
}
protocol CoordinatorFinishDelegate: AnyObject {
func coordinatorDidFinish(childCoordinator: Coordinator)
}
- ChildCoordinator가 할일이 마무리 되어 ParentCoordinator에게 자신을 지워 달라고 요청하기 위한 finishDelegate도 포함해야함
ChildCoordinator 생성
class ParentCoordinator: Coordinator {
//생략
func showChild(){
let child = ChildCoordinator(navigationController: navigationController)
child.finishDelegate = self
childCoordinators.append(child)
child.start()
}
}
- ParentCoordinator에서 ChildCoordinator 생성 및 ChildCoordinator 화면 흐름 시작
ChildCoordinator의 화면전환 예시
class ChildCoordinator: Coordinator {
func pushToDetail(name: String) {
let detailVC = DetailViewController.instantiate()
detailVC.setNavigationTitle("상세화면")
detailVC.name = name
detailVC.coordinator = self
navigationController.pushViewController(detailVC, animated: true)
}
}
final class ChildViewController: UIViewController {
weak var coordinator: Coordinator?
func tapButton() {
coordinator?.pushToDetail(name: "Joseph Cha") // childCoordinator를 통한 화면전환
}
}
- start메소드를 통한 화면 흐름 시작 이후, ViewController(or ViewModel)에서 ChildCoordinator를 통해 화면전환
ChildCoordinator제거
final class ChildCoordinator: Coordinator {
weak var finishDelegate: CoordinatorFinishDelegate?
func finish() {
finishDelegate?.coordinatorDidFinish(childCoordinator: self)
}
// 생략
}
extension ParentCoordinator: CoordinatorFinishDelegate {
func coordinatorDidFinish(childCoordinator: Coordinator) {
for (index, coordinator) in childCoordinators.enumerated() {
if coordinator === childCoordinator {
childCoordinators.remove(at: index)
break
}
}
}
}
- ChildCoordinator가 해당 화면 종료를 원할 시, ParentCoordinator에서 자기 자신을 지우도록 finishDelegate의 제거 메소드를 호출함
참고
https://velog.io/@ellyheetov/Coordinator-Pattern
https://duwjdtn11.tistory.com/644
'iOS' 카테고리의 다른 글
동시성 프로그래밍(개념, GCD, OperationQueue) (0) | 2023.08.16 |
---|---|
RxSwift? 이거 하나로 종결 (전체 요약) (2) | 2023.08.09 |
iOS MVVM 패턴 (0) | 2023.08.09 |
SOLID 원칙 (0) | 2023.08.09 |
네트워크 Endpoint (0) | 2023.08.09 |