강의나 수업, 타 블로그의 글을 읽다보면 MVC 패턴, MVVM 패턴과 같은 단어를 종종 읽을 수 있습니다. 하지만 내가 연습해온 프로젝트들은 항상 혼자서 진행되었고, 특정 패턴을 적용하는 것이 더 복잡하다고 느껴졌기 때문에 한 파일, 혹은 두 파일에 모든 기능을 때려박는 식으로 작성을 해왔습니다. 하지만 이번 프로젝트는 조금 더 복잡할지라도 MVC 패턴을 적용해보도록 하였습니다.
MVC 패턴이란 말그대로 Model, View, Controller로 프로젝트를 구성하는 소프트웨어 디자인 패턴의 하나입니다. UI를 반영하는 View 부분에서 사용자의 이벤트들을 Controller에 보내주고, Controller에서는 이 입력들을 처리하여 View를 수정하거나, Model에 요청을 보내고, Data와 Logic을 다루는 Model 부분에서는 Controller에서 받은 요청에 대해서 처리한 데이터를 Controller에 다시 보내주는 방식으로 패턴을 적용합니다. 핵심적인 부분은 "Model과 View는 직접 관여하지 않고, Controller를 통해 관여한다" 입니다. 복잡해보이는 이러한 패턴을 적용하는 이유는 프로젝트가 커지게 되어 한번에 프로젝트를 전부 파악하기 어렵거나, 여러 사람과의 협업을 진행할 때, MVC 패턴으로 진행하겠다! 하는 약속을 통해서 각각의 작업을 찾아 확인하기 더 수월하게 하기 위해서입니다. 각 M V C가 1대1 대응이 아니기 때문에, 프로젝트 크기가 커질수록 상호간의 의존성이 커진다는 단점도 있습니다.
프로젝트의 사이즈가 작기 때문에, MVC가 비교적 명확하게 정의되었습니다. 스토리보드를 통해 사용자에게 직접 보이는 부분은 View, 해당 View를 조작하는 ViewController가 Controller, 앞선 기록에서 언급되었던 질문들(국물이 땡기시나요? ...)과, 각 대답에 따른 식당들(강강술래, 쉐이크 쉑, 알떡 ...), 그리고 질문의 대답에 따라 다음 질문 혹은 식당을 결정하도록 하는 logic이 포함된 Model로 구성되어있습니다.
질문과 식당, 처리 로직이 담긴 Model 폴더에는 struct Question
을 선언한 파일 Question.swift와 let restaurant
에 식당들을 String 형태의 배열로 선언해준 Restaurant.swift 파일과 이 둘을 활용하여 다음 질문, 혹은 식당으로 넘어가도록 처리해주는 QuestionBrain.swift 파일로 이루어져있습니다.
// Question.swift
struct Question {
let keys: Int
let text: String
let yes: Int
let no: Int
init(_ keys: Int, _ text: String, _ yes: Int, _ no: Int) {
self.keys = keys // 질문 번호
self.text = text // 질문 내용
self.yes = yes // "네!" 인 경우 질문/식당 번호
self.no = no // "아니오" 인 경우 질문/식당 번호
}
}
// Restaurant.swift
let restaurant = [
"강강술래",
"가리탕집",
"집밥",
"생고기짜글이",
"쉑쉑",
"서오릉피자",
"알떡",
"돈까스"
]
// QuestionBrain.swift
struct QuestionBrain {
let question = [
Question(0, "뜨끄은 한 궁물이 땡기시나요?", 1, 2),
Question(1, "갈비탕이 땡기시나요?", 3, 4),
Question(2, "양식이 땡기시나요?", 5, 6),
Question(3, "혹시 육회비빔밥도 땡기시나용..?", 7, 8),
Question(4, "오늘은 가까운 곳에서 식사하고 싶으신가용?",9, 10),
Question(5, "혹시,,, 쉐잍크가 땡기시나용???", 11, 12),
Question(6, "그렇다면 한식이 땡기시나용???", 13, 14)
]
...
}
위와 같은 구성으로 알 수 있듯, question
의 index는 6까지 이므로, 사용자의 input에 따른 결과값이 7 이상이 되는 경우, restaurant에서 결과값 - 7 에 해당하는 index의 내용이 식당으로 출력되도록 설계했습니다.
// QuestionBrain.swift
// 결과가 6 이하인 경우
func getText() -> String {
return question[questionKey].text
}
// 결과가 7이상인 경우
func getRestaurant() -> String {
return "오늘의 저녁 메뉴는 ... \(restaurant[questionKey - 7]) 입니다! 다시 하시겠습니까?"
}
이외에도 QuestionBrain.swift
에는 사용자의 클릭에 따른 결과가 6 이하인지를 확인하는 isInProcess()
와 결과를 questionKey
라는 변수에 저장하도록 하는 setQuestionKey()
가 선언되어있습니다.
사용자에게 직접 보이는 부분인 View는 단순한 형태의 Single-View-Application의 형태로, 한 화면에 Label 하나, Button 2개를 통해 구성했습니다. Label의 경우 Constraints를 Top과 Leading에만 주고, X축 center 정렬에 지정해주었고, Button 2개의 경우 하나의 StackView에 embed한 후 해당 StackView를 Bottom과 Leading, Trailing에 Constarints를 지정하고, fill equally와 각 버튼의 height를 지정해주어 Auto Layout을 구현해 보았습니다.
Model과 View를 이어주는 Controller에서는 가장 먼저 View
의 요소 Label과 두개의 Button, Model
의 요소 QuestionBrain()을 선언해주었습니다.
@IBOutlet weak var questionLabel: UILabel!
@IBOutlet weak var yesButton: UIButton!
@IBOutlet weak var noButton: UIButton!
var brain = QuestionBrain()
또한 View
의 button이 touch up inside된 경우, Model
의 questionKey를 최신화 하도록 했습니다.
@IBAction func buttonPressed(_ sender: UIButton) {
let answer = sender.currentTitle!
brain.setQuestionKey(answer)
updateUI()
}
View
가 로드 된 직후와, 각 버튼이 클릭될 때마다 실행되는 updateUI() 함수는 View
를 최신화 시켜주는 함수로, 만약 모든 질문이 끝나고, 결과로서 식당이 Label에 출력되면 "아니오" 버튼을 비활성화 하고 색깔을 바꾸도록 했고, 만약 다음 질문이 아직 남아있다면 Label의 내용을 Model
의 brain에서 얻어와 View
에 보이도록 했습니다.
func updateUI() {
noButton.isEnabled = true
noButton.backgroundColor = UIColor.orange
if brain.isInProcess() {
questionLabel.text = brain.getText()
} else {
questionLabel.text = brain.getRestaurant()
noButton.backgroundColor = UIColor.darkGray
noButton.isEnabled = false
}
}
View
를 단순화하고자 하나의 View에 결과까지 모두 보여주도록 하였으나, 결과도 질문의 Label에 띄워주는 것(1)과 결과 화면에서 다시 하시겠습니까?에 아니요가 활성화 되지 않는 것(2)이 아쉽다고 느꼈습니다. 두가지 아쉬운 점 중, 첫번째의 경우는 결과 화면을 Modal로 보여주는 것을 통해 개선 할 수 있을 것으로 예상되고, 두번째의 경우는 다시하지 않기를 선택한 경우 보여줄 수 있는 추가적인 컨텐츠에 대해 고민해보아야 할 것 같습니다. 다음 기록에서는 Modal을 활용해서 두개의 Controller와 View를 다루는 연습을 해보겠습니다.
MVC 패턴을 사용하셨네요!! :) 많이 배워갑니다ㅎㅎ