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