์๋ ํ์ธ์ Foma ๐ป ์ ๋๋ค!
์ค๋์ ์ ๋ง ์ค๋๋ง์ ๋์์ธ ํจํด์ ์ ๋ฆฌํ๊ฒ ๋์๋๋ฐ์.
๊ทธ ์ค์์ ๊ฐ์ฅ ์ธ๊ธฐ(?)์๊ณ ํซํ MVVM ๋์์ธ ํจํด์ ๋ค๋ค๋ณด๋ ค๊ณ ํฉ๋๋ค.
(SwiftUI ๋ํ ๊ธฐ๋ณธ ๋์์ธ ํจํด์ผ๋ก MVVM์ ์ฌ์ฉํฉ๋๋ค.)
๋ฐ๋ก ์์ํ ๊ฒ์~
MVVM(Model - View - ViewModel)ํจํด์ด๋? ๐ง
MVVM์ ํ๋์ ์ํํธ์จ์ด ์ํคํ ์ฒ ํจํด ์ผ๋ก GUI ์ฝ๋๋ก ๊ตฌํํ๋ ๊ทธ๋ํฝ ์ฌ์ฉ์ ์ธํฐํ์ด์ค(๋ทฐ)์ ๊ฐ๋ฐ์ ๋น์ฆ๋์ค ๋ก์ง ๋๋ ๋ฐฑ์๋ ๋ก์ง(๋ชจ๋ธ)๋ก๋ถํฐ ๋ถ๋ฆฌ์์ผ์ ๋ทฐ๊ฐ ์ด๋ ํน์ ํ ๋ชจ๋ธ ํ๋ซํผ์ ์ข ์๋์ง ์๋๋ก ํด์ค๋ค. MVVM์ ๋ทฐ ๋ชจ๋ธ์ ๊ฐ ๋ณํ๊ธฐ์ธ๋ฐ, ์ด๋ ๋ทฐ ๋ชจ๋ธ์ด ๋ชจ๋ธ์ ์๋ ๋ฐ์ดํฐ ๊ฐ์ฒด๋ฅผ ๋ ธ์ถ(๋ณํ)ํ๋ ์ฑ ์์ ์ง๊ธฐ ๋๋ฌธ์ ๊ฐ์ฒด๋ฅผ ๊ด๋ฆฌํ๊ณ ํํํ๊ธฐ๊ฐ ์ฌ์์ง๋ค๋ ๊ฒ์ ์๋ฏธํ๋ค. ์ด๋ฌํ ์ ์์, ๋ทฐ ๋ชจ๋ธ์ ๋ทฐ ๋ณด๋ค๋ ๋ ๋ชจ๋ธ์ธ ๊ฒ์ด๋ฉฐ, ๋ชจ๋ ๋ทฐ๋ค์ ๋์คํ๋ ์ด ๋ก์ง์ ์ ์ธํ ๋๋ถ๋ถ์ ๊ฒ๋ค์ ์ฒ๋ฆฌํ๋ค. - ์ํค ๋ฐฑ๊ณผ -
์ฆ, ํ๋ฉด์ ๋ง๋๋ ์ฝ๋์ ๋ค์ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ๋ ์ฝ๋๋ฅผ ๋ถ๋ฆฌํด์ ํ๋ ๊ฒ์ด ๋ฐ๋ก MVVM์ด ํต์ฌ์ด์ฃ !
๋ง์ดํฌ๋ก์ํํธ์ ์ผ ์ฟ ํผ์ ํ ๋ ํผํฐ์ค์ ์ํด ๋ฐ๋ช ๋์์ผ๋ฉฐ ์กด ๊ตฌ์ค๋จผ์ด 2005๋ ์ฒ์์ผ๋ก ๋ธ๋ก๊ทธ์ ๋ฐํํ๋ค๊ณ ํ๋ค์!
(๊ทธ๋๋ ์ต๊ทผ์ ๋ง๋ค์ด์ก๋ค๊ณ ์๊ฐํ๋๋ฐ 16๋ ์ด๋ ๋์๋ค์..)
๊ฐ ์ญํ ์ ๊ทธ๋ฆผ์ผ๋ก ์ดํด๋ณด๋ฉด ์๋์ ๊ฐ์ต๋๋ค.
ํน์ง
๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ์ ์ฌ์ฉํ์ฌ ๋ทฐ๊ฐ ๋ทฐ๋ชจ๋ธ์ ๊ฐ์ ๊ด์ฐฐํ์ฌ ๋ณํ๋ฅผ ๋ฐ์ํฉ๋๋ค.
MVVM์ ์ฅ๋จ์ ๐๐ป
์ฅ์
- ๋ทฐ ๋ก์ง๊ณผ ๋น์ง๋์ค ๋ก์ง์ ๋ถ๋ฆฌํ์ฌ ์์ฐ์ฑ์ ๋ํ ์ ์๋ค. (UI๊ฐ ๋์ค์ง ์์๋ ๊ฐ๋ฐ ๊ฐ๋ฅ)
- ํ ์คํธ๊ฐ ์์ํด์ง๋ค. (์์กด์ฑ์ด ์๊ธฐ ๋๋ฌธ)
- ๋ทฐ์ ๋ทฐ๋ชจ๋ธ์ด 1:n ๊ด๊ณ์ด๊ธฐ ๋๋ฌธ์ ์ค๋ณต๋๋ ๋ก์ง์ ๋ชจ๋ํ ํด์ ์ฌ๋ฌ ๋ทฐ์ ์ ์ฉํ ์ ์๋ค. (์ฝ๋ ์ฌ์ฌ์ฉ ๊ฐ๋ฅ)
- ๋ง์ ๊ธฐ์ ๋ค์ด ์ ์ฉํ๋ ๋์์ธ ํจํด์ด๋ค.
๋จ์
- ์ค๊ณํ๊ธฐ๊ฐ ๋ณต์กํ๋ค. (Rx,๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ์ ๋ํ ์ง์ ํ์)
- ๋ทฐ๋ชจ๋ธ์ด ๋น๋ํด์ง ์ ์๋ค.
- ๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ์ผ๋ก ์ธํ ๋ฉ๋ชจ๋ฆฌ ์๋ชจ๊ฐ ์ฌํ๋ค.
์ฝ๋ ๊ตฌํ
์ค๋์ ๊ฐ๋จํ๊ฒ Rx๋ฅผ ์ฌ์ฉํ์ง ์๊ณ MVVM์ ๊ตฌํํด๋ณด๊ฒ ์ต๋๋ค.
์ด์ ์ MVC,MVP๋ฅผ ๊ตฌํํ๋ ์บ๋ฆญํฐ ํ๋ฉด์ ๊ทธ๋๋ก ์ฐ๊ฒ ์ต๋๋ค.
(๊ฐ๋จํ๊ฒ ๊ณผ์ ์ ์ค๋ช ๋๋ฆฌ๋ฉด ์๋ Previous์ Next ๋ฒํผ์ ๋๋ฅด๋ฉด ์บ๋ฆญํฐ ๋ชจ๋ธ์ด ์ด์ ๋๋ ๋ค์ ๋ชจ๋ธ๋ก ๋ฐ๋๊ฒ ๋๊ณ
๋ทฐ๋ ๋ฐ๋ ๋ชจ๋ธ์ ๋ฐ๋ผ ๋์ฐ๋ ์ ๋ณด๊ฐ ๋ฐ๋๊ฒ ๋ฉ๋๋ค.)
Observable
MVVM์ ํต์ฌ์ธ ์ต์ ๋ฒ๋ธ์ ๊ตฌํํ๊ฒ ์ต๋๋ค.
๋จผ์ Listener๋ฅผ ์ต๋ช ํด๋ก์ ๋ก ๋ง๋ค์ด๋์ต๋๋ค.
value๋ didSet์ ์ด์ฉํด์ ๋ฆฌ์ค๋์ ์ด๋ค ๊ฐ์ด ๋ค์ด์ค๋ฉด ๊ทธ์ ๋ง๊ฒ ๋ณํํ๋๋ก ๋ง๋ค์ด๋์ต๋๋ค.
bind ๋ฉ์๋๋ฅผ ํตํด์ ํ์ฌ Observable์ ๋ฆฌ์ค๋๋ฅผ ์ ์ฉํ๊ณ ๋ฆฌ์ค๋๋ ๊ฐ์ด ๋ณ๊ฒฝ๋๋ฉด ๋ฐ๋ก ํด๋ก์ ธ๋ฅผ ์คํํฉ๋๋ค.
import Foundation
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)
}
}
Model
์บ๋ฆญํฐ๋ฅผ ๊ตฌ์ฑํ๋ ๋ชจ๋ธ์ ๋๋ค.
import UIKit
struct MVVM_Character {
//ํ๋ฉด์ ๊ตฌ์ฑํ ๋ฐ์ดํฐ
let image:UIImage
let name:String
let gender:String
let country:String
//์ด๊ธฐํ
init(name:String,image:UIImage,gender:String,country:String) {
self.name = name
self.image = image
self.gender = gender
self.country = country
}
}
View
๋ทฐ๋ ์๋์ ๊ฐ์ด ๋ทฐ๋ชจ๋ธ์ ์์ ํ๊ณ ์์ต๋๋ค.
๋ทฐ๋ชจ๋ธ์ ํ๋กํผํฐ๋ค์ ๋ทฐ์ ํ๋กํผํฐ์ ๋ฐ์ธ๋ฉ์ ์์ผ์ค๋๋ค. (์ด๊ฒ์ด ๋ฐ๋ก ๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ์ ๋๋ค.)
์ฆ, ๋ทฐ๋ชจ๋ธ์ ๊ด์ฐฐํ๊ณ ๋ฐ๋๋ ๊ฐ์ ๋ฐ๋ผ์ ๋ทฐ๋ฅผ ๋ณํ์์ผ์ค๋ค๊ณ ์ค์ ํ๋ ๊ฒ์ ๋๋ค.
์ ์ ์ ์ธํฐ๋์ ์ ๋ฐ๊ฒ ๋๋ฉด ๋ทฐ๋ชจ๋ธ์ ํน์ ๋ฉ์๋๋ฅผ ์คํ์์ผ ๋ทฐ๋ชจ๋ธ ๊ฐ์ ๋ฐ๊ฟ์ค๋๋ค.
ํ๋ง๋๋ก ๋ทฐ์์ ์ด๋ค ๋ฒํผ์ด ๋๋ฆฌ๋ฉด -> ๋ทฐ๋ชจ๋ธ์ ๊ฐ์ด ๋ณํํ๊ณ -> ๊ฐ์ด ๋ณํํ๋ฉด ๋ทฐ๊ฐ ๊ฐ์ ๋ง๊ฒ ๋ณํํ๋ ๊ฒ์ ๋๋ค.
์ด๋ ๊ฒ ๋ณด๋ฉด ๋ทฐ์ ์ฝ๋๊ฐ MVC์ ๋นํด ํจ์ฌ ๊ฐ๊ฒฐํด์ง ๊ฒ์ ๋ณผ ์ ์์ฃ ?
import UIKit
class MVVM_CharacterViewController: UIViewController {
//MARK:- IBOutlets
@IBOutlet weak var country: UILabel!
@IBOutlet weak var name: UILabel!
@IBOutlet weak var gender: UILabel!
@IBOutlet weak var image: UIImageView!
@IBOutlet weak var nextButton: UIButton!
@IBOutlet weak var previousButton: UIButton!
//MARK:- Properties
private let viewModel:CharacterViewModel = CharacterViewModel()
//MARK:- Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
bind()
}
//MARK:- Functions
func bind() {
viewModel.image.bind { [weak self] image in
self?.image.image = image
}
viewModel.name.bind { [weak self] name in
self?.name.text = name
}
viewModel.gender.bind { [weak self] gender in
self?.gender.text = gender
}
viewModel.country.bind { [weak self] country in
self?.country.text = country
}
}
//MARK:- IBActions
@IBAction func tapPreviousButton(_ sender: Any) {
viewModel.tapButton(isPrevious: true)
}
@IBAction func tapNextButton(_ sender: Any) {
viewModel.tapButton(isPrevious: false)
}
}
ViewModel
์ฐ์ ์ ์ฒด ๋ชจ๋ธ ๋ฐ์ดํฐ๋ ์์๋ก ๋ฏธ๋ฆฌ ๋ง๋ค์ด๋์์ต๋๋ค.
๋ทฐ์ ํ์ํ ํ๋กํผํฐ๋ค์ Observable ํ์ ์ผ๋ก ๋ง๋ค์ด๋์ต๋๋ค.
๋ทฐ์์ ์ผ์ด๋๋ ์ธํฐ๋์ ์ ๋ฐ๋ผ ๊ฐ์ด ๋ณํํ๋๋ก ํ๋ ๋น์ง๋์ค ๋ก์ง์ ์์ฑํฉ๋๋ค.
import UIKit
class CharacterViewModel {
//MARK:- Properties
let image:Observable<UIImage?> = Observable(nil)
let name:Observable<String?> = Observable(nil)
let gender:Observable<String?> = Observable(nil)
let country:Observable<String?> = Observable(nil)
var index:Int = 0
init() {
self.image.value = characters[0].image
self.name.value = characters[0].name
self.gender.value = characters[0].gender
self.country.value = characters[0].country
}
func tapButton(isPrevious:Bool) {
if isPrevious {
index = index > 0 ? index-1 : 0
}else {
index = index < characters.count - 1 ? index + 1 : characters.count - 1
}
self.image.value = characters[index].image
self.name.value = characters[index].name
self.gender.value = characters[index].gender
self.country.value = characters[index].country
}
}
์คํํ๋ฉด
๋๋์
์ง๊ธ๊น์ง MVVM ํจํด์ ์ฌ์ฉํด์ ์ฑ์ ๊ตฌํํ๋ฉด์ ์๊ฐํ๋ ๋ฐฉ์์ด ๋ณํํ๋ ๊ฒ์ ๋๊ผ๋ค.
์๋ MVC๋ก ๊ตฌํํ๋ค๋ฉด "๋ฒํผ์ ๋๋ฅด๋ฉด ๋ทฐ๊ฐ ์ด๋ ๊ฒ ๋ณํํ๋ฉด ๋๊ฒ ๋ค"๋ผ๋ฉด
MVVM์ผ๋ก๋ "๋ฒํผ์ ๋๋ฅด๋ฉด ๋ทฐ๋ชจ๋ธ์๊ฒ ์๋ ค์ฃผ๊ณ ๋ทฐ๋ชจ๋ธ์ ๊ฐ๋๋ก ๋ทฐ๊ฐ ๋ณํ๊ฒ ๋ค."๋ก ๋ฐ๋์๋ค.
๋์ ์ฐจ์ด์ ์ MVC๋ ๋ทฐ๋ฅผ ๋ด๊ฐ ์ง์ ๋น ๋ฅด๊ฒ ๋ฐ๊พธ๋ ๋๋์ด๊ณ MVVM์ ๋ฏธ๋ฆฌ ์์ฑํด๋์ ๋ฐฉ์๋๋ก ๋ทฐ๊ฐ ์์์ ๋ฐ๋๋ ๋๋์ด์๋ค.
ํ์ง๋ง ํ์คํ ์ฅ๋จ์ ์ ์์๋ค.
MVVM์ ์ค์ ๋ก ๋น์ง๋์ค ๋ก์ง์ด ๋ทฐ๋ชจ๋ธ์ ์์ด์ ์ด๋ค ์ฝ๋๋ฅผ ์ฐพ๊ฑฐ๋ ์ฝ์ ๋ ํจ์ฌ ์์ํ๋ค.
๋ํ ๋ฏธ๋ฆฌ ์์ฑํด๋์ ์ฝ๋๋ฅผ ์ฌ์ฌ์ฉํ ์๋ ์์ด์ ์์ฐ์ ์ธ ํจ์จ์ ์ง์ ์ ์ผ๋ก ๋ง์ด ๋๊ผ๋ค.
ํ์ง๋ง ๊ฐ๋จํ ํ๋ฉด์ ๋ง๋ค ๋์กฐ์ฐจ ๋จธ๋ฆฌ๊ฐ ๋ณต์กํด์ง๊ณ ์๊ฐ๋ ์ค๋ ๊ฑธ๋ ธ๋ค...
MVVM ํจํด์ ์ ์ฉํ์ผ๋ฉด ์๋ฌด๋ฆฌ ๊ฐ๋จํ ํ๋ฉด์ด๋ผ๋ ๋ญ๋ ํ๋๋ถํฐ ์ด๊น์ง MVVM์ผ๋ก ๊ตฌํํด์ผ๋ผ!
๋ผ๊ณ ์๊ฐํ๊ณ ์๊ฐ์ ๋ง์ด ์ผ๋ ๊ฒ ๊ฐ๋ค.
ํ์ง๋ง ๊ฐ์ฅ ์ข์๊ฑด MVC์ ์ ํฉํ ํ๋ฉด์ MVC๋๋ก MVVM์ ๋ง๋ ํ๋ฉด MVVM๋๋ก ์ ํฉํ ๋์์ธ ํจํด์ ์ฌ์ฉํด๊ฐ๋ฉด์
์์ฐ์ฑ์ด๋ ํ์ ์ ํจ์จ์ ์ฌ๋ ค์ฃผ๋ ๋์์ธ ํจํด์ ์ ์ฉํด์ ๊ฐ๋ฐํ๋ ๊ฒ์ด ์ข๋ค๊ณ ๋๊ผ๋ค.
Source Code
๊ฐ ๋์์ธ ํจํด์ ์์ค์ฝ๋๋ ์๋ ๊นํ์ ์ ๋ฆฌํด๋๊ฒ ์ต๋๋ค!
Reference
์๋ ์ฌ์ดํธ๋ฅผ ์ฐธ๊ณ ํ์ต๋๋ค.
'๐ iOS > Architecture' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[iOS/Framework] Quick/Nimble์ด ๋ญ๊น? (feat. ์ฌ์ฉ๋ฒ) (0) | 2021.08.31 |
---|---|
[RIBs] AddMemo ๊ตฌํํ๊ธฐ (๋ฉ๋ชจ ์ถ๊ฐํ๊ธฐ) (0) | 2021.08.26 |
[RIBs] Memo ์ญ์ ,์์ ๊ตฌํํด๋ณด๊ธฐ (0) | 2021.08.26 |
[RIBs] Router๋ฅผ ์ด์ฉํด ํ๋ฉด ์ ํํด๋ณด๊ธฐ (feat. Memo) (0) | 2021.08.24 |
[RIBs] Interactor๋ก ๋น๋์ง์ค ๋ก์ง ์ฒ๋ฆฌํด๋ณด๊ธฐ (feat. ์ด๊ธฐ์ธํ ) (0) | 2021.08.18 |
๋๊ธ