디자인패턴이 뭘까❓
공통의 문제에 검증된, 정형화된 해결책
건축으로 비유하자면 여러 건축공법이 있다. 한 개의 건물을 짓기 위해 다양한 건축공법을 활용해 다양한 방법으로 지을 수 있다.
그렇듯 코딩에서도 코딩공법?이 있다.
앱의 아키텍처도 여러가지가 있다
예) Singletom패턴, Delegate패턴, MVC, MVVM, MVP...
디자인 패턴이 필요한 이유가 뭘까❓
결국 앱 하나를 만들기 위해 여러명의 개발자가 하나의 앱을 함께 작업한다.
여러명이 함께 작업할 때 효율적인 유지보수성, 지속적인 기능 개발, 추가와 이를 위한 효율성을 위한 템플릿화가 필요하기 때문이다.
💥 MVC(Model-View-Controller) 디자인패턴
MVC는 Model, View, Controller의 약자이다.
하나의 애플리케이션, 프로젝트를 구성할 때 그 구성요소를 세가지의 역할로 구분한 패턴이다.
위의 그림처럼 사용자가 Controller를 조작하면 Controller는 Model을 통해 데이터를 가져오고 데이터를 바탕으로 View를 통해 시각적 표현을 제어하여 사용자에게 전달하게된다.
Model
- 비즈니스 로직(화면과 전혀 관련이 없는 로직, 데이터)
- 앱의 데이터를 정의
- 데이터를 사용해 결과값을 도출하는 ➡️ 몸무게와 키로 BMI를 계산하는 로직, 내가 선택한 숫자보다 타겟이 업인지, 다운인지 판별하는것
- 구조체나 클래스로 구현
- 비즈니스 로직, 네트워킹 등을 중심으로 테스트
예시
- Network Code: 네트워크 통신은 단일 클래스에서 사용하는 것이 좋다. HTTP헤더, 응답 및 오류처리 등 모든 네트워크 코드를 추상화하여 구현한다.
- Persistence Code: 데이터베이스, 코어 데이터, 디바이스 데이터를 저장할 때 사용한다.
- Parsing Code: 네트워크 response를 parsing하는 JSON Codable모델을 정의할 때 사용한다.
- Constants: 상수를 모델로 정의하면 유용하다. Storyboard 이름, 날짜formatter, 색상 등을 여러 곳에서 재사용할 수 있다.
- Helpers와 extensions: 프로젝트에서 String등의 기능 추가도 모델을 사용한다.
View
- 사용자 화면 표시(뷰컨트롤러의 명령을 받아 화면 표시. View는 사용자가 보는 화면을 그리며 사용자와 앱의 상호작용을 담당한다.
- Storyboard와 가깝다.
- 모델과 소통해도 안되고 어떠한 비즈니스 로직도 포함되면 안된다.
예시
- UILable, UIButton, UIImage등
Controller
- View와 Model을 중개하는 역할
- View로부터 사용자의 Action을 받아 Model에게 어떤 작업을 해야할지 알려주거나,
- Model의 데이터 변화를 View에게 전달하여 View를 업데이트해야한다고 알려준다.
- 중재자 역할만한다.
Model은 앱이 가지는 비즈니스로직을 갖고있다.
Ciew는 앱에서 유저에게 보이는 역할을 한다. 그러므로 비즈니스 로직을 포함하지 않고 재사용될 수 있다.
Controller는 View와 Model을 잇는 역할을 한다.
❓애플의 MVC패턴
애플의 MVC패턴에 대해 알아본다.
MVC패턴은 애플이 UIKit에서 채택한 디자인 패턴이라 iOS에서는 가장 일반적이다.
ViewController를 사용하기 때문에 View와 Controller를 결합시켜 ViewController와 Model로 구분된다.
Controller가 View의 LifeCycle까지 관리하면서 Controller의 역할이 늘어났다.
하지만 View와 Model의 연결은 더욱 간편해졌다.
원래는 Controller를 거쳐 View에게 전달해야 했는데 그냥 ViewController에게 전달하면 된다.
결론적으로 애플의 MVC는 View와 Controller를 결합한 것일 뿐, MVC의 근본은 동일하다.
MVC패턴의 장점
MVC의 가장 큰 장점은 생산성이 높고 쉽다는 것이다.
MVC는 각 구조의 역할이 명확하므로 역할 분담하여 빠르게 구현할 수 있다.
다른 패턴에 비해 코드량이 적으며 많은 개발자에게 친숙하기 때문에 쉽게 접근, 유지보수할 수 있다.
프로젝트 규모가 크지 않고, 패턴이 필요하지 않을 때 MVC를 사용하면 빠르고 쉽게 개발할 수 있다.
MVC패턴의 단점
MVC의 가장 큰 단점은 테스트가 힘들고 Controller의 크기가 크다는 것이다.
View와 Controller가 결합되어 Controller가 View의 역할도 할 수 있다.
따라서 분리하기 어렵고 재사용성이 떨어진다.
유닛 테스트를 진행하기 힘들어지고 내부 구조가 복잡해질 수 있다.
MVC를 지키는 5가지 방법
1. Model은 Controller와 View에 의존하면 안된다.
Model 내부에 View와 Controller와 관련된 코드가 없어야한다.
2. View는 Model에만 의존하고 Controller를 의존하면 안된다.
View 내부에 Model의 코드만 존재하고 Controller 코드는 존재하면 안된다.
3. View가 Model로부터 데이터를 받을 때는 사용자마다 다르게 보여줘야 하는 데이터만 받아야한다.
Title같은 공통 문구는 Model에서 받지 않아야하고 사용자이름, 닉네임처럼 사용자마다 다른 데이터만 Model로 부터 받아야한다.
4. Controller는 View와 Model에 의존해도 된다.
Controller내부에 Model과 View코드가 존재해도 된다.
5. View가 Model로부터 데이터를 받을 때는 Controller를 통해 받아야 한다.
Controller는 View와 Model을 중개하므로 View는 Controller로 부터 Model의 데이터를 받아야 한다.
실습
이 앱은 BMI를 계산하는 앱이이다.
Model은 앱의 핵심적인 비즈니스 로직을 갖고있다. BMI를 계산하는 로직을 분리시킨다.
Model/BMICalculatorManager.swift
BMI를 계산해내는 객체의 역할을 한다. 그러므로 BMI를 계산해내는 모든 로직은 여기에 위치한다.
그래서, BMICalculatorManager와 ViewController가 대화하도록 만들어주면 된다. 그 방법은 아래와 같다.
struct BMICalculatorManager {
private var bmi: BMI?
// BMI얻기 메서드
mutating func getBMI(height: String, weight: String) -> BMI{
// BMI 만들기 메서드 호출
calculateBMI(height: height, weight: weight)
//BMI리턴
return bmi ?? BMI(value: 0.0, advice: "문제발생", matchColor: UIColor.white)
}
// BMI 계산 메서드
mutating private func calculateBMI(height: String, weight: String){
guard let h = Double(height), let w = Double(weight) else {
bmi = BMI(value: 0.0, advice: "문제발생", matchColor: UIColor.white)
return
}
var bmiNum = w / (h * h) * 10000
bmiNum = round(bmiNum * 10) / 10
switch bmiNum{
case ..<18.6:
let color = UIColor(displayP3Red: 22/255,
green: 231/255,
blue: 207/255,
alpha: 1)
bmi = BMI(value: bmiNum, advice: "저체중", matchColor: color)
case 18.6..<23.0:
let color = UIColor(displayP3Red: 212/255,
green: 251/255,
blue: 121/255,
alpha: 1)
bmi = BMI(value: bmiNum, advice: "표준", matchColor: color)
case 23.0..<25.0:
let color = UIColor(displayP3Red: 218/255,
green: 127/255,
blue: 164/255,
alpha: 1)
bmi = BMI(value: bmiNum, advice: "저체중", matchColor: color)
case 25.0..<30.0:
let color = UIColor(displayP3Red: 255/255,
green: 150/255,
blue: 141/255,
alpha: 1)
bmi = BMI(value: bmiNum, advice: "저체중", matchColor: color)
case 30.0...:
let color = UIColor(displayP3Red: 255/255,
green: 100/255,
blue: 78/255,
alpha: 1)
bmi = BMI(value: bmiNum, advice: "저체중", matchColor: color)
default:
let color = UIColor.black
bmi = BMI(value: bmiNum, advice: "에러발생", matchColor: color)
}
}
}
컨트롤러에서는 BMIClaculatorManager를 생성해서 BMI계산을 이 객체에 위임해야한다.
이후 BMI와 관련된 모든 작업을 bmimanager가 담당한다.
보통의 모델데이터는 struct / 작업을 수행하는 mannager는 class로 작성한다.
만약 코드로 UI를 작성하였다면 뷰도 나눠야한다. 그렇다면 컨트롤러에서 뷰를 교체해줘야하는데 이는
viewDidLoad보다 먼저 호출되는 loadView에서 진행할 수 있다.
// Controller
override func loadView() {
view = loginView
}
또한 버튼들의 수행방식을 지정한 메서드들은 컨트롤러에 위치한다.