Как вы применяли паттерн MVVM в iOS-разработке?

«Как вы применяли паттерн MVVM в iOS-разработке?» — вопрос из категории Архитектура, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

MVVM (Model-View-ViewModel) — это архитектурный паттерн, который разделяет ответственность, упрощает тестирование и уменьшает связность кода.

Структура компонентов:

  • Model: Представляет данные и бизнес-логику (например, структуры User, Post).
  • View: Отвечает за отображение UI и пользовательские взаимодействия (UIViewController, UIView, SwiftUI View). View содержит ссылку на ViewModel, но не наоборот.
  • ViewModel: Промежуточный слой, который преобразует данные Model в удобный для View формат. Он не должен импортировать UIKit/SwiftUI.

Пример реализации с биндингом через замыкания:

// Model
struct User {
    let name: String
    let email: String
}

// ViewModel
class UserViewModel {
    private let user: User

    // Published свойства для биндинга
    var userName: String { user.name }
    var userEmail: String { user.email }

    // Замыкание для обновления View
    var onUpdate: (() -> Void)?

    init(user: User) {
        self.user = user
    }

    func fetchUserData() {
        // Сетевой запрос или работа с БД
        // После получения данных вызываем onUpdate?()
    }
}

// View (UIViewController)
class UserViewController: UIViewController {
    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var emailLabel: UILabel!

    var viewModel: UserViewModel! {
        didSet {
            viewModel.onUpdate = { [weak self] in
                self?.updateUI()
            }
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        updateUI()
        viewModel.fetchUserData()
    }

    private func updateUI() {
        nameLabel.text = viewModel.userName
        emailLabel.text = viewModel.userEmail
    }
}

Преимущества:

  • Тестируемость: ViewModel легко тестируется юнит-тестами без UI.
  • Разделение ответственности: View занимается только отрисовкой, ViewModel — подготовкой данных.
  • Гибкость: Для биндинга можно использовать Combine, RxSwift или простые замыкания.

Важно: ViewModel не должна хранить сильные ссылки на View, чтобы избежать циклов удержания.