๐ŸŽ iOS

[iOS] MVVM Pattern ์†Œ์†Œํ•˜๊ฒŒ ์ ์šฉ

gom1n 2022. 9. 7. 16:50

MVVM ์„ค๋ช…์€ ๊ฑฐ๋‘์ ˆ๋ฏธํ•˜๊ณ  ์ผ๋‹จ ์ ์šฉ๋ถ€ํ„ฐ ํ•ด๋ณธ๋‹ค ...

* ์ฐธ๊ณ ๋กœ RxSwift ์•ˆ ์“ฐ๋Š” ๊ฑฐ*

* ๊ณต๋ถ€ ์ค‘์ด๋ผ ํ‹€๋ฆด ์ˆ˜ ์žˆ์Œ*

 

๊ฐœ์ธ์ ์œผ๋กœ ๋‚˜์˜ ๋А๋‚€ ์ ์€, MVC์—์„œ MV๋Š” ๊ทธ๋Œ€๋กœ๊ณ  ViewModel๋งŒ ์ถ”๊ฐ€ํ•˜๋ฉด ๋  ๊ฒƒ ๊ฐ™๋‹ค. (๋‹น์—ฐํ•จ)

 

ViewModel ํŒŒ์ผ์—์„œ๋Š” ๋กœ์ง ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ณ , ๋ณ€๊ฒฝ์ด ๊ฐ์ง€๋จ์— ๋”ฐ๋ผ ๋ทฐ๊ฐ€ ๋ณ€๊ฒฝ๋œ๋‹ค๊ณ  ๋ณด๋ฉด ๋  ๊ฒƒ ๊ฐ™๋‹ค.

์œ„ ํ™”๋ฉด์—์„œ ๋™๋„ค๋ฅผ ํด๋ฆญํ•˜๋ฉด, ๋™๋„ค๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋Š” ํŒ์—… ์ฐฝ์ด ๋œจ๊ณ , ๋™๋„ค ์„ ํƒ ์‹œ ํŒ์—… ์ฐฝ์ด ์‚ฌ๋ผ์ง€๋ฉฐ ์ƒ๋‹จ๋ฐ”์˜ ๋™๋„ค์ด๋ฆ„์ด ๋ฐ”๋€๋‹ค.

์‚ฌ์‹ค ์ €๋Ÿฐ ๊ฐ„๋‹จํ•œ ๊ธฐ๋Šฅ์— ๋ทฐ๋ชจ๋ธ์„ ์ ์šฉํ•˜๋Š” ๊ฒƒ์ด ์˜คํžˆ๋ ค ๋” ๋ณต์žกํ•ด์ ธ์„œ ๊ถŒ์œ ํ•˜๊ณ  ์‹ถ์ง€๋Š” ์•Š์ง€๋งŒ, 

๊ณต๋ถ€ํ•˜๋Š” ๊ณผ์ •์ด๋‹ˆ ์ผ๋‹จ ํ•ด๋ณธ๋‹ค. 

ํ•˜๋ฉด์„œ๋„ ์ž์‹ ์ด ์—†๋‹ค...

 

1. Observable ํŒŒ์ผ ์ถ”๊ฐ€

final class Observable<T> {
    typealias Listener = (T) -> Void
    var listener: Listener?
    var value: T {
        didSet {
            listener?(value)
        }
    }
    
    init(_ value: T) {
        self.value = value
    }
    
    func bind(listener: Listener?) {
        self.listener = listener
        listener?(value)
    }
}

 

 

 

 

2. View ํŒŒ์ผ ์•ˆ์—์„œ

var viewModel = ViewModel()

๋ทฐ๋ชจ๋ธ ๊ฐ์ฒด ์ƒ์„ฑ ํ›„,

 

viewDidLoad ํ•จ์ˆ˜์•ˆ์— bind()ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•ด์ค€๋‹ค.

override func viewDidLoad() {
    super.viewDidLoad()

    if let viewModel = self.viewModel {
        bind()
    }
}

bindํ•จ์ˆ˜๊ฐ€ ์—†์–ด์„œ ์—๋Ÿฌ ๋‚  ๊ฒƒ์ด๋‹ค.

์•„๋ž˜์ชฝ์— bindํ•จ์ˆ˜๋ฅผ ์งœ์ค€๋‹ค.

 

private func bind() {
    self.viewModel.myLocation.bind { myLocation in
        print("location changed!", myLocation)
    }
}

์ฝ”๋“œ ํ•ด์„์„ ํ•ด๋ณด์ž๋ฉด, ๋ทฐ๋ชจ๋ธ์—์„œ Observable(๊ด€์ฐฐ) ๊ฐ€๋Šฅํ•œ myLocation์ด๋ผ๋Š” ๋ณ€์ˆ˜์˜ ๊ฐ’์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ์ €๋ ‡๊ฒŒ ์ถœ๋ ฅ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

์˜๋„๋Œ€๋กœ ๋˜๋‚˜ ๋ณด์ž.

 

3. ViewModel

class MainViewModel {
    var myLocation: Observable<String?> = Observable(nil)
    
    init() {
        
    }
    // ๋™๋„ค ํด๋ฆญ ์ด๋ฒคํŠธ
    func locationDidTap(_ location: String) {
        myLocation.value = location
    }
}

ViewModel์˜ ์ฝ”๋“œ๋Š” ์œ„์™€ ๊ฐ™๋‹ค. 

๋™๋„ค ํด๋ฆญ ์ด๋ฒคํŠธ๊ฐ€ ์ผ์–ด๋‚  ๋•Œ๋งˆ๋‹ค locationDidTap์ด๋ผ๋Š” ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋˜๊ณ , 

๊ทธ๋•Œ์˜ myLocation์˜ ๊ฐ’์˜ ๋กœ์ง ์ฒ˜๋ฆฌ๋ฅผ ์•ˆ์—์„œ ํ•ด์ฃผ๋Š” ๊ฒƒ์ด๋‹ค.

(์œ„ ๊ฒฝ์šฐ๋Š” ๋„ˆ๋ฌด ๊ฐ„๋‹จํ•ด์„œ ์˜คํžˆ๋ ค ์—†๋Š” ๊ฒŒ ๋‚˜์„ ์ •๋„ ... ๊ทธ๋ž˜๋„ ํ•ด๋ณด์ž)

 

[View์˜ TableView Delegate ๋ถ€๋ถ„]

"์–‘์žฌ๋™", "์„œ๊ต๋™", "๋ˆ์•”๋™" ์ด๋ผ๊ณ  ์จ์ ธ์žˆ๋Š” ์ € ํ…Œ์ด๋ธ”๋ทฐ๋ฅผ ํด๋ฆญ ์‹œ ํ˜ธ์ถœ๋˜๋Š” ํ•จ์ˆ˜์ด๋‹ค. 

ViewModel ์˜ locationDidTap์„ ํ˜ธ์ถœํ•ด์ค€๋‹ค.

func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let idx = indexPath.item
    self.viewModel.locationDidTap(locationData[idx])
    
    tableView.deselectRow(at: indexPath, animated: true)
}

 

4. ๋กœ๊ทธ ์ถœ๋ ฅ

๊ตฟ ~~ 

ViewModel์˜ ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ์„ View์—์„œ ์•Œ์•„์ฑ˜๋‹ค๋Š” ๊ฒƒ์ด ์ฆ๋ช…๋˜์—ˆ๋‹ค.

 

์ด์ œ RxSwift๋ฅผ ์‚ฌ์šฉํ•ด ์ฝ”๋“œ๋ฅผ ๋‹ค๋“ฌ์–ด๋ณด์ž.