์ค๋์ ๋ณด๋ค ๋ ๊ตฌํ๊ณผ ๊ฐ๊น์ด Coordinator ๊ด๋ จ ๋ธ๋ก๊ทธ ๊ธ์ ์ฝ์ด ๋ณด๊ณ ์ ํฉ๋๋ค ๐ช
์๋ณธ ๋งํฌ๋ ์๊ธฐ..
Three Problems
Overstuffed App Delegates
App Delegate์์ ์๊ธธ ์ ์๋ ๋ฌธ์ ์ ์ ์ฌ๊ธฐ์ ๋ชจ๋ ๊ฒ์ ์ค์ ๋ฃ์ ์ ์๋ค๋ ๊ฒ์ด๋ค ,.. ์๋ฅผ ๋ค์ด ์ฑ์ root๋ก TabBarController๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ๊ทธ tabBar์ child viewController๋ฅผ ์ธํ ํด์ค์ผ๋๋๋ฐ ๊ทธ ์์น๊ฐ App Delegate๊ฐ ๋ ํ๋ฅ ์ด ๋๋ค๋ ๊ฒ์ด์ง. ํ์ง๋ง ๊ฑฐ๊ธฐ๊ฐ ๋ง๋ ์์น๋ ์๋๋ผ๋๊ฑฐ…. ์ด๋ฌํ app configuration logic์ ์ํด ์กฐ๊ธ ๋ ์ข์ ์๋ฆฌ๊ฐ ํ์ํ๋ค !! (์ ์๋ฆฌ๊ฐ ์๋ง coordinator๊ฐ ๋์ง ์์๊น !)
Too Many Responsibilities
app delegate๊ฐ ๊ทธ๋ฌ๋๊ฒ์ฒ๋ผ ๊ฐ๊ฐ์ view controller๋ค๋ ๋๋ฌด ๋ง์ ์ญํ ๋ค๋ก ์ธํด ๊ณ ํต๋ฐ์ ์ ์์ต๋๋ค … View Controller์ ์ญํ ์ด ๋ฌด์์ธ์ง๋ ํ์คํ๊ฒ ์ ํ๊ณ ๊ฐ์ผํฉ๋๋ค ..
์ฐ๋ฆฌ๊ฐ view controller๋ค์๊ฒ ์๊ตฌํ๋ ์ญํ ๋ค …
- Model-View Binding
- Subview Allocation
- Data Fetching
- Layout
- Data Transformation
- Navigation Flow
- User Input
- Model Mutation
- and many more besides
๋๋ฌด ๋ง์ฃ ?
๊ทผ๋ฐ ์ด ์ค์์ ์ด๋ค ๋ถ๋ถ๋ค์ด view controller์ ๋จ์์ผํ๋๊ฐ ... ๊ทธ๊ฑด ์ง์ง ์ ํ๊ธฐ ์ด๋ ต๋ค...
ํ์คํ๊ฒ ๋ฐ์ดํฐ, ๊ทธ๋ํฝ์ด ์๋ ๋ชจ๋ ๊ฒ๋ค์ด controller ๋ด๋ถ์ ๋ค์ด๊ฐ๋ค๋ณด๋ controller๊ฐ ๊ฒฐ๊ตญ ๋ธ๋ํ์ฒ๋ผ ๋ชจ๋ ์ฝ๋๋ฅผ ๋นจ์๋ค์ฌ์ massive view controller๊ฐ ๋๋ฅ...
์ด๋ ๊ฒ ๋์ง ์๊ธฐ ์ํด์ ์ฐ๋ฆฌ๋ ๋ง์ ์ฃผ์๋ฅผ ๊ธฐ์ธ์ฌ์ผํ๋ค ..
Smooth Flow
Navigation Flow์ ๋ํด์๋ ์๊ธฐํด๋ณด๊ณ ์ถ์๋ฐ์ …
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let object = self.dataSource.object(at: indexPath)
let detailViewController = SKDetailViewController(detailObject: object)
self.navigationController?.pushViewController(detailViewController, animated: true)
}
ํํ๊ฒ ๋ดค๋ ์ด ์ฝ๋….์ด๊ฑด ์ ๊ฐ์ค๋ฝ๊ฒ๋ ์ฐ๋ ๊ธฐ์ ๋๋ค. ์ ๊ทธ๋ฐ์ง ํ์ค์ฉ ์ดํด๋ด ์๋ค.
let object = self.dataSource.object(at: indexPath)
์ฒซ๋ฒ์งธ ์ค์ ๋ฌธ์ ์์ต๋๋ค ใ
let detailViewController = SKDetailViewController(detailObject: object)
์ฌ๊ธฐ์๋ถํฐ ์ฝ๊ฐ ๋ฌธ์ ๊ฐ ์๊ธฐ๊ธฐ ์์ํ๋๋ฐ์…. ์๋ก์ด view controller๋ฅผ ์์ฑํ๊ณ configureํ๊ณ ์์ฃ ?
๊ทธ๋ฌ๋ฉด์ ๋ค์์ ์ด๋ค ๊ฐ์ฒด๊ฐ ์ค๊ฒ ๋๋์ง, ์ด๋ป๊ฒ configureํ๋์ง.. ๋๋ฌด ๋ง์ ๊ฒ๋ค์ ๋ํด ์๊ฒ ๋๋ฒ๋ฆฝ๋๋ค.
self.navigationController?.pushViewController(detailViewController, animated: true)
์ฌ๊ธฐ์๋ถํฐ ์์ …๋ชจ๋ ๊ฒ ๊ธธ์ ๋ฒ์ด๋๊ธฐ ์์ํฉ๋๋ค. viewController๊ฐ ๋ถ๋ชจ๊น์ง ๋ถ์ก๊ธฐ ์์…๊ทธ๋ฆฌ๊ณ ๋ถ๋ชจํํ ํ ์ผ์ ๋ํด์ ์ง์ ๋ฉ์ธ์ง๋ฅผ ๋ณด๋ด๊ณ ์์ต๋๋ค. bossing their parent around๋ผ๋ ํํ์ ์ฌ์ฉํ๋๋ฐ child๋ ์ ๋ parent๋ฅผ boss around ํด์๋ ์๋์ง ! children์ ๊ทธ๋ค์ ๋ถ๋ชจ๊ฐ ๋๊ตฐ์ง๋ ์์์๋ ์๋๋ค~~
Coordinators
What is a coordinator?
coordinator๋ ํ๋ ์ด์์ view controller๋ค์๊ฒ ๋ช ๋ น์ ๋ด๋ฆฌ๋ ๊ฐ์ฒด๋ค.
Flow logic์ view controller๋ถํฐ ๋ถ๋ฆฌํ๊ณ ์๋จ์ ๋ ์ด์ด๋ก ํ๋ ๋ ์์ฑํจ์ผ๋ก์จ ๋ ํจ์จ์ ์ธ ์ฝ๋๋ฅผ ์์ฑํ ์ ์๊ฒ ๋๋ ๊ฒ.
์ผ๋จ App Coordinator๊ฐ ์๋๋ฐ ์ต์๋จ์ coordinator๊ฐ์ ๋๋์ธ๊ฑฐ์ง.
view controller๋ค์ ์์ฑํ๊ณ configureํ๊ฑฐ๋ ์๋ก์ด child coordinator๋ฅผ ์์ฑํ ์๋ ์๊ณ ~ (์์์ ๋งํ๋ overstuffed app delegate์ ๋ฌธ์ ๋ฅผ app coordinator๋ฅผ ํตํด ํด๊ฒฐํ ์ ์์!)
๊ทธ๋ผ Coordinator์ ์ญํ ์ ๋ฌด์์ด ๋ผ์ผํ๋?
๋๋ ๋ค๋น๊ฒ์ด์ ๋ง ์๊ฐํ๋๋ฐ ์ด ์ ์๋ model mutation๊น์ง ์๊ธฐํ๊ณ ์๋ค… (user’s change๋ฅผ db์ ์ ์ฅํ๊ฑฐ๋ API์ PUT, POST ์์ฒญ์ ๋ณด๋ด๋ ๋ฑ… ์ ์ ์ ๋ฐ์ดํฐ๋ฅผ ๋ณํํ ์ ์๋ ํ์๋ค๊น์ง coordinator๊ฐ ๋ด๋นํด์ผํ๋ค….) ์ ๊ทธ๋์ผํ๋์ง ์ข ๊ถ๊ธํ๋น…ใ ใ กใ ๊ทธ๋ฅ ๋ฑ navigation๋ง ๋ด๋นํ๋๊ฒ ๋ซ์ง์๋…์๋๊ฐ….
์ด ๋ฐ์ ์ Model mutation๊น์ง ๋ด๋นํด์ผํ๋์ง ์๋ ค์ฃผ๋๊ฑฐ๊ฐํผ๋ฐ coordinator๋ฅผ ์ ์ฉํ๊ณ ๋๋ฉด view controller๋ presented, fetch data, transform it for presentation, display it ๊น์ง ๋ด๋นํ๊ณ ๋ณํ์ ํ ์ ์๋ค ! alterํ๋๊ฑด coordinator์ ์ญํ …? ์์ง ์ ์ดํด๊ฐ ์๋ฉ๋๋ค ใ ใ ์์๋ฅผ ์ผ๋จ ๋ด์ผ๊ฒ ๊ตฐ….
Code Example
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
rootViewController = UINavigationController()
appCoordinator = SKAppCoordinator(navigationController: rootViewController)
appCoordinator?.start()
window?.makeKeyAndVisible()
return true
}
App delegate์์๋ app์ window, root view controller๋ฅผ ์ธํ ํ๊ณ app coordinator๋ฅผ fire up!ํ๋ค. ์ด๋ appCoordinator์ initialization๊ณผ start ๋ฉ์๋๋ ๋ถ๋ฆฌ๋ผ์๋ค๋ ๊ฒ์ ํ์ธ~ ์ด๋ฅผ ํตํด ์์ฑํ๋ ๊ฑด ๋ง๋๋ก ํ๊ณ ์ํ ๋ startํ๋ฉด ๋๋ค๋ ๊ฒ์ ์ ์ ์๋น.
๊ทธ๋ฆฌ๊ณ ์ด๋ ์ค์ํ๊ฑด Coordinator๋ plain NSObject๋ผ๋ ๊ฒ !
closed source๋ผ์ ๋ด๋ถ ๋์์ ๋ค ์ด์ด๋ณผ ์ ์๋ UIViewController์ ๊ฐ์ ๊ฐ๋ ๊ณผ๋ ๋ค๋ฅด๊ฒ Coordinator๋ ์ฐ๋ฆฌ๊ฐ ์ง์ ๋ง๋ ๋จ์ํ ๊ฐ์ฒด์ด๊ณ ์ด๋ฌํ ๊ฐ์ฒด๊ฐ ์ฑ์ flow๋ฅผ ๋ด๋นํ๊ฒ ๋๋ฉด ๋ชจ๋ ๊ฑธ ๋จ์ํ๊ฒ ๋ง๋ ๋ค !
init?(navigationController: UINavigationController) {
self.navigationController = navigationController
super.init()
}
func start() {
if isLoggedIn() {
showContent()
} else {
showAuthentication()
}
}
์ด start ๋ฉ์๋๋ appCoordinator ๋ด๋ถ์ start ๋ฉ์๋
์ด์ ์๋ ์ด๋ฌํ ๋ก์ง์ viewController ํน์ appDelegate์ ๋ฃ์์ํ ๋ฐ…์ข์ง ์์! ์ฝ๊ฐ ์ด ๋ถ๋ถ์ ๋ณด๋๊น RIBs์์ ViewlessRIB์ ๋ณด๋ ๋ฏํ ๋๋ ?
func showAuthentication() {
let authCoordinator = SKAuthenticationCoordinator(navigationViewController: self.navigationController)
authCoordinator.delegate = self
authCoordinator.start()
self.childCoordinators.append(authCoordinator)
}
์ด ๋ฉ์๋ ์์์ child Coordinator๋ฅผ ์์ฑํ๊ณ App Coordinator์ childCoordinators ๋ฐฐ์ด์ ์ถ๊ฐํจ์ผ๋ก์จ ๋ฉ๋ชจ๋ฆฌ์์ ํด์ ๋๋ ๊ฒ์ ๋ฐฉ์งํ๊ณ ์์! ๊ทธ์น… ์๋๋ ๋ฉ์๋ ๋ด๋ถ์์ ์์ฑ๋ ๊ฐ์ฒด๋ ๋ฉ์๋๊ฐ ๋๋๋ฉด ๋ฉ๋ชจ๋ฆฌ์์ ํด์ ๋๋๊น…ใ ใ กใ
child coordinator๋ view controller๋ค์ ์์ฑํ๊ณ ๊ฑ๋คํํ ์ผ์ ์ํด. ๊ทธ๋ฆฌ๊ณ ๊ทธ๊ฒ ๋๋๋ฉด ๋ฉ์ธ์ง๋ฅผ ์ค ! ๊ทธ๋ฆฌ๊ณ ์ผ์ด ๋๋๋ฉด ์๊ธฐ ์์ ์ clean up ํ๊ณ parentํํ ๋ฉ์ธ์ง๋ฅผ ์ฃผ๊ธฐ ์ํด์ delegate๋ฅผ ์ฌ์ฉํจ.
func coordinatorDidAuthenticate(_ coordinator: SKAuthenticationCoordinator) {
if let index = self.childCoordinators.firstIndex(where: { $0 === coordinator }) {
self.childCoordinators.remove(at: index)
}
showContent()
}
Authentication์ด ๋๋๋ฉด delegate๋ก ํธ์ถ์ ๋ฐ์๊ฑฐ๊ตฌ child coordinator๋ก ํ์ฌ๊ธ ๋ฉ๋ชจ๋ฆฌ์์ ํด์ ๋๋๋ก ํ์ฉํด์คใ ใ
AuthCoordinator ๋ด๋ถ์์๋ ํ์ํ view controller๋ค์ ์์ฑํ๊ณ ๋ฐ์ navigation controller๋ก Push!
class AuthCoordinator {
var navigationController: UINavigationController
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
}
func start() {
// sign up, log in ๋ฒํผ์ด ์๋ viewController
let firstRunViewController = SKFirstRunViewController()
firstRunViewController.delegate = self
navigationController.pushViewController(firstRunViewController, animated: false)
}
FirstRunViewController ์์๋ signup ๋ฒํผ์ด ์๊ณ ์ด๊ฑฐ์ ๋ํ delegate๋ฅผ AuthCoordinator์์ ๊ฐ๊ณ ์์
func firstRunViewControllerDidTapSignup(_ firstRunViewController: SKFirstRunViewController) {
let signUpViewController = SKSignUpViewController()
signUpViewController.delegate = self
navigationController?.pushViewController(signUpViewController, animated: true)
}
์ฌ๊ธฐ์๋ ๋ง์ฐฌ๊ฐ์ง๋ก signUpViewController์ delegate๋ฅผ ์ฑํํ๋ฉด์ signUpViewController์์ ๋ฐ์ํ๋ ์ด๋ฒคํธ์ ๋ํ ์ฒ๋ฆฌ๋ฅผ ํด์ค ์ ์๊ฒ๋จ
// ์ฌ๊ธฐ์ signup API request ์คํํ๊ณ authentication token ์ ์ฅํ๊ณ parent coordinator์๊ฒ ๋ค ๋๋ฌ์ด~~~ ์๋ ค์ฃผ๊ฒ๋๋๊ณ ์ง
func signUpViewController(_ signUpViewController: SKSignUpViewController, didTapSignupWithEmail email: String, password: String) {
//...
}
๊ฒฐ๊ตญ user input๊ฐ์ ์ด๋ฒคํธ๊ฐ view controller์์ ์ผ์ด๋๋ฉด view controller๋ ๊ทธ ์ด๋ฒคํธ๋ฅผ delegate๋ก coordinator์๊ฒ ์ ๋ฌํ๊ณ ์ค์ ์ผ ์ฒ๋ฆฌ๋ coordinator๊ฐ ํ๋๊ณ ์ง… ํ ๊ทธ๋ผ view model์ ์ญํ ๊น์ง coordinator๊ฐ ํ๋๊ฑด๊ฐ? MVVM-C๋ ๊ทธ๋ผ ๊ธฐ์กด coordinator์ ์ญํ ์ธ navigation + model mutation์ viewModel๊ณผ coordinator๊ฐ ๊ฐ๊ฐ model mutation, navigation์ ๋งก๊ฒ ๋ถ๋ฆฌํ๋๊ฑด๊ฐ? ์์๋ด์ผ๊ฒ ๊ตฐ….
Why are coordinators great?
- viewController๋ค์ ์ญํ ์ด ๋ถ๋ฆฌ๋จ
์ด์ ViewController๋ ๋ฐ์ดํฐ๋ฅผ ๋ณด์ฌ์ฃผ๋ ๋ฐฉ๋ฒ ์ธ์๋ ์๋ฌด๊ฒ๋ ๋ชจ๋ฆ ! ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋ฉด delegate๋ฅผ ํธ์ถํ๋ ๊ฒ ์ธ์๋ ์๋ฌด๊ฒ๋ ํ์ง์์…ใ ใ ใ - ViewController๊ฐ ์ฌ์ฌ์ฉ๊ฐ๋ฅํด์ง
- ์ฑ์ ๋ชจ๋ ์์ ๊ณผ ํ์ ์์ ์ ์ด์ ์บก์ํ๋๋ ์ ์ฉ ๋ฐฉ๋ฒ์ด ์๋ค.
- ์ฝ๋๋ค์ดํฐ๋ side effect๊ณผ ๋์คํ๋ ์ด ๋ฐ์ธ๋ฉ์ ๋ถ๋ฆฌํ๋ค.
- ์ฝ๋๋ค์ดํฐ๋ ๋น์ ์ด ์์ ํ ํต์ ํ ์ ์๋ ๋์์ด๋ค.
์๋ฅผ ๋ค์ด UIKit์ด๋ผ๋ ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํ ๋๋ viewDidLoad ๋ฉ์๋๊ฐ ๋ถ๋ฆด๋๊น์ง ๊ธฐ๋ค๋ฆฌ๋ ๋ฑ ํ๋ ์์ํฌ์ ์์กดํ์ฌ ๊ฐ๋ฐํด์ผํ๋ ๋ถ๋ถ์ด ์์์ง๋ง plain NSObject์ธ coordinator๋ฅผ ์ฌ์ฉํ๋ฉด ๋ด๊ฐ ๋ชจ๋ flow๋ฅผ ํต์ ํ ์ ์๋ค๋ ์ฅ์ ์ด ์๋ค๋ ์ !!
์ด๋์ ๋ ์ฝ๋๋ฅผ ํตํด ๊ฐ์ ์ก์์ผ๋ ๋ค์์๋ ์ฝ๋ฐ์ฝ ํํ ๋ฆฌ์ผ์ ์ค์ตํด๋ณด๋ ๊ธ๋ก ๋์์ค๊ฒ ์ต๋๋ค ! ๐ซก
'๐ iOS' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Swift] Property Wrapper ๋ฝ๊ฐ๊ธฐ (1) (0) | 2024.01.12 |
---|---|
[Swift] defer๋ฅผ ์ด๋ป๊ฒ ์จ์ผ ์ ์ผ๋ค๊ณ ์๋ฌธ์ด ๋ ๊น (3) | 2024.01.05 |
[Swift] rethrows ํค์๋๋ ์ธ์ ์ฐ๋๊ฑธ๊น ๐ค (3) | 2024.01.02 |
[Coordinator] The Coordinator (4) | 2023.12.28 |