[SwiftUI] MVVM 패턴에 대해

Laav·2022년 3월 23일
6

SwiftUI

목록 보기
3/9
post-thumbnail

작성하기에 앞서, 이 포스팅은 Udemy "MVVM Design Pattern in iOS Using SwiftUI" 강의를 바탕으로 작성된 것이며 모든 내용의 저작권은 강의의 원작자인 Mohammad Adam 에게 있음을 밝힙니다.

SwiftUI를 통해 iOS 앱 개발 프로젝트를 진행하던 중, 코드량이 늘어나고 서버와의 통신을 진행함에 따라 Design Pattern 적용의 필요성을 느끼게 되었다.

그래서 구글과 유튜브의 포스팅과 강의 등을 찾아보며 SwiftUI에서 가장 많이 쓰이는 MVVM 패턴을 공부하고 적용해보려 했는데, 생각보다 이해가 잘 되지 않았다.

따라서 Udemy에서 강의를 들으며 공부하게 되었고, SwiftUI에서의 MVVM 패턴에 대해서 알게 된 개념들을 정리해보려고 한다.

Swift의 문법 등에 대한 기본적인 지식이 있다는 가정 하에 작성되었다.


📌 디자인 패턴이란?

Design Pattern, 즉 디자인 패턴이란 패턴 각각의 성격에 따라 class와 object 사이의 관계를 정립해놓은 것이라고 볼 수 있다.

패턴 각각마다 특정한 상황에 알맞는, 보통 가장 적절한 솔루션을 제공하며, 이런 디자인 패턴을 사용함으로써 다음과 같은 이득을 볼 수 있다.

  1. 개발 속도 향상
  2. 프로그래밍 언어에 구애받지 않음
  3. 유연성, 재사용성, 유지보수성

디자인 패턴은 이미 존재하는 것이고, 개발자는 이러한 패턴을 프로그램에 적용하기만 하면 되기 때문에 개발 속도가 향상된다. 그리고 디자인 패턴마다 적용시킬 수 있는 특정 언어가 정해져 있지 않기 때문에, 언어에 구애받지 않고 사용할 수 있는 것이다.

📌 MVVM이란? 이걸 왜 써야 하는데?

오케이. 디자인 패턴이라는 것이 그런 이점을 가지고 있는건 알았다. 그렇다면 MVVM은 어떤 패턴일까?

M-V-VM 은 Model, View, View Model의 약자이다.

Model은 사용자 정보, 상품 정보 등 여러 정보를 담고 있다. 쉽게 말해서 앱의 메모리라고 생각하면 될 것 같다. 이러한 Model의 정보를 우리는 View를 통해 사용자에게 보여주게 된다. 그렇다면 그냥 보여주면 되지 않나? 라고 생각할 수 있다.

절대 안된다!

Model에는 단순 정보 뿐 아니라 많은 로직과 비즈니스 룰이 들어가 있으며, 그것을 우리는 사용자에게서 숨길 필요가 있다. 만약 우리가 은행 앱을 서비스한다고 가정해보면, 이것이 얼마나 큰 일을 초래하는지 상상할 수 있을 것이다.

그럼, 어떻게 해야할까?

이제, View Model이 해결사로 나오게 된다.

View Model은 View에서 띄우고 싶은 정보만 Model에서 가져오고, View에서 사용자가 업데이트한 정보가 있다면 그것을 Model에 전달한다. View와 Model 중간에서 매개체 역할을 해준다고 이해할 수 있다.

우리는 이러한 MVVM 패턴을 통해 (어떻게 보면 View Model을 통해) 사용자에게 보여주고 싶은 정보만 보여주고, 사용자가 업데이트한 정보 중 진짜 필요한 정보만 Model에 넘길 수 있게 된다.

그럼, MVVM 패턴을 실제로 적용한 사례를 보도록 하자.

💻 MVVM 패턴의 적용 - Premium Counter

'Increment' 를 클릭하면 숫자가 올라가고, 올라간 숫자가 3의 배수가 되면 화면에 'Premium' 을 출력하는 앱을 MVVM 패턴에 기반하여 작성한 코드를 살펴보도록 하자.

먼저, Model의 코드는 다음과 같다.

//  Created by Mohammad Azam
//  Copyright © 2020 Mohammad Azam. All rights reserved.

import Foundation

struct Counter {
    
    var value: Int = 0
    var isPremium: Bool = false
    
    mutating func increment() {
        value += 1
        
        // business logic 
        if value.isMultiple(of: 3) {
            // premium
            isPremium = true
        } else {
            // not premium
            isPremium = false
        }
        
    }
}

코드를 보면 "Counter" 라는 Model이 구조체로 정의되어 있는 것을 볼 수 있고, 구조체 안에 숫자를 증가시켜주고 증가한 숫자에 따라 Premium을 결정하는 로직이 포함되어 있는 것을 확인할 수 있다.


View Model의 코드는 다음과 같다.

//  Created by Mohammad Azam
//  Copyright © 2020 Mohammad Azam. All rights reserved.

import Foundation
import SwiftUI

class CounterViewModel: ObservableObject {
    
    @Published private var counter: Counter = Counter()
    
    var value: Int {
        counter.value
    }
    
    var premium: Bool {
        counter.isPremium
    }
    
    func increment() {
        counter.increment()
    }
}

"CounterViewModel" 이라는 이름의 View Model은 ObservableObject로 선언되었고, 그 안에서 Counter Model을 가져와서 선언해주었으며 이는 @Published 로 선언된 것을 볼 수 있다.

ObservableObject란 Model에 생긴 변화를 자동으로 View에 업데이트하라고 알려주는 프로토콜이라고 생각하면 되는데, 우리는 이는 struct가 아닌 class에서 사용할 수 있다. 따라서 CounterViewModel을 class로 선언해준 것을 볼 수 있고, 어떠한 event로 변수의 값이 바뀐다면 해당 변수를 @Published로 선언하여 자동으로 바뀐 값을 View에서 렌더링해줄 수 있다.

Counter Model 구조체를 @Published를 붙인 "counter" 객체로 가져와서, 객체 내에서 변수의 값을 함수를 통해 바꿔주고 View에 바뀐 것을 알려주고 있다.

그렇다면 View에서는 어떻게 알리는 것을 받으면 될까?

View의 코드는 다음과 같다.

//  Created by Mohammad Azam
//  Copyright © 2020 Mohammad Azam. All rights reserved.

import SwiftUI

struct ContentView: View {
    
    @ObservedObject private var counterVM: CounterViewModel
    
    init() {
        counterVM = CounterViewModel()
    }
    
    var body: some View {
        VStack {
            
            Text(counterVM.premium ? "PREMIUM" : "")
                .foregroundColor(Color.green)
                .frame(width: 200, height: 100)
                .font(.largeTitle)
            
            Text("\(counterVM.value)")
                .font(.title)
            Button("Increment") {
                self.counterVM.increment()
            }
        }
    }
}

가장 눈여겨볼 것은 역시, @ObservedObject 이다.

@ObservedObject는, ObservableObject에서 Publish한 값을 받는 친구이다. 따라서 우리는 CounterViewModel의 객체를 @OvservedObject로 선언함에 따라 View Model이 변화시킨 Model의 값을 받아와 렌더링해줄 수 있는 것이다.

'counterVM' 라는 이름의 View Model 객체를 통해 Model과 소통한다고 보면 이해가 쉬울 것이다.

View 안에서 "counterVM.increment()" 함수를 통해 Model의 value 값을 증가시키고, "counterVM.value" 를 통해 그 값을 가져와 다시 화면을 렌더링하는 방식이다. 그리고 "counterVM.premium" 을 통해 로직을 확인하고 로직에 따라 화면에 Premium 이라는 text를 띄울지 결정하는 것을 볼 수 있다.


📌 요약 & 마무리

필자는 MVVM 패턴에 대해 이해할 때 View Model의 역할을 제대로 이해하는 것이 가장 중요하다고 생각한다. 정보를 담고 있는 Model과 사용자에게 정보를 제공하는 View 사이에서 어떠한 역할을 담당하는지 생각해본다면, 조금 이해가 수월할 것이다.

처음에는 하나도 감이 안잡혔지만, 사실 막상 해보니 생각보다 어렵지 않았고 실제로도 범용적으로 많이 쓰이는 디자인 패턴이니 여러 번 더 연습해서 완전한 내 것으로 만들어야겠다.

다음 포스팅에서는, MVVM 패턴을 기반으로 한 Backend 서버와의 HTTP 통신에 대해 다룰 것이다.

printf("Thank You!\n");
printf("Posted by Thirsty Developer\n");
profile
iOS 왕초보

2개의 댓글

comment-user-thumbnail
2022년 4월 29일

간결하면서도 이해가 쉬운 글이었습니다. ^^

1개의 답글