MVVM + KVO 이해하기 쉬운 예제

MVVM + RxSwift 코드를 Rx 대신 KVO(Key-Value Observing) 로 변경해 보았다.

// ViewController
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        if let v: View = UIView.loadFromNib() {
            v.add(self.view)
        }
    }
}
// View
class View: UIView {
    @IBOutlet var label: UILabel!
    
    private let viewModel = ViewModel()
    private var ob: NSKeyValueObservation!
    
    override init(frame: CGRect) {
        super.init(frame: frame)
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        
        // ViewModel의 output.label 값이 변경되는 것을 감시하여 변경되면 View의 label을 업데이트
        ob = viewModel.output.observe(\.label) { [weak self] obj, _ in
            guard let self = self else { return }
            self.label.text = obj.label
        }
        
        viewModel.loadLabel()
    }
    
    func add(_ superView: UIView) {
        superView.addSubview(self)
        self.translatesAutoresizingMaskIntoConstraints = false
        
        var constraints = [NSLayoutConstraint]()
        constraints.append(superView.topAnchor.constraint(equalTo: self.topAnchor))
        constraints.append(superView.leadingAnchor.constraint(equalTo: self.leadingAnchor))
        constraints.append(superView.trailingAnchor.constraint(equalTo: self.trailingAnchor))
        constraints.append(superView.bottomAnchor.constraint(equalTo: self.bottomAnchor))
        NSLayoutConstraint.activate(constraints)
    }
}

extension UIView {
    class func loadFromNib<T>() -> T? {
        let identifier = String(describing: T.self)
        let view = Bundle.main.loadNibNamed(identifier, owner: self, options: nil)?.first
        return view as? T
    }
}
// Model
struct Model {
    var label: String = "model"
}
// ViewModel
class ViewModel {
    class Output: NSObject { //View에서 observe 하려면 NSObject 상속해야 함
        @objc dynamic var label: String = ""
    }
    
    var output = Output()
    private var m = Model()
    
    init() {
    }
    
    func changed() {
        DispatchQueue.main.async {
            self.output.label = self.m.label
        }
    }
    
    func loadLabel() {
        output.label = "init"
        
        DispatchQueue.global().asyncAfter(deadline: .now()+2) {
            self.m.label = "value from server or app db"
            self.changed()
        }
    }
}

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다