๐ญ Coordinator Pattern์ด ๋ญ๊ฐ์?
ํ๊ต ๋น์ ๋ก ์ดํดํด๋ณด๊ธฐ
ํ๊ต์์ ๊ฐ ๋ฐ ๋ด์์ ์๋์ด ์๋ค๊ณ ์๊ฐํด๋ณด์ธ์.
๊ธฐ์กด ๋ฐฉ์ (๋ฌธ์ ๊ฐ ์๋ ๋ฐฉ์):
- 1๋ฐ ๋ด์์ด ์ง์ 2๋ฐ, 3๋ฐ, 4๋ฐ์ ๋ชจ๋ ๊ด๋ฆฌ
- 2๋ฐ์์ ์ฒด์ก ์๊ฐ์ ์ด๋์ฅ ๊ฐ๋ ค๋ฉด 1๋ฐ ๋ด์์๊ฒ ํ๋ฝ๋ฐ๊ธฐ
- 3๋ฐ์์ ๊ธ์์ค ๊ฐ๋ ค๋ฉด 1๋ฐ ๋ด์์๊ฒ ๋ ํ๋ฝ๋ฐ๊ธฐ
- ๐ต ๋๋ฌด ๋ณต์กํ๊ณ ๋ด์์ ์๋์ด ๊ณผ๋ก์ ์ฐ๋ฌ์ง!
Coordinator ๋ฐฉ์ (๋๋ํ ๋ฐฉ์):
- ๊ฐ ๋ฐ๋ง๋ค ๋ด์์ ์๋์ด ์์ (๊ฐ๊ฐ์ ํ๋ฉด ๋ด๋น)
- ๊ต๋ฌด์ค์ ๊ต๋ฌด๋ถ์ฅ์ ์๋์ด ์์ด์ ๋ฐ ๊ฐ์ ์ด๋์ ์ด๊ด ๊ด๋ฆฌ
- 1๋ฐ์์ 2๋ฐ์ผ๋ก ๊ฐ๊ณ ์ถ์ผ๋ฉด โ ๊ต๋ฌด๋ถ์ฅ์๊ฒ ๋งํ๋ฉด โ ๊ต๋ฌด๋ถ์ฅ์ด ์์ ํ๊ฒ ์๋ด
- โจ ๊ฐ ๋ด์์ ์๊ธฐ ๋ฐ๋ง ์ ๊ฒฝ์ฐ๊ณ , ์ด๋์ ๊ต๋ฌด๋ถ์ฅ์ด ๋ด๋น!
๐ค ์ ์ด๋ฐ ๋ฐฉ์์ ์ธ๊น์?
๊ธฐ์กด ๋ฐฉ์์ ๋ฌธ์ ์
์ฑ์ ๋ง๋ค ๋, ๊ฐ ํ๋ฉด(ViewController)์ด ์ง์ ๋ค๋ฅธ ํ๋ฉด์ผ๋ก ์ด๋ํ๋ ์ฝ๋๋ฅผ ์์ฑํ๋ฉด:
- ๐ ์คํ๊ฒํฐ ์ฝ๋: ๋ชจ๋ ํ๋ฉด์ด ์๋ก ์ฝํ์์ด์ ์ฝ๋๊ฐ ๋ณต์กํด์ง
- ๐ ์ค๋ณต ์ฝ๋: ๊ฐ์ ํ๋ฉด์ผ๋ก ๊ฐ๋ ์ฝ๋๋ฅผ ์ฌ๋ฌ ๋ฒ ์์ฑํด์ผ ํจ
- ๐ ๋ฒ๊ทธ ๋ฐ์: ํ๋๋ฅผ ๊ณ ์น๋ฉด ๋ค๋ฅธ ๊ณณ์ด ๋ง๊ฐ์ง
- ๐ฐ ํ ์คํธ ์ด๋ ค์: ๋ชจ๋ ๊ฒ ์ฐ๊ฒฐ๋์ด ์์ด์ ํ ์คํธํ๊ธฐ ํ๋ฆ
Coordinator Pattern์ ์ฅ์
- ๐ฏ ๊น๋ํ ์ญํ ๋ถ๋ด: ๊ฐ ํ๋ฉด์ ์๊ธฐ ์ผ๋ง, ์ด๋์ Coordinator๊ฐ ๋ด๋น
- ๐ง ์ฌ์ด ์์ : ํ๋ฉด ์ด๋ ๋ฐฉ์์ ๋ฐ๊พธ๊ณ ์ถ์ผ๋ฉด Coordinator๋ง ๊ณ ์น๋ฉด ๋จ
- โป๏ธ ์ฌ์ฌ์ฉ ๊ฐ๋ฅ: ํ ๋ฒ ๋ง๋ ํ๋ฉด์ ์ฌ๋ฌ ๊ณณ์์ ์ฝ๊ฒ ์ฌ์ฉ
- โ ํ ์คํธ ์ฌ์: ๊ฐ ๋ถ๋ถ์ ๋ฐ๋ก๋ฐ๋ก ํ ์คํธํ ์ ์์
๐๏ธ ์ด๋ป๊ฒ ๋ง๋๋์?
1๋จ๊ณ: ๊ต๋ฌด๋ถ์ฅ(Coordinator) ์ญํ ์ ํ๊ธฐ
// ๋ชจ๋ ๊ต๋ฌด๋ถ์ฅ์ด ์ง์ผ์ผ ํ ๊ท์น
protocol Coordinator: AnyObject {
var navigationController: UINavigationController { get set } // ๋ณต๋ ์ญํ
var childCoordinators: [Coordinator] { get set } // ๋ถํ ๊ต๋ฌด๋ถ์ฅ๋ค
func start() // ์
๋ฌด ์์!
}
์ฝ๊ฒ ๋งํ๋ฉด:
navigationController
: ํ๊ต ๋ณต๋ ๊ฐ์ ์ญํ (ํ๋ฉด ๊ฐ ์ด๋ํต๋ก)childCoordinators
: ๋ถํ ๊ต๋ฌด๋ถ์ฅ๋ค ๋ชฉ๋กstart()
: “์, ์ด์ ์์ํด!”
2๋จ๊ณ: ๊ตฌ์ฒด์ ์ธ ๊ต๋ฌด๋ถ์ฅ ๋ง๋ค๊ธฐ
// ๋ฉ์ธ ๊ต๋ฌด๋ถ์ฅ ํด๋์ค
class MainCoordinator: Coordinator {
var navigationController: UINavigationController
var childCoordinators = [Coordinator]()
init(navigationController: UINavigationController) {
self.navigationController = navigationController
}
// ์ฑ ์์ํ ๋ ์ฒซ ํ๋ฉด ๋ณด์ฌ์ฃผ๊ธฐ
func start() {
let firstScreen = ViewController.instantiate()
firstScreen.coordinator = self // "๋ด๊ฐ ๋์ ๊ต๋ฌด๋ถ์ฅ์ด์ผ"
navigationController.pushViewController(firstScreen, animated: false)
}
// ์์ธ ํ๋ฉด์ผ๋ก ์ด๋ํ๊ธฐ
func showDetailScreen() {
let detailCoordinator = DetailCoordinator(navigationController: navigationController)
childCoordinators.append(detailCoordinator) // ๋ถํ ๊ต๋ฌด๋ถ์ฅ ๋ฑ๋ก
detailCoordinator.start() // "์์ธํ๋ฉด ๋ด๋น ๊ต๋ฌด๋ถ์ฅ, ์ถ๋!"
}
}
3๋จ๊ณ: ํ๋ฉด(ViewController) ์ฝ๊ฒ ๋ง๋ค๊ธฐ
// ์คํ ๋ฆฌ๋ณด๋์์ ํ๋ฉด์ ์ฝ๊ฒ ๊ฐ์ ธ์ค๋ ๋๊ตฌ
protocol Storyboarded {
static func instantiate() -> Self
}
extension Storyboarded where Self: UIViewController {
static func instantiate() -> Self {
// ๋ง๋ฒ์ ์ฃผ๋ฌธ์ผ๋ก ์คํ ๋ฆฌ๋ณด๋์์ ํ๋ฉด ์ํ!
let fullName = NSStringFromClass(self)
let className = fullName.components(separatedBy: ".")[1]
let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)
return storyboard.instantiateViewController(withIdentifier: className) as! Self
}
}
4๋จ๊ณ: ์ฑ ์์ํ ๋ ์ค์ ํ๊ธฐ
// AppDelegate - ์ฑ์ ๊ต์ฅ์ ์๋
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var coordinator: MainCoordinator? // ๋ฉ์ธ ๊ต๋ฌด๋ถ์ฅ
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// 1. ๋ณต๋(NavigationController) ๋ง๋ค๊ธฐ
let navController = UINavigationController()
// 2. ๋ฉ์ธ ๊ต๋ฌด๋ถ์ฅ ์๋ช
ํ๊ธฐ
coordinator = MainCoordinator(navigationController: navController)
coordinator?.start() // "๊ต๋ฌด๋ถ์ฅ๋, ์
๋ฌด ์์ํด์ฃผ์ธ์!"
// 3. ํ๊ต(์ฑ) ๋ฌธ ์ด๊ธฐ
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = navController
window?.makeKeyAndVisible()
return true
}
}
๐ฏ ์ค์ ์ฌ์ฉ ์์
๋ก๊ทธ์ธ โ ๋ฉ์ธํ๋ฉด โ ์์ธํ๋ฉด ํ๋ฆ
// 1. ๋ก๊ทธ์ธ ํ๋ฉด์์ ๋ฒํผ์ ๋๋ ์ ๋
@IBAction func loginButtonTapped(_ sender: UIButton) {
// โ ๋์ ๋ฐฉ๋ฒ: ์ง์ ํ๋ฉด ์ด๋
// let mainVC = MainViewController()
// navigationController?.pushViewController(mainVC, animated: true)
// โ
์ข์ ๋ฐฉ๋ฒ: ๊ต๋ฌด๋ถ์ฅ์๊ฒ ๋ถํ
coordinator?.showMainScreen()
}
// 2. ๋ฉ์ธ ํ๋ฉด์์ ์
์ ํฐ์นํ์ ๋
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedItem = items[indexPath.row]
// โ
๊ต๋ฌด๋ถ์ฅ์๊ฒ "์ด ์์ดํ
์ ์์ธํ๋ฉด ๋ณด์ฌ์ค!" ๋ผ๊ณ ๋ถํ
coordinator?.showDetailScreen(for: selectedItem)
}
๐ฑ ๋ฐ์ดํฐ ์ฃผ๊ณ ๋ฐ๊ธฐ
๋ถ๋ชจ์์ ์์์ผ๋ก (์์ ํ๋ฉด โ ํ์ ํ๋ฉด)
// ์์ธํ๋ฉด ๊ต๋ฌด๋ถ์ฅ - ์ด๋ค ์์ดํ
์ ๋ณด์ฌ์ค์ง ๋ฏธ๋ฆฌ ์๊ณ ์์
class DetailCoordinator: Coordinator {
private let itemInfo: Item // ๋ณด์ฌ์ค ์์ดํ
์ ๋ณด
init(navigationController: UINavigationController, item: Item) {
self.navigationController = navigationController
self.itemInfo = item // "์ด ์์ดํ
์ ๋ณด๋ฅผ ๊ฐ์ง๊ณ ์์๊ฒ"
}
func start() {
let detailScreen = DetailViewController.instantiate()
detailScreen.item = itemInfo // "์ฌ๊ธฐ ์์ดํ
์ ๋ณด์ผ!"
navigationController.pushViewController(detailScreen, animated: true)
}
}
์์์์ ๋ถ๋ชจ๋ก (ํ์ ํ๋ฉด โ ์์ ํ๋ฉด)
// ๋๋ฆฌ์ ํจํด ์ฌ์ฉ - "์ผ์ด ๋๋๋ฉด ์๋ ค์ค๊ฒ!"
protocol DetailCoordinatorDelegate: AnyObject {
func detailDidFinish(with result: String)
}
class DetailCoordinator: Coordinator {
weak var delegate: DetailCoordinatorDelegate?
func finishWithResult(_ result: String) {
delegate?.detailDidFinish(with: result) // "๋ถ๋ชจ๋, ์ผ ๋๋ฌ์ด์!"
finish() // ์ ๋ฆฌํ๊ณ ๋ง๋ฌด๋ฆฌ
}
}
โ ๏ธ ์ฃผ์ํ ์ ๋ค
1. ๋ฉ๋ชจ๋ฆฌ ๊ด๋ฆฌ (์ฐ๋ ๊ธฐ ์น์ฐ๊ธฐ)
// ๋ถํ ๊ต๋ฌด๋ถ์ฅ์ด ์ผ์ ๋๋ด๋ฉด ๋ชฉ๋ก์์ ์ ๊ฑฐํด์ฃผ๊ธฐ
func removeChildCoordinator(child: Coordinator) {
childCoordinators.removeAll { $0 === child }
}
2. ๋๋ฌด ๋ณต์กํ๊ฒ ๋ง๋ค์ง ๋ง๊ธฐ
- ๊ฐ๋จํ ์ฑ์์๋ Coordinator๊ฐ ์คํ๋ ค ๋ณต์กํ ์ ์์ด์
- ํ๋ฉด์ด 3-4๊ฐ๋ฟ์ด๋ผ๋ฉด ๊ตณ์ด ์ฌ์ฉํ์ง ์์๋ ๋ฉ๋๋ค
3. ์ ์ ํ ๋ ์ฌ์ฉํ๊ธฐ
- ํ๋ฉด์ด ๋ง๊ณ ๋ณต์กํ ์ด๋์ด ์์ ๋ ์ ์ฉํด์
- ์ฌ๋ฌ ์ฌ๋์ด ํจ๊ป ๊ฐ๋ฐํ ๋ ํนํ ์ข์ต๋๋ค
๐ ์ ๋ฆฌ
Coordinator Pattern์ ๋ง์น ํ๊ต์ ๊ต๋ฌด๋ถ์ฅ์ ์๋์ฒ๋ผ ํ๋ฉด ๊ฐ์ ์ด๋์ ์ ๋ดํ๋ ๋งค๋์ ๋ฅผ ๋๋ ๋ฐฉ์์ ๋๋ค.
ํต์ฌ ์์ด๋์ด:
- ๊ฐ ํ๋ฉด์ ์๊ธฐ ์ผ๋ง ์ง์ค (UI ๋ณด์ฌ์ฃผ๊ธฐ)
- ํ๋ฉด ์ด๋์ Coordinator๊ฐ ์ ๋ด
- ์ฝ๋๊ฐ ๊น๋ํด์ง๊ณ ๊ด๋ฆฌํ๊ธฐ ์ฌ์์ง
์ธ์ ์ฌ์ฉํ๋ฉด ์ข์๊น์?
- ํ๋ฉด์ด 5๊ฐ ์ด์์ธ ์ฑ
- ๋ณต์กํ ํ๋ฉด ์ด๋์ด ์๋ ์ฑ
- ์ฌ๋ฌ ๋ช ์ด ํจ๊ป ๊ฐ๋ฐํ๋ ์ฑ
- ํ ์คํธ๊ฐ ์ค์ํ ์ฑ
์ฒ์์๋ ๋ณต์กํด ๋ณด์ผ ์ ์์ง๋ง, ํฐ ์ฑ์ ๋ง๋ค ๋๋ ์ ๋ง ์ ์ฉํ ํจํด์ด์์! ๐โจ
์ ํํ์๋ค์! ๊ฒ์ ๊ฒฐ๊ณผ๋ฅผ ๋ณด๋ฉด:
Soroush Khanlou๋ ์ธํฐ๋ทฐ์์ ์ถ์ฒ ๋์๋ก “Domain Driven Design”, “Practical Object-Oriented Design in Ruby”, “Patterns of Enterprise Application Architecture”๋ฅผ ์ธ๊ธํ์ต๋๋ค PodtailInside iOS Dev. ํนํ **”Patterns of Enterprise Application Architecture”**๋ Martin Fowler์ ์ฑ ์ผ๋ก, ๊ทธ๋ ์์ ์ ๋ธ๋ก๊ทธ์์ “You can find this pattern in the literature, in books like Patterns of Enterprise Application Architecture. They call it the Application Controller”๋ผ๊ณ ๋ช ์์ ์ผ๋ก ์ธ๊ธํ์ต๋๋ค Khanlou | Coordinators Redux.
์ฐฝ์์์ ๋ฐฐ๊ฒฝ์ ๋ณด๋ฉด:
- ๋ค์ํ ์ธ์ด ๊ฒฝํ: Swift, Objective-C๋ฟ๋ง ์๋๋ผ Ruby, Haskell ๋ฑ ๋ค๋ฅธ ์ธ์ด๋ค๋ ํ์ตํ๋ค๊ณ ๋ฐํ์ต๋๋ค KhanlouKhanlou
- ์ํฐํ๋ผ์ด์ฆ ํจํด์ ๋ํ ์ดํด: Martin Fowler์ “Patterns of Enterprise Application Architecture”์์ Application Controller ํจํด์ iOS์ฉ์ผ๋ก ๋ณํํ ๊ฒ์์ ์ธ์
- SOLID ์์น ์ ์ฉ: ๊ทธ์ ๊ธ์์ “View controllers now can focus on their goal of binding a model to a view”๋ผ๊ณ ํ๋ฏ์ด ๋จ์ผ ์ฑ ์ ์์น์ ๋ช ํํ ์์ํ๊ณ ์์์ต๋๋ค Khanlou | The Coordinator
๊ฒฐ๋ก ์ ์ผ๋ก ๋น์ ์ ๋ถ์์ด 100% ๋ง์ต๋๋ค:
- โ ๋จ์ผ ์ฑ ์ ์์น ์ ์ฉ์ด ํต์ฌ ๋ชฉ์
- โ ํ๋ฉด ์ ํ ์ฝ๋๋ฅผ ViewController์์ ๋ถ๋ฆฌ
- โ ์ฐฝ์์๊ฐ ๋์์ธ ํจํด์ ์กฐ์๊ฐ ๊น์ (ํนํ ์ํฐํ๋ผ์ด์ฆ ํจํด)
- โ Java/.NET ๊ธฐ๋ฐ ์ํฐํ๋ผ์ด์ฆ ํจํด์ ์ํฅ์ ๋ฐ์ (Martin Fowler์ Application Controller ํจํด)
iOS ๊ฐ๋ฐ์๋ค์ด ์ฃผ๋ก MVC๋ง ์๊ณ ์๋ 2015๋ ์, ์ํฐํ๋ผ์ด์ฆ ๊ฐ๋ฐ ๊ฒฝํ์ด ์๋ ๊ฐ๋ฐ์๊ฐ SOLID ์์น์ iOS์ ์ ๋๋ก ์ ์ฉํ ์ผ์ด์ค๋ผ๊ณ ๋ณผ ์ ์๊ฒ ๋ค์! ๐ฏ
SOLID ์์น์ ๊ฐ์ฒด ์งํฅ ํ๋ก๊ทธ๋๋ฐ์์ ์ฝ๋์ ๊ฐ๋ ์ฑ, ์ ์ฐ์ฑ, ์ ์ง ๋ณด์์ฑ์ ๋์ด๊ธฐ ์ํด ์ง์ผ์ผ ํ 5๊ฐ์ง ์ค๊ณ ์์น์ ์๋ฏธํฉ๋๋ค. ๊ฐ๊ฐ ๋จ์ผ ์ฑ ์ ์์น (SRP), ๊ฐ๋ฐฉ-ํ์ ์์น (OCP), ๋ฆฌ์ค์ฝํ ์นํ ์์น (LSP), ์ธํฐํ์ด์ค ๋ถ๋ฆฌ ์์น (ISP), ์์กด ์ญ์ ์์น (DIP)์ ๋ํ๋ ๋๋ค. ์ด๋ฌํ ์์น๋ค์ ์ค์ํ๋ฉด ์ฝ๋ ๋ณ๊ฒฝ์ ๋ฐ๋ฅธ ์ํ์ ์ค์ด๊ณ , ์๋ก์ด ๊ธฐ๋ฅ ์ถ๊ฐ ๋ฐ ํ์ฅ์ด ์ฉ์ดํด์ง๋๋ค.
SOLID ์์น ์์ธ ์ค๋ช :
- 1.ย ๋จ์ผ ์ฑ ์ ์์น (SRP – Single Responsibility Principle):ํ๋์ ํด๋์ค๋ ํ๋์ ์ฑ ์๋ง ๊ฐ์ ธ์ผ ํฉ๋๋ค.ย ์ฆ, ํด๋์ค๋ ๋ณ๊ฒฝ๋ ์ด์ ๊ฐ ๋จ ํ๋์ฌ์ผ ํฉ๋๋ค.ย
- 2.ย ๊ฐ๋ฐฉ-ํ์ ์์น (OCP – Open/Closed Principle):์ํํธ์จ์ด ๊ตฌ์ฑ ์์๋ ํ์ฅ์ ์ด๋ ค ์์ด์ผ ํ์ง๋ง, ๋ณ๊ฒฝ์๋ ๋ซํ ์์ด์ผ ํฉ๋๋ค.ย ์ฆ, ๊ธฐ์กด ์ฝ๋๋ฅผ ๋ณ๊ฒฝํ์ง ์๊ณ ์๋ก์ด ๊ธฐ๋ฅ์ ์ถ๊ฐํ ์ ์์ด์ผ ํฉ๋๋ค.ย
- 3.ย ๋ฆฌ์ค์ฝํ ์นํ ์์น (LSP – Liskov Substitution Principle):์์ ํด๋์ค๋ ๋ถ๋ชจ ํด๋์ค์ ์ญํ ์ ๋์ฒดํ ์ ์์ด์ผ ํฉ๋๋ค.ย ์ฆ, ๋ถ๋ชจ ํด๋์ค ๋์ ์์ ํด๋์ค๋ฅผ ์ฌ์ฉํด๋ ํ๋ก๊ทธ๋จ์ด ์ ์์ ์ผ๋ก ๋์ํด์ผ ํฉ๋๋ค.ย
- 4.ย ์ธํฐํ์ด์ค ๋ถ๋ฆฌ ์์น (ISP – Interface Segregation Principle):ํด๋ผ์ด์ธํธ๋ ์์ ์ด ์ฌ์ฉํ์ง ์๋ ์ธํฐํ์ด์ค์ ์์กดํ์ง ์์์ผ ํฉ๋๋ค.ย ์ฆ, ํด๋ผ์ด์ธํธ๋ ์์ ์ด ํ์ํ ์ธํฐํ์ด์ค๋ง ์ฌ์ฉํด์ผ ํฉ๋๋ค.ย
- 5.ย ์์กด ์ญ์ ์์น (DIP – Dependency Inversion Principle):๊ณ ์์ค ๋ชจ๋์ ์ ์์ค ๋ชจ๋์ ์์กดํด์๋ ์ ๋ฉ๋๋ค.ย ์ด ๋ ๋ชจ๋ ๋ชจ๋ ์ถ์ํ์ ์์กดํด์ผ ํฉ๋๋ค.ย ์ฆ, ๊ตฌ์ฒด์ ์ธ ํด๋์ค๋ณด๋ค๋ ์ธํฐํ์ด์ค๋ ์ถ์ ํด๋์ค์ ์์กดํด์ผ ํฉ๋๋ค.ย
SOLID ์์น์ ๋ฐ๋ฅด๋ฉด ์ฝ๋์ ํ์ง์ ๋์ด๊ณ , ์ ์ง ๋ณด์ ๋ฐ ํ์ฅ์ด ์ฉ์ดํ ์ ์ฐํ ์์คํ ์ ๊ตฌ์ถํ ์ ์์ต๋๋ค.
๋ต๊ธ ๋จ๊ธฐ๊ธฐ