MVVM-C?
- MVVM-C 는 MVVM에서 뷰컨트롤러 계층을 관리하는 Coordinator를 따로 분리 하는 것!
- Coordinator 의 가장 중요한 역활은 viewcontroller로 부터 화면전환 로직을 가져가는 것이다.
- 장점
- ViewController로 하여금 뷰모델과 UI바인딩, 그리고 UI액션을 처리하는 일부 기능이외의 책임을 줄일 수 있다
- ViewController가 독립적이므로 재사용하기 용이하다.
- 의존성을 외부에서 주입(DI)할 수 있다
- ViewController의 계층, 화면 간의 연결 플로우를 Coordinato에서 모아 관리 할 수 있다.
MVVM-C 적용하기
기존 코드
func didLogin() {
let mainVC = MainViewController(with: object)
self.navigationController?.present(mainVC, animated: true, completion: nil)
}
문제점
- 1. ViewController가 화면전환로직 다음 단계를 직접 결정
-
let mainVC = MainViewController(with: object)
-
- 2. ViewController가 부모 ViewController에게 해야할 일에 대한 메세지를 보내고 있다.
-
self.navigationController?.present(mainVC, animated: true, completion: nil)
-
- 3. 위와 같은 화면전환로직 처리가 여러 ViewController에 분산 되어 있다
해결방안
- 전체 앱에서 ViewController관리하고 이들 간에 흐름 로직을 처리하는 높은 수준의 객체가 필요 → Coordinators
코드 리팩토링
다음과 같은 구조로 리팩토링
![](https://blog.kakaocdn.net/dn/b3OPUC/btrAcvKSG9l/tafangJMm5HQ8AHLmY2eOK/img.png)
- Coordinator 프로토콜을 작성
protocol Coordinator : class {
var childCoordinators : [Coordinator] { get set }
func start()
}
2. App Coordinator 구현
- isLoggedIn이 true면 Main화면을, 아니라면 Login화면을 보여준다
class AppCoordinator: Coordinator {
var childCoordinators: [Coordinator] = []
private var navigationController: UINavigationController!
var isLoggedIn: Bool = false
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func start() {
if self.isLoggedIn {
self.showMainViewController()
} else {
self.showLoginViewController()
}
}
private func showLoginViewController() {
//
}
private func showMainViewController() {
//
}
}
3. LoginCoordinator구현
class LoginCoordinator: Coordinator {
var childCoordinators: [Coordinator] = []
private var navigationController: UINavigationController!
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func start() {
let viewController = LoginViewController()
self.navigationController.viewControllers = [viewController]
}
}
4-1. LoginViewController에서 로그인이 되면 LoginCoordinator에게 메시지를 전달하도록 LoginViewControllerDelegate 구현
protocol LoginViewControllerDelegate {
func login()
}
class LoginViewController: UIViewController {
var delegate: LoginViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
// 생략..
func didLogin() {
self.delegate?.login()
}
}
4-2. LoginCoordinator는 LoginViewControllerDelegate 준수 및 로그인이 되었음을 AppCoordinator에게 전달하도록 LoginCoordinatorDelegate 구현
protocol LoginCoordinatorDelegate {
func didLoggedIn(_ coordinator: LoginCoordinator)
}
class LoginCoordinator: Coordinator, LoginViewControllerDelegate {
var childCoordinators: [Coordinator] = []
var delegate: LoginCoordinatorDelegate?
private var navigationController: UINavigationController!
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func start() {
let viewController = LoginViewController()
viewController.delegate = self // 1.LoginViewControllerDelegate Conform
self.navigationController.viewControllers = [viewController]
}
func login() { // 1.LoginViewControllerDelegate Conform
self.delegate?.didLoggedIn(self) // 2. AppCoordinator에게 메세지 전달
}
}
5-1. AppCoordinator는 showLoginViewController()에서 LoginCoordinator의 delegate를 자신으로 설정함으로써LoginCoordinatorDelegate를 준수한다
class AppCoordinator: Coordinator, LoginCoordinatorDelegate {
var childCoordinators: [Coordinator] = []
private var navigationController: UINavigationController!
var isLoggedIn: Bool = false
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func start() {
if self.isLoggedIn {
self.showMainViewController()
} else {
self.showLoginViewController()
}
}
private func showMainViewController() {
//
}
private func showLoginViewController() {
let coordinator = LoginCoordinator(navigationController: self.navigationController)
coordinator.delegate = self // 1-1. LoginCoordinator의 delegate를 자신으로 설정
coordinator.start()
self.childCoordinators.append(coordinator)
}
// 1-2.LoginCoordinatorDelegate를 준수
func didLoggedIn(_ coordinator: LoginCoordinator) {
self.childCoordinators = self.childCoordinators.filter { $0 !== coordinator }
self.showMainViewController()
}
}
5-2. MainViewController와 MainCooridnator도 Login 과 같이 구현함으로써 AppCoordinator에서 MainViewContoller로 이동할 수 있다
class AppCoordinator: Coordinator, LoginCoordinatorDelegate {
var childCoordinators: [Coordinator] = []
private var navigationController: UINavigationController!
var isLoggedIn: Bool = false
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
func start() {
if self.isLoggedIn {
self.showMainViewController()
} else {
self.showLoginViewController()
}
}
private func showMainViewController() {
// MainViewController로 이동 !
let coordinator = MainCoordinator(navigationController: self.navigationController)
coordinator.delegate = self
coordinator.start()
self.childCoordinators.append(coordinator)
}
private func showLoginViewController() {
let coordinator = LoginCoordinator(navigationController: self.navigationController)
coordinator.delegate = self
coordinator.start()
self.childCoordinators.append(coordinator)
}
func didLoggedIn(_ coordinator: LoginCoordinator) {
self.childCoordinators = self.childCoordinators.filter { $0 !== coordinator }
self.showMainViewController()
}
}
참고자료
'iOS 🍎 > iOS' 카테고리의 다른 글
iOS FireBase Crashlytics 적용 : CocoaPods, SPM (0) | 2022.03.29 |
---|---|
SPM, Swift Package Manager (0) | 2022.03.29 |
Xcode 13, No StoryBoard Settings without SceneDelegate (0) | 2022.03.22 |
Animation 2 - Core Animation (0) | 2022.03.21 |
CALayer vs UIView (0) | 2022.03.18 |