沒有反應式程式設計的 MVVM

我將從一個非常簡短的解釋開始,在 iOS 應用程式中使用 Model-View-ViewModel(MVVM) 設計模式是什麼以及為什麼。當 iOS 首次出現時,Apple 建議使用 MVC(模型 - 檢視 - 控制器)作為設計模式。他們在所有示例中都展示了它,並且所有第一批開發人員都樂於使用它,因為它很好地分離了業務邏輯和使用者介面之間的關注點。隨著應用程式變得越來越大,越來越複雜,出現了一個新的問題 - 大規模檢視控制器(MVC)。因為所有業務邏輯都是在 ViewController 中新增的,所以隨著時間的推移它們通常會變得太大而複雜。為了避免 MVC 問題,一種新的設計模式被引入 iOS 世界 - 模型 - 檢視 - 檢視模型(MVVM)模式。

StackOverflow 文件

上圖顯示了 MVVM 的外觀。你有一個標準的 ViewController + View(在 storyboard,XIB 或 Code 中),它充當 MVVM 的 View(在後面的文字中 - View 將引用 MVVM 的 View)。檢視引用了我們的業務邏輯所在的 ViewModel。重要的是要注意 ViewModel 對 View 沒有任何瞭解,也從不對檢視有任何引用。ViewModel 具有對 Model 的引用。
這對 MVVM 的理論部分來說已經足夠了。有關它的更多資訊可以在這裡閱讀。

MVVM主要問題之一是當 ViewModel 沒有任何引用並且甚至不知道有關 View 的任何內容時,如何通過 ViewModel 更新 View。

這個例子的主要部分是展示如何使用 MVVM(更確切地說,如何繫結 ViewModel 和 View)而不需要任何反應式程式設計(ReactiveCocoa,ReactiveSwift 或 RxSwif)。就像一個註釋:如果你想使用 Reactive 程式設計,甚至更好,因為使用它可以很容易地完成 MVVM 繫結。但是這個例子是關於如何在沒有 Reactive 程式設計的情況下使用 MVVM。

讓我們建立一個簡單的例子來演示如何使用 MVVM。

我們的 MVVMExampleViewController 是一個簡單的 ViewController,帶有標籤和按鈕。按下按鈕時,標籤文字應設定為 Hello。由於決定如何處理使用者使用者互動是業務邏輯的一部分,因此 ViewModel 必須決定在使用者​​按下按鈕時要執行的操作。MVVM 的 View 不應該做任何業務邏輯。

class MVVMExampleViewController: UIViewController {
    
    @IBOutlet weak var helloLabel: UILabel!
    
    var viewModel: MVVMExampleViewModel?
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction func sayHelloButtonPressed(_ sender: UIButton) {
        viewModel?.userTriggeredSayHelloButton()
    }
}

MVVMExampleViewModel 是一個簡單的 ViewModel。

class MVVMExampleViewModel {
    
    func userTriggeredSayHelloButton() {
        // How to update View's label when there is no reference to the View??
    }
}

你可能想知道如何在 View 中設定 ViewModel 的引用。我通常在初始化 ViewController 時或在顯示之前執行此操作。對於這個簡單的例子,我會在 AppDelegate 中做這樣的事情:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        if let rootVC = window?.rootViewController as? MVVMExampleViewController {
            let viewModel = MVVMExampleViewModel()
            rootVC.viewModel = viewModel
        }
        
        return true

現在真正的問題是:如何在不向 ViewModel 提供 View 的情況下從 ViewModel 更新 View? (請記住,我們不會使用任何 Reactive Programming iOS 庫)

你可以考慮使用 KVO,但這會讓事情變得太複雜。一些聰明的人已經考慮過這個問題,並提出了邦德庫 。該庫可能看起來很複雜,起初有點難以理解,所以我只需要它的一小部分,使我們的 MVVM 完全正常執行。

讓我們來介紹 Dynamic 類,它是我們簡單但功能齊全的 MVVM 模式的核心。

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

Dynamic 類使用 Generics 和 Closures 將我們的 ViewModel 繫結到 View。我不會詳細介紹這個類,我們可以在評論中做到這一點(使這個例子縮短)。現在讓我們更新我們的 MVVMExampleViewControllerMVVMExampleViewModel 來使用這些類。

我們更新了 MVVMExampleViewController

class MVVMExampleViewController: UIViewController {
    
    @IBOutlet weak var helloLabel: UILabel!
    
    var viewModel: MVVMExampleViewModel?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        bindViewModel()
    }
    
    func bindViewModel() {
        if let viewModel = viewModel {
            viewModel.helloText.bind({ (helloText) in
                DispatchQueue.main.async {
                    // When value of the helloText Dynamic variable
                    // is set or changed in the ViewModel, this code will
                    // be executed
                    self.helloLabel.text = helloText
                }
            })
        }
    }
    
    @IBAction func sayHelloButtonPressed(_ sender: UIButton) {
        viewModel?.userTriggeredSayHelloButton()
    }
}

更新了 MVVMExampleViewModel

    class MVVMExampleViewModel {
    
    // we have to initialize the Dynamic var with the
    // data type we want
    var helloText = Dynamic("")
    
    func userTriggeredSayHelloButton() {
        // Setting the value of the Dynamic variable
        // will trigger the closure we defined in the View
        helloText.value = "Hello"
    }
}

這就對了。你的 ViewModel 現在能夠更新 View 而不需要參考 View

這是一個非常簡單的例子,但我想你已經知道這有多強大了。我不會詳細介紹 MVVM 的優點,但是一旦從 MVC 切換到 MVVM,你就不會再回頭了。試試吧,親眼看看吧。