cleanUrl: /programming/do-not-blame-mvc-2/
아주 잠시만 시간을 내서 아래 앱을 만들어보시겠어요? 아주 간단한 앱입니다. “Cat”세그먼트를 클릭하면 고양이 이모지 셀들이 보이고, “Smiley”세그먼트를 클릭하면 스마일리 이모지 셀들이 보이는 앱입니다. 바쁘시면 그냥 눈을 감고 상상코딩을 해보세요.
다 만드셨나요? 그렇다면 아래 코드를 봐주세요. 혹시 이런 식으로 만들지는 않으셨는지요?
class ModelController: NSObject {
var kittisList = ["🐱", "😹", "😼", "😸", "😽", "😾"]
var smileyList = ["😐", "😂", "😏", "😊", "😊", "😠", "😱"]
}
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
// MARK: - View
@IBOutlet var tableView: UITableView!
@IBOutlet var segmentControl: UISegmentedControl!
// MARK: - Model
let modelController = ModelController()
// MARK: - LifeCycle
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
}
override func viewWillAppear(_ animated: Bool) {
tableView.reloadData()
}
// MARK: - Actions
@IBAction func segementSelected(_ sender: UISegmentedControl) {
tableView.reloadData()
}
// MARK: - UITableViewDataSource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if segmentControl.selectedSegmentIndex == 0 {
return modelController.kittisList.count
}
else {
return modelController.smileyList.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if segmentControl.selectedSegmentIndex == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "catCell", for: indexPath)
cell.textLabel?.text = modelController.kittisList[indexPath.row]
return cell
}
else {
let cell = tableView.dequeueReusableCell(withIdentifier: "smileyCell", for: indexPath)
cell.textLabel?.text = modelController.smileyList[indexPath.row]
return cell
}
}
}
여러분이 만든, 혹은 만들려던 형태와 비슷한가요? 그렇다면 이 글이 도움이 될 지도 모르겠습니다.
위 코드를 보면, 별 것 아닌 앱인데도, 벌써 ViewController가 50줄을 넘으려 하네요. 로직 몇 개만 더하면 100줄을 넘는 건 일도 아닐 듯 합니다.
그게 당연하다고 생각하시나요? 이 정도는 짧은 거라고 생각하시나요? 아닙니다. 절대 그렇지 않습니다. 이게 짧다고 느끼는 건, 마치 오래 사용해서 누렇게 된 체육복을 입으면서 “체육복은 원래 노랗지”라고 생각하게 되는 것과 비슷한 상황입니다. 체육복은 빨면 하얗게 될 수 있습니다.
위 코드를 보면 절반 가량을 UITableViewDataSource관련 함수들이 잡아먹고 있습니다. 우리는 종종 이는 불가피한 일이라고 생각하기도 합니다. 심지어 UITableView를 만들자마자 무의식적으로 tableView.dataSource = self
와 같은 코드를 쓰기도 하죠.
이런 습관이 생긴 데에는 애플의 책임도 적지 않습니다. 많은 예제코드와 템플릿 코드에서 애플이 직접 그런 코드를 쓰니까요. 예를들어 Xcode에서 새로운 UITableViewController를 만들면, 즉시 그 자신을 UITableViewDataSource로, 그리고 UITableViewDelegate로 설정해놓은 코드가 만들어집니다.
하지만 정작 UITableView에 관한 애플의 문서에서는 다음과 같이 말하고 있습니다.
Table views are a collaboration between many different objects, including:
제가 영어를 엄청 잘 하진 않습니다만, 제게는 UITableViewDataSource가 반드시 UIViewController일 필요는 없다는 내용을 행간에 담고 있는 듯 보입니다.
UITableViewDataSource를 별도의 객체로 만들어 위 코드를 리팩토링 해봅시다.
class ViewController: UIViewController {
// MARK: - View
@IBOutlet var tableView: UITableView!
@IBOutlet var segmentControl: UISegmentedControl!
// MARK: - DatSources
var dataSources:[UITableViewDataSource] = []
let catDataSource = CatDataSource()
let smileyDataSOurce = SmileyDataSource()
// MARK: - Model
let modelController = ModelController()
// MARK: - LifeCycle
override func viewDidLoad() {
super.viewDidLoad()
catDataSource.dataList = modelController.kittisList
smileyDataSOurce.dataList = modelController.smileyList
dataSources = [catDataSource, smileyDataSOurce]
tableView.dataSource = dataSources[0]
tableView.tableFooterView = UIView(frame: CGRect.zero)
}
override func viewWillAppear(_ animated: Bool) {
tableView.reloadData()
}
// MARK: - Actions
@IBAction func segementSelected(_ sender: UISegmentedControl) {
tableView.dataSource = dataSources[sender.selectedSegmentIndex]
tableView.reloadData()
}
}