본문 바로가기

개발 코딩 정보 공유/개발후기

MVVM-C 패턴을 활용한 앱 개발 후기

 

안녕하세요. 블루스웨터 소프트 입니다.

클린 아키텍처, 클린 코드(clean code:Robert C. Martin) 등은 이제 주변에서 흔하게 접하는 단어가 되었습니다.

MVC, MVP, MVVM... 각종기법이 사용되고 있습니다. 장기적으로 유지보수측면서 환영할만한 일이지만

해당 기법들이 무분별하게 사용되어진 프로젝트들을 보면 (모든 기법이 뒤섞인) 유지보수측면에서 정말 지옥이 아닐수 없습니다. 

오늘은 MVVM-C 라고 하는 패턴을 사용한 작업 후기를 공유 합니다.

 

MVVM 패턴은 매우 인기있는 클린아키텍처 접근 방법 입니다. 이 패턴에 Coordinator 라는 개념을 추가 한것이 MVVM-C 패턴 입니다. 

 

"왠 코디네이터 입니까?"

 

코디네이터는 일종의 목적지 가이드 라고 이해하면 쉽게 접근할수 있습니다.

코디는 우리가 어디로 가야할지, 무엇이 필요한지를 아는자 입니다.

func goToLogin() { 
    let vc = LoginViewController.instantiate(from: authStoryboard) //목적지를 정하고
    let vm = LoginViewModel() //필요한 정보를
    vm.apiClient = authApi 
    vm.authCoordinator = self
     vc.viewModel = vm //전달합니다
    navigationController.setViewControllers([vc], animated: true ) //목적지로 이동합니다
}

하나의 코디네이터 클래스에는 위와 같은 여러개의 이동정보가 존재합니다.

coordinator의 설계

AppCoordinator 라는 공통의 부모 코디가 있고 그 밑에는 화면별로 코디가 존재하게 됩니다. 화면에 따른 Coordinator, ViewModel, ViewController, View... 등이 존재하는 것이죠.

 

protocol Coordinator {
    var parentCoordinator: Coordinator? { get set }
    var children: [Coordinator] { get set }
    var navigationController : UINavigationController { get set }
    
    func start()
}
class AppCoordinator: Coordinator {
var parentCoordinator: Coordinator?
var children: [Coordinator] = []
var navigationController: UINavigationController    

init(navCon : UINavigationController) {
	self.navigationController = navCon
}    

func start() {
    print("===앱의 시작점===")
}}

 

예를 들면 AppDelegate는 아래와 같이 만들어집니다. root navigation 을 만들고 appCoordi 에게 전달 합니다.

class AppDelegate: UIResponder, UIApplicationDelegate {
   var window: UIWindow?
   var appCoordinator : AppCoordinator?

   func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
       
        window = UIWindow(frame: UIScreen.main.bounds)
        let navigationCon = UINavigationController.init()
        appCoordinator = AppCoordinator(navigationController: navigationCon)
        appCoordinator!.start()
        window!.rootViewController = navigationCon
        window!.makeKeyAndVisible()
        return true
   }
}

 

Coordinator의 핵심은 전달받은 navigator로 화면 처리를 운용하는데 목적이 있습니다.

 func start() {
	goToLoginPage()
}

 

AppCoordinator에 소스를 추가 합니다. 화면은 띄워봐야 하니까요.

let storyboard = UIStoryboard.init(name: "Main", bundle: .main) //스토리보드사용시

func goToLoginPage(){
     let loginViewController = storyboard.instantiateViewController(withIdentifier: "LoginViewController") as! LoginViewController //화면을 구성합니다.
     let loginViewModel = LoginViewModel.init() //뷰모델을 생성하고
     loginViewModel.appCoordinator = self //뷰 모델이 코디를 알아야 호출하겠죠?
     loginViewController.viewModel = loginViewModel //viewController는 ViewModel을 알아야합니다.
     navigationController.pushViewController(loginViewController, animated: true) //마지막은 push하면 됩니다.
}

 

코드를 보면 아시겠지만 코디가 하는 일은 그저 화면을 만들고 그안에 필요한 내용을 구성해주는 역할이 다 입니다. 클린 아키텍처의 핵심은 규칙을 잘 지키고, 할일만 하고, 서로를 잘 몰라야 합니다. UI는 ViewModel을 모르고, ViewModel은 Repository, Service를 모릅니다. (모른다는건 생성하지 않는다는 종속되지 않는다는 의미) DI를 통해 전달받고 그저 사용할뿐입니다.

 

뷰모델도 살펴 보겠습니다.

class LoginViewModel {
    weak var coordinator : AppCoordinator!
    
    func goToLogin(){
        appCoordinator.goToLoginPage()
    }
}

 

정말 단순한 예제 입니다😅

뷰컨트롤러도 확인해보겠습니다.

class LoginViewController : UIViewController {
    var viewModel : LoginViewModel!
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    @IBAction func goToLoginTapped(_ sender: Any) {
        viewModel.goToLogin()
    }
}

 

뷰컨트롤러도 사실상 하는일이 없습니다. 비지니스 로직에는 전혀 관혀하지 않습니다. 유저 와 View 간의 상호작용하고 ViewModel 로 모든 로직을 위임합니다. ViewModel에서는 상황에 맞게 Service, Repository 를 운용하고 최종적으로 Coordinator를 통해 화면을 이동시켜줍니다. 

 

MVVM 의 개념을 알고있다면 Coordinator만 추가 해주면 됩니다. Coordinator를 추가함으로써 복잡한 화면 이동처리를 위임하고 비즈니스 로직에 집중할수 있습니다. 이것이 MVVM-C 활용의 전부 입니다. 🤩

 

 

 

 

참고문서

 

https://www.linkedin.com/pulse/understanding-mvvm-c-budhabhooshan-patil/

https://medium.com/nerd-for-tech/mvvm-coordinators-ios-architecture-tutorial-fb27eaa36470