Простой пример архитектуры MVVM для загрузки элементов в UITableView и обработки информации.

В этом руководстве мы объясним, как создать простое приложение с использованием архитектуры MVVM. Для нашей демонстрации мы создадим приложение для загрузки списка сортов пива в UITableView.
Что такое MVVM
MVVM расшифровывается как Model View — View — Model и представляет собой архитектуру с простой логикой, использующей эти 3 разных объекта:
- Контроллер представления: должен выполнять единственную операцию с нашим пользовательским интерфейсом. Должен только получать и отображать информацию.
- Модель просмотра: ViewController будет отправлять информацию в модель просмотра. Модель представления будет обрабатывать их и выполнять всю бизнес-логику, а также предоставлять выходные данные обратно в контроллер представления.
- Модель: это просто простая модель, которая обычно содержит проанализированные данные из ответа службы. Это будет использоваться только из модели представления.
Установочный проект
Прежде чем объяснять архитектуру, нам нужно настроить наш новый проект. Назовем его WorldBeers. Вы можете найти проект здесь
Давайте посмотрим, как организовать ваши папки. В вашем проекте это выглядит так:

Сейчас мы сосредоточимся только на трех папках. Модели, ViewControllers и ViewModels. Другие папки предназначены только для создания нашего диспетчера API, который нам нужен для вызова службы, чтобы предоставить нам список пива (https://api.punkapi.com/v2/beers)
Если вы хотите узнать, как вызвать службу http (api), вы можете проверить мою статью
Теперь, когда мы настроили наш проект, давайте сосредоточимся на трех файлах, которые нам нужны.
Модель
import Foundation
class BeerResponse: Decodable {
let name: String?
let tagline: String?
let image_url: String?
let abv: Double?
let ibu: Double?
}
Модель должна быть чем-то простым, без какой-либо бизнес-логики внутри. В этом примере это информация, которая нам нужна о пиве. У нас есть название, слоган, изображение ur и abv, ibu, которые являются некоторыми значениями для пива.
ViewModel
Это самая важная часть нашей архитектуры. Этот файл должен иметь доступ к модели и содержать всю бизнес-логику, конкатенацию строк, числовые операции и т.д. Давайте посмотрим, как должен выглядеть код:
import Foundation
class BeersViewModel: BaseViewModel {
var beersLoaded: (([BeerResponse]?, Bool) -> Void)?
var beerList: [BeerResponse]?
override func callService() {
ApiManager.shared.retrieveBeers { [weak self] response in
self?.beerList = response
self?.handleResponse(response: response, success: true)
} fail: { [weak self] _ in
self?.handleResponse(response: nil, success: false)
}
}
private func handleResponse(response: [BeerResponse]?, success: Bool) {
if let beersLoaded = self.beersLoaded {
beersLoaded(response, success)
}
}
func numberOrRows() -> Int {
return self.beerList?.count ?? 0
}
func getBeer(index: Int) -> BeerResponse? {
return self.beerList?[index]
}
}
Как вы можете видеть в этой модели, мы создали функцию, которая нам нужна для вызова API пива, и блок для возврата ответа обратно в ViewController. Как видите, ViewModel хранит модель (ViewController не должен знать об этом). Помимо вызова API мы создали еще две функции, которые нужны viewController. Один для количества пива, а другой для получения информации о пиве.
Если вы заметили, что здесь нет никаких операций с пользовательским интерфейсом. ViewController должен справиться с этим. В этом классе вы также можете добавить другие операции, которые вы делаете с моделью (например, изменить имя, слоган и т. д.).
Пришло время увидеть ViewController и соединить все элементы вместе:
ViewController
В контроллере представления нам нужен UITableView с ячейкой для отображения изображения, названия и некоторой другой информации о пиве. Контроллер представления должен быть очень «глупым», поскольку он не должен получать и отображать информацию.
import UIKit
class HomeViewController: BaseViewController {
private var beersViewModel = BeersViewModel()
@IBOutlet weak var searchBar: UISearchBar!
@IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
showLoader()
self.beersViewModel.beersLoaded = { [weak self] (_, success) in
self?.hideLoader()
if success {
self?.tableView.reloadData()
} else {
}
}
}
private func setupUI() {
self.tableView.registerCell(type: BeerTableViewCell.self)
self.tableView.dataSource = self
self.tableView.delegate = self
}
}
extension HomeViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
}
}
extension HomeViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.beersViewModel.numberOrRows()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueCell(withType: BeerTableViewCell.self, for: indexPath) as? BeerTableViewCell, let beerData = self.beersViewModel.getBeer(index: indexPath.row) else {
return UITableViewCell()
}
cell.setBeerData(beerData: beerData)
return cell
}
}
Как видите, нам нужно иметь доступ только к файлу ViewModel и выполнять операции с пользовательским интерфейсом. ViewController ничего не знает о диспетчере API и о том, как вызывать службу, поэтому он просто «слушает» блок пиваLoaded ViewModel.
Контроллер представления не имеет доступа к модели, поэтому, если вы видите функции UITableViewDelegate, он спросит, что ему нужно от ViewModel. В этих проектах мы просили указать количество сортов пива (чтобы мы знали, сколько позиций отображать) и информацию об одном пиве, отображаемом в каждой строке.
Результат
Наконец, давайте посмотрим, как будет выглядеть наше приложение:

Если вам нужно добавить тень и углы в UITableViewCell, вы можете проверить эту статью
Чтобы поддержать меня, если вы хотите присоединиться к среде, вы можете сделать это по моей реферальной ссылке