iOS MVVM 不用RAC 如何实现双向绑定

在iOS开发中,MVVM(Model-View-ViewModel)是一种常用的架构模式,它有助于分离视图逻辑与业务逻辑,提高代码的可维护性和可测试性。本文将探讨如何在MVVM架构中实现双向绑定,而不依赖ReactiveCocoa(RAC)。我们将通过一个具体的例子来说明如何进行数据的双向绑定,以便用户在界面中修改数据后,这些修改能够即时反映到模型中。

问题描述

假设我们有一个简单的用户信息界面,用户可以在界面上输入他们的姓名和年龄信息。我们希望一旦用户在界面上输入信息,相关的模型数据也能够立即更新。而反过来,当模型数据发生变化时,界面也应该相应更新。

解决方案

我们将创建一个UserViewModel类,该类将作为MVC架构中的ViewModel,它将管理用户输入和用户数据模型之间的绑定。下面是实现的核心类结构:

类图

classDiagram
    class User {
        +String name
        +Integer age
    }
    class UserViewModel {
        +User user
        +String name
        +Integer age
        +updateUserData()
    }

实现代码

首先,我们定义一个User模型类,用于表示用户的数据:

class User {
    var name: String
    var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }
}

接下来,我们创建一个UserViewModel类来处理数据与视图之间的交互:

class UserViewModel {
    private var user: User {
        didSet {
            // Notify the view to update after user data changed
            self.onUserUpdate?()
        }
    }

    var name: String {
        get {
            return user.name
        }
        set {
            user.name = newValue
        }
    }

    var age: Int {
        get {
            return user.age
        }
        set {
            user.age = newValue
        }
    }

    var onUserUpdate: (() -> Void)?

    init(user: User) {
        self.user = user
    }
    
    func updateUserData() {
        // Custom logic if needed to process data when required
        // For now, simply notify the UI
        onUserUpdate?()
    }
}

视图部分

在UIViewController中,我们将实现输入框和标签,并通过ViewModel与视图建立绑定。

class UserViewController: UIViewController {
    private var viewModel: UserViewModel!

    @IBOutlet weak var nameTextField: UITextField!
    @IBOutlet weak var ageTextField: UITextField!
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // Initialize ViewModel with User
        let user = User(name: "John", age: 25)
        viewModel = UserViewModel(user: user)

        // Update UI with existing user data
        nameTextField.text = viewModel.name
        ageTextField.text = "\(viewModel.age)"

        // Binding text field changes to ViewModel
        nameTextField.addTarget(self, action: #selector(nameDidChange), for: .editingChanged)
        ageTextField.addTarget(self, action: #selector(ageDidChange), for: .editingChanged)

        // Notify when user data is updated
        viewModel.onUserUpdate = { [weak self] in
            self?.nameTextField.text = self?.viewModel.name
            self?.ageTextField.text = "\(self?.viewModel.age ?? 0)"
        }
    }

    @objc private func nameDidChange() {
        viewModel.name = nameTextField.text ?? ""
    }

    @objc private func ageDidChange() {
        if let ageText = ageTextField.text, let age = Int(ageText) {
            viewModel.age = age
        }
    }
}

序列图

sequenceDiagram
    participant User as User
    participant VM as UserViewModel
    participant View as UserViewController

    View->>VM: nameTextField text change
    VM->>User: update name
    User-->>VM: name updated
    VM->>View: update UI
    View-->>User: Show updated name

结论

通过上述实现,我们成功构建了一个简单的双向绑定机制,能够在用户输入数据时自动更新模型,同时在模型更改时更新视图。尽管没有使用 ReactiveCocoa,使用关闭和 KVO(键值观察)等方式也可轻松实现数据绑定。这样的设计使得代码结构清晰,逻辑分离,提升了可维护性。希望本文的示例能对您的MVVM实践有所帮助。