-
Model-View-Controller(MVC)Swift 2022. 4. 3. 17:55
안녕하세요.
오늘부터 아키텍처 패턴들에 대해서 하나씩 알아보도록 하겠습니다.
오늘은 그중에서 아마 가장 처음 접하게 될 Model-View-Controller, 즉 MVC에 대해서 살펴보겠습니다.
1. MVC
MVC는 앱을 Model, View, Controller 세개의 역할로 분리한 구조입니다.
애플의 주장으로는 MVC를 사용하면
- more reusable(재사용성이 좋다)
- interface be better defined(인터페이스 정의가 더 쉽다)
- more easily extensible(확장성도 좋다)
- many Cocoa technologies and architectures are based on MVC(심지어 Cocoa Framework도 MVC를 기준으로 한다)
이러한 장점이 있다고 합니다.
정말 어마어마한 아키텍처 패턴이네요.
그럼 그 요소를 하나씩 알아보도록 하겠습니다.
2. Model
MVC에서의 모델은 다른 아키텍처에서 볼 수 있는 Entity와는 사뭇 느낌이 다릅니다.
데이터뿐만 아니라, 비즈니스 로직, 네트워크, 영구 저장소 등 데이터와 관련된 코드들은 모두 Model에 포함됩니다.
Model을 작성할 때 주의할 점은 Model은 여러 화면에서 재사용이 가능한 구조여야 합니다.
그러기 위해서는 View나 Controller에 의존적이어서는 안 되겠죠.
그렇다면 Model은 Controller에게 어떻게 변화된 정보를 알릴 수 있을까요?
여러 가지 방법이 있겠지만 NotificationCenter를 이용하는 방법과 Key-Value-Observing과 같은 옵저버 패턴을 사용해 해결하는 것이 대표적입니다.
Model이 상태가 변화하면 Model은 상태가 변했다는 사실을 관찰자들에게 알리는 거죠.
Model은 직접적으로 그 관찰자가 누군지는 모르지만 관찰자들은 Model의 정보가 바뀐 것을 알 수 있습니다.
3. View
iOS에서 View는 데이터를 사용자들에게 보여주는 역할뿐만 아니라 이벤트를 전달받는 역할도 하고 있습니다.
즉 사용자와 상호작용하는 영역을 담당하는 층입니다.
UIView를 상속받는 객체들은 대부분 View 영역이라고 생각하면 될 것 같습니다.
View의 경우도 재사용성이 매우 강조됩니다.
따라서 Controller를 직접 알고 있어서는 안 됩니다.
이러한 문제는 Target-Action이나 delegate, datasource와 같은 위임자 패턴을 통해 해결합니다.
TableView나 CollectionView를 다뤄보신 분들이라면 익숙하실 텐데요.
View는 어떤 이벤트가 발생하면 위임자에게 넘기겠다는 사실만 알고 있습니다.
상세한 구현은 위임받은 객체에 구현을 하는 것이죠.
4. Controller
Controller는 뷰와 모델 사이의 중개자 역할을 합니다.
View로부터 이벤트를 전달받아 해당 이벤트를 자신이 직접 처리해 View에게 다시 알리거나 Model의 변화가 필요한 경우 Model에게 알립니다.
Model의 변화가 발생하면 변화를 감지해 View에게 다시 그리기를 요청합니다.
따라서 Controller는 View와 Model을 모두 알고 있습니다.
지금까지 MVC에 개념을 알아보았습니다.
간단한 앱을 통해 직접 살펴보겠습니다.
+를 누르면 값이 숫자가 증가하고 -를 누르면 값이 감소하는 앱입니다.
이 경우 Model은 저 Label이 표시하기 위한 값이 됩니다.
// Model class Amount: NSObject { @objc dynamic var number: Int override init() { self.number = 0 } func increase() { self.number += 1 } func decrease() { self.number -= 1 } }
View는 Label, + 버튼, - 버튼입니다.
class ViewController: UIViewController { // Controller private var amount: Amount? // View private lazy var increaseButton: UIButton = { let button = UIButton() button.setImage(UIImage(systemName: "plus"), for: .normal) // + 버튼 눌렀을 경우 호출될 메서드 지정 button.addTarget(self, action: #selector(increaseButtonTouched(_sender:)), for: .touchUpInside) return button }() private lazy var decreaseButton: UIButton = { let button = UIButton() button.setImage(UIImage(systemName: "minus"), for: .normal) // - 버튼 눌렀을 경우 호출될 메서드 지정 button.addTarget(self, action: #selector(decreaseButtonTouched(_sender:)), for: .touchUpInside) return button }() private lazy var amountLabel: UILabel = { let label = UILabel() label.font = label.font.withSize(32) label.text = String(0) return label }() private var observation: NSKeyValueObservation? override func viewDidLoad() { super.viewDidLoad() self.amount = Amount() //모델의 값 변화 관찰 self.observation = self.amount?.observe(\.number, options: .new) {object, change in self.amountLabel.text = String(change.newValue ?? 0) } } @objc func increaseButtonTouched(_sender: UIButton) { self.amount?.increase() } @objc func decreaseButtonTouched(_sender: UIButton) { self.amount?.decrease() }
Controller에서 Button들에 addTarget을 통해 호출될 메서드를 지정하고
모델의 값을 관찰합니다.
애플의 말대로 MVC는 훌륭한 아키텍처임에는 틀림이 없습니다.
역할을 분리함으로써 View와 Model의 재사용성이 강해지고 이는 결국 유지보수에 용이합니다.
또한 UIKit 관련 객체들이 MVC를 기반으로 작성되었기 때문에 아키텍처를 구성함에도 어렵지 않습니다.
따라서 빠른 개발이 필요한 경우 적합한 아키텍처 패턴이라 생각합니다.
하지만 단점도 역시 존재합니다.
첫 째, Controller의 역할이 너무나도 많다는 점입니다.
위에서도 살펴보았듯이 Controller는 View의 위임자 역할도 해야 하고, Model의 관찰자 역할도 해야 합니다.
이렇게 한 개의 객체가 너무 많은 역할을 하면 좋지 않다는 점은 Solid원칙의 단일 책임 원칙을 통해서 알 수 있습니다.
두 번째, Controller의 테스트 코드 작성이 어렵습니다.
Controller의 작성된 코드가 굉장히 많음에도 불구하고
UIViewController의 Life Cycle과 관련된 코드들은 직접 앱을 실행시키지 않는 한 테스트가 어렵습니다.
오늘은 MVC에 대해서 알아보았습니다.
다음 시간에는 또 다른 아키텍처들을 통해 과연 MVC의 문제점을 어떻게 해결했을지 알아보겠습니다.
'Swift' 카테고리의 다른 글
Model-View-ViewModel(MVVM) (0) 2022.04.08 Model-View-Presenter(MVP) (0) 2022.04.04 Property Wrapper (0) 2022.03.25 SOLID 원칙 (0) 2022.01.28 Codable (0) 2021.12.28