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์ด๋ผ๋Š” ํ‚ค์›Œ๋“œ๋กœ ๊ฒ€์ƒ‰ํ•˜๋ฉด ๋งŽ์€ ์ž๋ฃŒ๊ฐ€ ๋‚˜์˜ฌ ๊ฒƒ์ด๋‹ค)

[์ •๋ฆฌ] MVVM์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•œ ์กฐ๊ฑด

  1. ๊ด€์ฐฐ ๊ฐ€๋Šฅํ•œ ๊ฐ์ฒด

  2. ๋ชจ๋ธ

  3. ๋ทฐ ๋ชจ๋ธ

  4. ๋ทฐ ์ปจํŠธ๋กค๋Ÿฌ (View)

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