오늘 MVC, MVVM 대해 간략한 공부를 했었다.
내가 알아 본 결과 간단히 표현하자면, MVC와 MVVM 중 어떤 패턴을 따를지 결정하는 기준은
간단한 프로젝트일 경우,
복잡한 앱 구조일 경우,
이렇게 나뉘는 것 같다.
이렇게 공부하고 났는데,
혹시라도 MVC
구조를 더 잘 이해하고 싶다면, UIKit의 UITableViewController
처럼
MVC
기반으로 한 시스템 컨포넌트를 분석해보는 것을 추천한다는 걸 보게되었고 분석 해보려 한다.
UITableViewController
는 아마 곧 사용해볼 것 같은데
iOS앱에서 데이터를 리스트 형식으로 보여줄 때 자주 사용된다고 한다.
UITableViewController
가 MVC패턴
을 잘 이해하는데 큰 도움이 된다고 한 이유는
기본적으로 Model-View-Controller
패턴을 따르기 때문인데, 이 패턴을 어떻게 적용하는지 예시를 통해 하나씩 확인해보자.
앱에서 표시할 데이터를 관리하는 부분이라고 보면 되는데,
보통 배열(Array)이나 딕셔너리(Dictionary) 같은 데이터 구조체를 사용하여 데이터를 모델링한다.
예로 들자면 영화 목록, 친구 목록, 상품 목록 등 다양한 데이터를 나타낼 수 있다는걸 알고 있으면 된다.
UITableView
는 데이터를 보여주는 View
역할을 한다. UITableViewCell
을 이용해 각 항목을 어떻게 보이게 할지 설정한다.
UITableViewController
는 Controller
역할을 한다. 데이터와 View
를 연결하고, 사용자 인터페이스와 데이터 변경을 처리한다. 데이터를 UITableView
에 표시하고, 셀 선택, 편집 등을 처리한다.
아래 코드는 UITableViewController
를 사용하여 간단한 영화 목록 앱을 만드는 예시 코드인데,
이 예시를 통해 MVC 구조를 좀 더 구체적으로 이해할 수 있었다.
먼저, 데이터를 정의한다. 영화 목록을 관리하는 Movie 클래스를 만든다.
import UIKit
// Model: 테이블에 표시할 데이터
struct Movie {
let title: String // 영화 이름
var year: Int // 영화 개봉 연도
}
UITableViewController
를 상속받은 MovieTableViewController
를 만들고,
이곳에서 데이터를 처리하고, UITableView
와 연결한다.
class MovieTableViewController: UITableViewController {
// Model 데이터: 영화 목록
var movies: [Movie] = [
Movie(title: "킹콩", year: 2010),
Movie(title: "해리포터 비밀의 방", year: 2008),
Movie(title: "스톰", year: 2014),
Movie(title: "마션", year: 2017)
]
override func viewDidLoad() {
super.viewDidLoad()
// 테이블 뷰의 레이아웃, 스타일 등을 설정
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "MovieCell")
}
movies
는 Movie
객체들을 담고있는 배열인데,
각 객체는 영화 제목과 개봉 연도 정보를 가지고 있다. 이 정보는 모델 구조체에 저장되는 것이고,
해당 배열이 테이블 뷰에 표시될 데이터라고 생각하면 된다.
그리고 tableView.register(_:forCellReuseIdentifier:)
메서드는 테이블 뷰에서 셀을 재사용 할 수 있게 등록하는 역할을 하는데,
이 메서드는 테이블 뷰에서 셀을 재사용할 수 있도록 셀 타입을 등록한다.
등록하는 이유 : 테이블 뷰는 많은 양의 데이터를 표시할 때,
메모리 사용을 최적화하기 위해 셀을 재사용하는 방식을 사용하게 된다.
이 메서드를 사용해서 재사용할 셀의 타입과 식별자를 등록해줘야
dequeueReusableCell
메서드가 해당 셀을 재사용할 수 있다. (둘 은 세트라는 뜻)
이 메서드는 주로 viewDidLoad()에서 호출하여 테이블 뷰를 설정할 때 사용한다.
.
.
.
// 테이블 뷰의 셀을 설정하는 메서드
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return movies.count
}
해당 코드는 UITableViewDataSource
프로토콜에 정의된 tableView(_:numberOfRowsInSection:)
메서드를 구현한 것이다.
(tableView 에 커맨드를 누른 채로 클릭하면 UITableViewDataSource 프로토콜이 나온다)
테이블 뷰에서 한 섹션에 표시할 행의 개수를 반환하는 역할을 하는데,
각 섹션에 몇 개의 행이 표시될지를 결정하는 매우 중요한 메서드라고 보면 된다.
이 코드에서 tableView(_:numberOfRowsInSection:)
메서드는
movies
배열의 개수만큼 행을 표시할 것임을 지정하는 것이다.
결론적으로 movies.count
의 값이 행의 개수가 되어,
movies
배열에 저장된 영화의 개수만큼 행이 표시 된다.
.
.
.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// 1. 재사용 가능한 셀을 가져옴
let cell = tableView.dequeueReusableCell(withIdentifier: "MovieCell", for: indexPath)
// 2. 영화 데이터 가져오기
let movie = movies[indexPath.row]
// 3. 셀에 영화 제목과 년도를 설정
cell.textLabel?.text = "\(movie.title) (\(movie.year))"
// 4. 셀을 반환
return cell
}
tableView(_:cellForRowAt:)
메서드는 각 셀의 내용을 설정하는 메서드로,
테이블 뷰가 각 행을 그릴 때마다 호출된다.
이 코드에서는 dequeueReusableCell(withIdentifier:)를 사용해서
재사용 가능한 셀을 큐에서 가져오거나 새로운 셀을 생성하게 되는데,
셀을 재사용하는 방식은 메모리 효율적이고 성능을 향상시킨다.
기억하자 아래에서도 또 이야기 할 것이다.
영화 데이터(movies 배열)에서 현재 행에 해당하는 영화 제목과 연도를 가져와
셀에 표시한 후, 그 셀을 반환 할 것이다.
UITableViewController
는 기본적으로 UITableView
를 포함하고 있다.
UITableViewCell
을 사용하여 데이터가 어떻게 표시될지를 설정하는데,
나의 경우 위에서 봤듯, 각 영화의 제목과 개봉 연도를 셀에 표시했다.
UITableViewCell
은 각 행의 UI
를 표현하는 역할을 한다.
내 코드에서는 여러 데이터를 테이블 뷰의 각 셀에 표시하기 위해서 UITableViewCell
이 사용했는데,
let cell = tableView.dequeueReusableCell(withIdentifier: "MovieCell", for: indexPath)
tableView.dequeueReusableCell(withIdentifier:for:)
를 사용해
테이블 뷰가 셀을 재사용하도록 구현했다.
여기서 MovieCell
은 셀의 재사용 식별자(identifier)로,
재사용 큐에서 동일한 식별자를 가진 셀을 찾아서 반환하는데, 만약 재사용되지 않은 경우 새로 생성된다.
let movie = movies[indexPath.row]
cell.textLabel?.text = "\(movie.title) (\(movie.year))"
그리고 영화 정보를 표시하기 위해 Movie
모델의 데이터를 가져와
셀의 텍스트 라벨(textLabel)에 할당했다.
셀의 기본 스타일 중 .default
를 사용하고 있으므로, 텍스트와 이미지를 간단히 추가할 수 있다
셀 스타일을 지정할 때 알아야 할 기본 스타일
코드로만 작성 시 다음 네 가지 기본 스타일을 활용할 수 있다.
스트로보드 없이 만약 UITableViewCell
스타일을 설정하고 싶다면 순서는 이렇다.
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "MovieCell")
}
먼저 UITableView
에서 사용할 셀을 사전에 등록해 주어야 한다.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MovieCell", for: indexPath)
let movie = movies[indexPath.row]
cell.textLabel?.text = "\(movie.title) (\(movie.year))"
return cell
그 다음은 셀을 가져오고 스타일을 지정해주면 되는데,
dequeueReusableCell(withIdentifier: for:)
를 호출해 셀을 가져오거나,
혹시라도 셀을 초기화할 때, 스타일이 필요한 경우는UITableViewCell(style: reuseIdentifier:)
를 사용한다.
스타일이 필요할 경우의 자체는 재사용과 관련이 없다는 걸 알고 있어야한다.
이 초기화 방법을 사용할 경우,
매번 새로운 셀을 생성하는 방식이기 때문에 성능 면에서 비효율적일 수 있음을 기억하자.
셀을 재사용하려면 dequeueReusableCell(withIdentifier: for:)를 사용해야 한다.
이 방식은 큐에서 셀을 가져와 재사용하므로 메모리 관리에 유리하기 때문이다.
여기서 "큐에서 셀을 가져와서 재사용한다"는 말은,
UITableView
가 사용하는 재사용 큐를 의미하는데,
이 큐는 필요 없는 셀을 저장하고 있다가 다시 필요한 시점에 그 셀을 재사용하는 방식이라서
결론적으로 셀을 새로 만들지 않고 이미 사용했던 셀을 재사용하는 방법이라 보면 된다.
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "MovieCell")
그리고 위 코드와 세트로 함께 사용된다.
이 메서드는 테이블 뷰에서 셀을 재사용할 수 있도록 셀 타입을 등록한다고 위에서 설명했었다.
이 두 메서드는 셀을 효율적으로 관리하고 재사용하기 위한 핵심 메서드로 기억하는게 좋다.
마지막으로 내용을 정리하며
Model
Movie
구조체가Model
역할을 한다. == 각 영화의 데이터를 관리한다.
View
UITableView
와UITableViewCell
이View
역할을 한다. == 영화 목록을 사용자에게 보여주는 UI 구성 요소이다.
Controller
MovieTableViewController
는Controller
역할을 한다. ==데이터
(movies)를UITableView
에 연결하여 데이터를 화면에 표시하고 셀을 클릭하는 등의 사용자 상호작용을 처리한다.
오늘 MVC 패턴을 이해하고, UITableView의 설정과 셀 재사용 방법을 익혔는데,
UITableViewController를 코드로 설정하여 데이터 모델을 뷰에 연결하고,
성능 최적화를 위해 셀을 재사용하는 방법을 중심적으로 공부해서 재미있었다.
덕분에 효율으로 코드를 작성하게 될 때 도움이 될 듯하다.
이제 UITableViewDelegate
와 같은 추가적인 기능을 더 공부해서..
사용자 인터랙션에 대한 처리도 잘 할 수 있도록 연습할 예정이다.