부쩍 MVVM에 관심이 많아졌다.
보면 볼수록 이렇게 좋은게 있다고?! 싶다.
Massive ViewController 는 이제 겪었으니 다음 레벨 히비고 해야한다.
[Raywenderlich] iOS MVVM Tutorial: Refactoring from MVC
이 수업을 바탕으로 정리한 내용이다.
자 그럼 시작 ~
Box
final class Box<T> {
//1
//typealias Listener = (T) -> Void
var listener: ((T) -> Void)?
//2
var value: T {
didSet {
listener?(value)
}
}
//3
init(_ value: T) {
self.value = value
}
//4
func bind(listener: ((T) -> Void)?) {
self.listener = listener
//e.
listener?(value)
}
}
Box
라는 클래스를 만들었다.
typealias
는 별명으로 이해하면 되겠다.
(T) -> Void
= (T) -> ()
랑 같다. Void
= ()
그러나 return
값을 쓸 때는 Void
로 쓰는게 CONVENTION이라고 한다.
서론이 길었다. bind
함수에 대해서 설명해보겠다.
a. (T) → Void
라는 listener
를 인자로 받는다.
b. 음음 listener
는 closure
네.
c. (T)
라는 인자를 받고 return
값이 없는 closure
이군.
d. 이 closure
를 self.listener
로 update해주는군.
e. 그리고 이 listener
( 인자로 받은. not self.listener
)에다가는 가지고 있는 value
를 넣어주는군
인자로 받아온 listener
에 기존에 가지고 있었던 value
를 알려주는것이다.
이 과정이 없다면 인자로 받아온 listener
는 보시다시피 어디서 update
되겠는가 ?
value
안에 선언된 property observer인 didSet
에서만 바뀐다. 이말은 즉 value
의 '변화'가 있을 때만 listener
가 작동한다는 말이 되시겠다.
e과정이 없게 된다면 value
의 '초기값'을 모르게 된다.
Observer(여기선 Box) 객체를 다른 곳에서 만들었다고 가정했을 때, 다른 ViewController에서 해당 Observer 객체에 bind(ing)한다고 하면, ViewController에서는 현재 값을 모르니까 값이 전달이 안되서 보여지거나 하지 않겠지?
그렇담 사용 해볼까?
class ViewModel
public class ViewModel {
let name = Box(" ")
let meetingDate = Box(" ")
let profileImage = Box<UIImage?> = Box(nil)
init() {
holdMeeting()
}
func holdMeeting() {
self.name.value = "Aiam"
self.meetingDate.value = "2023-09-20"
self.profileImage = UIImage(named: "AiamProfile")
}
}
ViewModel
class를 만들었다. init
에 holdMeeting함수를 넣어줬다.
class ViewController
class ViewController: UIViewController {
let nameLabel = UILabel()
let dateLabel = UILabel()
let profileImageView = UIImageView()
private let viewModel = ViewModel()
override func viewDidLoad() {
super.viewDidLoad()
viewModel.name.bind { [weak self] name in
self?.nameLabel.text = name
}
viewModel.meetingDate.bind { [weak self] date in
self?.dateLabel.text = date
}
viewModel.profileImage.bind { [weak self] image in
self?.profileImageView.image = image
}
}
}
viewModel
의 property
에 해당하는 Layout-components
들을 binding
시켰다.
우리는 viewModel
의 holdMeeting()
함수를 지금 코드에서 불러줄 필요는 없다. viewModel
이 init
되는 시점에 holdMeeting()
을 넣어놨기 때문이다. (예시를 위한 함수지 재사용은 못하는 holdMeeting()
함수이다...)
Box의 → bind 함수를 보자.
((T) -> Void)?
라는 closure
를 listener
라는 인자로 받는다.
{ name in
self.nameLabel.text = date
}
작동은 앞에서 설명한 것처럼 되겠죠?
이해가 안간다면
→ 바로 위의 코드블록인 nameLabel.bind.closure 가 bind의 입력 인자인 listener가 된다
→ 해당 Box class
의 listener
가 된다. (묶묶!)
→ 이 클로저에는 Box
의 초기값인 value
가 전달된다.
ViewController
에서 viewModel
에 해당하는 값들을 binding
해 줬으니 이제 뚝딱뚝딱 기능( e.g 서버 통신.. )들은 viewModel
에서 구현해 주면 된다.