Поиск…


MVVM без реактивного программирования

Я начну с очень короткого объяснения, что и зачем использовать шаблон дизайна Model-View-ViewModel (MVVM) в приложениях iOS. Когда iOS впервые появилась, Apple предложила использовать MVC (Model-View-Controller) в качестве шаблона проектирования. Они показали это во всех своих примерах, и все первые разработчики были счастливы использовать его, потому что это прекрасно разделяло проблемы между бизнес-логикой и пользовательским интерфейсом. По мере того, как приложения становились все больше и сложнее, появилась новая проблема, получившая название Massive View Controllers (MVC). Поскольку вся бизнес-логика была добавлена ​​в ViewController, со временем они стали слишком большими и сложными. Чтобы избежать проблемы MVC, в мир iOS - модель-View-ViewModel (MVVM) был введен новый шаблон дизайна.

Диаграмма МВВМ

На приведенной выше диаграмме показано, как выглядит MVVM. У вас есть стандартный ViewController + View (в раскадровке, XIB или Code), который действует как представление MVVM (в более позднем тексте - View будет ссылаться на представление MVVM). В представлении есть ссылка на ViewModel, где наша бизнес-логика. Важно заметить, что ViewModel ничего не знает о представлении и никогда не ссылается на представление. ViewModel имеет ссылку на модель.
Этого достаточно для теоретической части MVVM. Подробнее об этом можно прочитать здесь .

Одна из основных проблем с MVVM заключается в том, как обновить View через ViewModel, когда ViewModel не имеет ссылок и даже ничего не знает о представлении.

Основная часть этого примера - показать, как использовать MVVM (точнее, как связать ViewModel и View) без какого-либо реактивного программирования (ReactiveCocoa, ReactiveSwift или RxSwif). Также как примечание: если вы хотите использовать реактивное программирование, еще лучше, так как привязки MVVM выполняются очень просто с его помощью. Но этот пример - о том, как использовать MVVM без реактивного программирования.

Давайте создадим простой пример, чтобы продемонстрировать, как использовать MVVM.

Наш MVVMExampleViewController - это простой ViewController с меткой и кнопкой. При нажатии кнопки текст ярлыка должен быть установлен на «Hello». Поскольку решение о взаимодействии с пользователем является частью бизнес-логики, ViewModel должен будет решить, что делать, когда пользователь нажимает кнопку. Представление MVVM не должно вести бизнес-логику.

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??
    }
}

Вы можете задаться вопросом, как установить ссылку 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

Реальный вопрос: как обновить View из ViewModel без указания ссылки на ViewModel? (Помните, мы не будем использовать ни одну из библиотек iOS с реактивным программированием)

Вы могли бы подумать об использовании KVO, но это слишком усложняло бы ситуацию. Некоторые умные люди задумались над этой проблемой и придумали библиотеку Bond . Вначале библиотека может показаться сложной и немного сложнее понять, поэтому я просто возьму одну ее часть и сделаю наш 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 с нашим представлением. Я не буду вдаваться в подробности об этом классе, мы можем сделать это в комментариях (чтобы сделать этот пример короче). Давайте теперь обновим наш MVVMExampleViewController и MVVMExampleViewModel для использования этих классов.

Наш обновленный 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, вы не вернетесь. Просто попробуйте и убедитесь сами.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow