Model-View-ViewModel (MVVM) в iOS разработке
Разберем подробнее шаблон проектирования на языке Swift - Model-View-ViewModel.
Как уже отмечалось ранее MVVM пришел на замену MVC для того чтобы облегчить процесс разработки сложных приложений, позволить отделить логические части проекта на разные объекты, отделить бизнес-логику от пользовательского интерфейса (View) и обложить все это качественным тестированием.
Вот что говорит Википедия о MVVM.
MVVM состоит из модели (Model), представления (View) или пользовательского интерфейса, и ViewModel - сущности, которая изолирована от пользовательского интерфейса, которая содержит всю бизнес-логику приложения, которая общается напрямую с моделью данных, и которая как-то должна влиять на View. Model в свою очередь, должна быть максимально простой и прозрачной. Она никак не связывается с представлением, а лишь делает свою работу по обработке данных (загрузка, созранение, изменение). Все входные данные приходят из ViewModel.
Ключевым моментом в создании MVVM является связывание (binding) пользовательского интерфейса (View) с логикой (ViewModel).
Связывание пользовательского интерфейса — это мост между View и ViewModel. Он может быть однонаправленный или двунаправленный и позволяет этим двум сущностям взаимодействовать совершенно прозрачным образом.
К сожалению, в iOS нет собственного способа добиться этого, поэтому вы должны использовать сторонние библиотеки/фреймворки или написать связывание самостоятельно.
Существуют разные способы реализовать привязку пользовательского интерфейса к коду Swift:
- Delegation
- Closures
- RxSwift (ReactiveCocoa)
При помощи Делегирования (Delegation)
Простое объяснения паттерна "Делегирование".
Если вы хотите избежать импорта и изучения новых фреймворков, вы можете использовать делегирование в качестве альтернативного способа для связывания View и ViewModel. К сожалению, используя этот подход, вы теряете силу прозрачной привязки, поскольку вам приходится делать привязку вручную. Эта версия MVVM становится очень похожей на MVP. Стратегия этого подхода заключается в сохранении ссылки на делегата, реализованного представлением, внутри вашей модели представления. Таким образом, ViewModel может обновлять представление, не имея ссылок на объекты UIKit.
// Часть MVVM, отвечающая за реализацию View
class ViewController: UIViewController, ViewModelDelegate {
@IBOutlet private weak var label: UILabel?
private let viewModel: ViewModel
init(viewModel: ViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
viewModel.delegate = self
}
func lableDidChange(text: String) {
label?.text = text
}
}
protocol ViewModelDelegate: class {
func labelDidChange(text: String)
}
// Часть MVVM, отвечающая за реализацию ViewModel
class ViewModel {
private var labelText: String {
didSet {
delegate?.labelDidChange(text: labelText)
}
}
weak var delegate: ViewModelDelegate? {
didSet {
delegate?.labelDidChange(text: labelText)
}
}
init() {
labelText = "Hello World!"
}
}
При помощи Замыканий (Closures)
Это очень похоже на делегирование, но вместо делегата вы используете замыкания. Замыкания — это свойства ViewModel, и View использует их для обновления пользовательского интерфейса. Вы должны обратить внимание на то, чтобы избежать зацикливаний в замыканиях с использованием [weak self].
class ViewController: UIViewController, ViewModelDelegate {
@IBOutlet private weak var userLabel: UILabel?
private let viewModel: ViewModel
init(viewModel: ViewModel) {
self.viewModel = viewModel
viewModel.delegate = self
}
func userNameDidChange(text: String) {
userLabel?.text = text
}
}
protocol ViewModelDelegate: class {
func userNameDidChange(text: String)
}
class ViewModel {
private var userName: String {
didSet {
delegate?.userNameDidChange(text: userName)
}
}
weak var delegate: ViewModelDelegate? {
didSet {
delegate?.userNameDidChange(text: userName)
}
}
init() {
userName = "Hello World!"
}
}
При помощи реактивного программирования и библиотеки RxSwift (ReactiveCocoa)
RxSwift — это Swift-версия семейства фреймворков ReactiveX. Освоив его, вы сможете легко переключаться на любой другой язык программирования где используется реактивное программирование - RxJava, RxJavascript и т. д.
Этот фреймворк позволяет вам писать код в стиле функционального реактивного программирования (FRP), а благодаря внутренней библиотеке RxCocoa вы можете легко связать View и ViewModel:
class ViewController: UIViewController {
@IBOutlet private weak var userLabel: UILabel!
private let viewModel: ViewModel
private let disposeBag: DisposeBag
private func bindToViewModel() {
viewModel.myProperty
.drive(userLabel.rx.text)
.disposed(by: disposeBag)
}
}
Существуют еще и другие способы связывания (binding) View и ViewModel, но мы рассмотрели основные. В своем ролике на YouTube я показывал, как можно реализовать связывание View и ViewModel с помощью дженериков и замыканий: