MVVM
๋ค์ด๊ฐ๊ธฐ ์
ViewModel์ด๋?
์ด์ ๊ธฐ์กด MVC ํ๋ก์ ํธ๋ฅผ MVVM์ผ๋ก ๋ฆฌํฉํ ๋งํ๊ธฐ ์ ์, ViewModel์ ๋ํด์ ๊ฐ๋จํ๊ฒ ์์๋ณด์.
ViewModel์ View์์ ์ผ์ด๋ ๋ณ๊ฒฝ์ ๊ฐ์งํด์ Model์๊ฒ ๊ทธ ๋ณ๊ฒฝ์ ์ ๋ฌํ๋ค.
์๋ฅผ ๋ค์ด, ์ฌ์ฉ์๊ฐ ์์ ์ ๊ฒ์ํ๋ฉด, ์ ๋ ฅํ ๋๋ง๋ค ๋๋ ๊ฒ์ ๋ฒํผ์ ๋๋ฅผ ๋ ViewModel์ด ๊ทธ ๋ณ๊ฒฝ์ ๊ฐ์งํ๋ ๊ฒ์ด๋ค. ์ดํ์ Model์ ๊ทธ ๋ณ๊ฒฝ์ ์ ๋ฌํ๊ธฐ ์ํด NetworkManager๋ผ๋ ๊ณ์ธต์๊ฒ ๋ฐ์ดํฐ ๋ณ๊ฒฝ์ ์์ฒญํ๋ ์ญํ ์ ๋ด๋นํ๋ค.
๊ธฐ์กด MVC์ ViewController์ MVVM์ ViewController์ ์ฐจ์ด๋ฅผ ์ดํด๋ณด์.
์ ์ ์ ์ ๋ ฅ์ ๋ํ ๋ณ๊ฒฝ์ ViewModel์ด ์ฒ๋ฆฌํ๊ฒ ๋์๊ณ , ๋คํธ์ํฌ ์์ฒญ์ ์ญํ ๋ ViewModel์ด ๋ด๋นํ๊ฒ ๋์๋ค. ๊ทธ๋์ View(๊ธฐ์กด ViewController)๋ ๋ทฐ๋ฅผ ๋ณด์ฌ์ฃผ๊ณ , TableView๋ฅผ reloadํ๋ ์ญํ ๋ง ๋จ๊ฒ ๋์๋ค.
MVVM์ ๊ตฌํํ๊ธฐ ์ํ ์กฐ๊ฑด
ViewModel์ ์ด๋ค ๋ณํ๋ฅผ ๊ฐ์งํ ์ ์์ด์ผํ๋ค. ์ฌ์ฉ์๊ฐ ๊ฒ์์ด๋ฅผ ์ ๋ ฅํ ๋, ๊ทธ ๋ณํ๋ฅผ ๋ทฐ๋ชจ๋ธ์ด ๊ฐ์งํด์ผํ๊ธฐ ๋๋ฌธ์ด๋ค.
ํ์ง๋ง UIKit์ ๊ทธ๋ฐ ๋ฐ์ธ๋ฉ ์์ง์ ์ ๊ณตํด์ฃผ์ง ์๋๋ค. ๋ฐ๋ผ์ UIKit์์ MVVM์ ๊ตฌํํ๊ธฐ ์ํด์๋ ๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ ์์ง์ ์ง์ ๊ตฌํํด์ฃผ์ด์ผํ๋ค.
(๋ฌผ๋ก Combine์ด๋ RxSwift ๋ฑ์ ํ์ฉํ์ฌ ์ํ์ ๋ณํ๋ฅผ ๋ฐ์ธ๋ฉํ ์ ์๋ค. ์ฑ์ฉ ๊ณต๊ณ ์ ๋์ค๋ MVVM ๋๋ถ๋ถ Combine์ด๋ RxSwift๋ฅผ ์ด์ฉํด์ ๊ตฌํํ๋ ๊ฒ ๊ฐ๋๋ผ. ํ์ง๋ง ๋๋ VM์ ์ฌ์ฉํ๋ ์ด์ ์กฐ์ฐจ.. ๋ชจ๋ฅด๊ฒ ๋ค..)
๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ ์์ง์ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ ๋ง์ง๋ง, ๋๋ ์ ๋ค๋ฆญ ํ์ ์ Observable ํด๋์ค๋ฅผ ๋ง๋ค์ด์ ์ฌ์ฉํ๋ค. (Boxing์ด๋ผ๋ ํค์๋๋ก ๊ฒ์ํ๋ฉด ๋ง์ ์๋ฃ๊ฐ ๋์ฌ ๊ฒ์ด๋ค)
ViewModel
๊ด์ฐฐ ๊ฐ๋ฅํ ๊ฐ์ฒด (Observable)
UIKit์ ์์คํ ์์ง์์ observableํ ํํ๋ฅผ ์ ๊ณตํด์ฃผ์ง ์๋๋ค.
์ด์ UIKit์์ MVVM์ ๊ตฌ์ถํ๊ธฐ ์ํด์๋ ๊ฐ์ฒด๋ฅผ ๊ด์ฐฐ๊ฐ๋ฅํ๋๋ก ์ค์ ํด์ผํ๋ค.
์ฌ๋ฌ๊ฐ์ง ๋ฐฉ๋ฒ์ด ์์ง๋ง, ๋๋ Observable ํด๋์ค๋ฅผ ๋ง๋ค์ด ๊ตฌํํ๋ค.
๋คํธ์ํฌ๋ฅผ ํตํด ๋ฐ์์จ ๋ฐ์ดํฐ๋ฅผ Model์ ์ฃผ์ ํ๋ค.
์ฝ๋
class Observable<T> {
var value: T? {
didSet {
listener?(value)
}
}
init(_ value: T?) {
self.value = value
}
private var listener: ((T?) -> Void)?
func bind(_ listener: @escaping (T?) -> Void) {
listener(value)
self.listener = listener
}
}
Observable์ด๋ผ๋ ์ ๋ค๋ฆญ ํด๋์ค๋ฅผ ๋ง๋ค์๋ค. ํ์ T์ ํด๋น๋๋ ๊ฐ์ ๋ํด ๋ณํ๋ฅผ ๊ฐ์งํ๋ ์ญํ ์ ์ํํ๊ณ , ๊ฐ์ด ๋ณํ ๋๋ง๋ค listener์๊ฒ ์๋ ค์ฃผ๊ฒ ๋๋ค.
bind ํจ์๊ฐ ์ดํด๊ฐ ์ด๋ ค์ธ ์ ์์ ๊ฒ ๊ฐ๋ค. ๊ฒฐ๋ก ์ ๋จผ์ ๋งํ์๋ฉด, ๋ฑ๋ก๋ ๊ฐ์ด ๋ณํ๊ณ , listner์ ๋ฑ๋ก๋ ํจ์๊ฐ ๋๋ ํ์ view์์ ํด์ผํ ์ผ์ ์์ฑํด์ฃผ๋ฉด ๋๋ค. (์ด ํ๋ก์ ํธ์์๋ tableview๋ฅผ reloadํ๋ ์ฝ๋๋ฅผ ์์ฑํ๋ฉด๋๋ค.)
์ง๊ด์ ์ผ๋ก ์ค๋ช ํ์๋ฉด listner ๋๋๊ณ ํด์ผํ ์ผ์ ์์ฑํ๋ ๊ณต๊ฐ์ด๋ผ๊ณ ์ดํดํ๋ฉด ๋๋ค. ์ด bind๋ฅผ ์ฌ์ฉํ๋ฉด, viewModel์ ๋ณํ๋ฅผ ๊ฐ์งํ ์ ์๊ฒ ๋๋ ๊ฒ์ด๋ค.
์ฝ๋ (ViewModel)
struct UserListViewModel {
init(){
fetchMusicData()
}
let networkManager = NetworkManager.shared
var users: Observable<[Music]> = Observable([])
func fetchMusicData() {
networkManager.requestData(term: "jazz") { result in
switch result {
case .success(let data):
self.users.value = data
case .failure(let error):
print(error.localizedDescription)
}
}
}
}
(์ง๊ธ์ ๊ฒ์์ ๋ํ ๊ธฐ๋ฅ์ด ์์ด์ ์ ์ ์ ๊ฒ์์ด๋ฅผ ๊ด์ฐฐํ๋ ๊ธฐ๋ฅ์ด ์๋ค)
NetworkManager์ ํต์ ํ๊ณ , Model์ธ users๋ฅผ ๊ด์ฐฐํ๊ณ ์๋ค.
์ฌ๊ธฐ์๋ ๋ํ๋์ง ์์์ง๋ง, ์ ์ ๊ฐ ๊ฒ์์ด๋ฅผ ์ ๋ ฅํ ๋ fetchMusicData๋ฅผ ํธ์ถํด์ฃผ๋ฉด ๋๋ค.
View (ViewController.swift)
๋ทฐ๋ชจ๋ธ์ ๋ณ๊ฒฝ์ ๊ฐ์งํด์ tableview๋ฅผ ๋ค์ ๋ก๋ํ๋ค.
์ฝ๋
final class ViewController: UIViewController {
private let tableView: UITableView = {
let table = UITableView()
table.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
return table
}()
private var viewModel = UserListViewModel()
override func viewDidLoad() {
super.viewDidLoad()
setTableView()
setDataBinding()
}
private func setTableView() {
view.addSubview(tableView)
tableView.frame = view.bounds
tableView.dataSource = self
}
private func setDataBinding() {
viewModel.users.bind { _ in
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = viewModel.users.value?[indexPath.row].musicTitle
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
viewModel.users.value?.count ?? 0
}
}
ViewController๋ ์ด์ View๋ฅผ ๊ทธ๋ฆฌ๋ ์ญํ ๋ง ๊ฐ์ง๊ณ ์๋ค.
๊ทธ๋์ ViewModel์ users ๋ฐ์ดํฐ๋ฅผ bindํ์ฌ ๋ณํ๋ฅผ ๊ด์ฐฐํ๊ณ , listner์ ๋์์ด ๋๋๋ฉด tableView๋ฅผ reloadํด์ฃผ๋ ์ญํ ์ ๊ฐ๊ฒ ๋๋ค.
๊ฒฐ๋ก
๊ทธ๋์ ์ MVVM์ ์ฌ์ฉํด์ผํ ๊น? ์ธ์ ์ฌ์ฉํ๋ฉด ์ข์๊น?
........ ์ ๋ชจ๋ฅด๊ฒ ๋ค. ์ฌ์ค Messive View Controller๋ผ๋ ์ด์ผ๊ธฐ๊ฐ ๋ง์ง๋ง, ๋ทฐ๋ฅผ ์ ๋ถ๋ฆฌํ๊ณ , NetworkModel์ ์ ์ค๊ณํ๋ฉด ๊ทธ ๋ฌธ์ ๋ ์ถฉ๋ถํ ํด๊ฒฐํ ์ ์์ ๊ฒ์ด๋ผ ์๊ฐํ๋ค..
๊ตฌ์กฐ๋ณํ์ ํด๋ดค์ง๋ง ์ฅ์ ์ ์ ๋ชจ๋ฅด๊ฒ ๋ค
๊ฐ์ฒด๋ฅผ ๊ด์ฐฐํ๊ธฐ์ํด ๋ ๋ค๋ฅธ ๊ฐ์ฒด ์์ ๋ด์ ๊ด๋ฆฌํด์ผํ๊ณ , ViewController๋ง๋ค ํ๋์ ViewModel์ด ํ์ํ ํ ๋ฐ(์๋๊ฐ..?) ์ฌ๋๋ค์ ์ด๊ฒ ์ง์ง ์ข์๊ฐ?
ํ ์คํฐ๋ธํ ์ฝ๋๊ฐ ๋๋ค! ๋ผ๊ณ ํ๋๋ฐ, ์ด ๋ถ๋ถ์ ์กฐ๊ธ ๋ ๊ณ ๋ฏผํด๋ณด๊ฒ ๋ค!
์์ง ๋ง์ ํ๋ก์ ํธ๋ฅผ ํด๋ณด์ง ์์์ ์ด๋ฐ ๊ณ ๋ฏผ์ ํ๋ ๊ฒ ๊ฐ๋ค
Last updated