Как передавать данные между view controllers в UIKit?
Данные вперёд передают через свойства или prepare(for:); назад — через делегата или closure. Для слабосвязанных компонентов подходит NotificationCenter или shared Store.
Способы передачи данных между UIViewController
UIKit предоставляет несколько механизмов для обмена данными между экранами. Выбор зависит от направления потока, связи между контроллерами и архитектурного подхода.
1. Прямая передача через свойства (forward)
Самый простой вариант для передачи данных вперёд — установить свойство на destination-контроллере до push или present:
// Отправитель
func showDetail(for item: Item) {
let vc = DetailViewController()
vc.item = item // передаём данные напрямую
navigationController?.pushViewController(vc, animated: true)
}
// Получатель
class DetailViewController: UIViewController {
var item: Item?
override func viewDidLoad() {
super.viewDidLoad()
titleLabel.text = item?.title
}
}
2. Prepare(for:sender:) при Storyboard segue
При использовании Storyboard метод prepare(for:sender:) вызывается перед переходом — здесь удобно настроить destination:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail",
let vc = segue.destination as? DetailViewController,
let indexPath = tableView.indexPathForSelectedRow {
vc.item = items[indexPath.row]
}
}
3. Делегаты (обратная передача)
Для передачи данных обратно (например, результат редактирования) используется паттерн делегата:
protocol EditViewControllerDelegate: AnyObject {
func editViewController(_ vc: EditViewController, didSave item: Item)
}
class EditViewController: UIViewController {
weak var delegate: EditViewControllerDelegate?
@IBAction func saveButtonTapped(_ sender: Any) {
let updated = Item(title: titleField.text ?? "")
delegate?.editViewController(self, didSave: updated)
dismiss(animated: true)
}
}
// Родительский контроллер
class ListViewController: UIViewController, EditViewControllerDelegate {
func editViewController(_ vc: EditViewController, didSave item: Item) {
items.append(item)
tableView.reloadData()
}
func openEditor() {
let vc = EditViewController()
vc.delegate = self
present(vc, animated: true)
}
}
4. Closures / callbacks
Лёгкая альтернатива делегату для одноразового обратного вызова:
class ColorPickerViewController: UIViewController {
var onColorSelected: ((UIColor) -> Void)?
func selectColor(_ color: UIColor) {
onColorSelected?(color)
dismiss(animated: true)
}
}
// Вызывающая сторона
func openPicker() {
let vc = ColorPickerViewController()
vc.onColorSelected = { [weak self] color in
self?.view.backgroundColor = color
}
present(vc, animated: true)
}
5. NotificationCenter
Для широковещательной передачи данных между слабосвязанными компонентами:
extension Notification.Name {
static let cartUpdated = Notification.Name("cartUpdated")
}
// Отправитель
NotificationCenter.default.post(
name: .cartUpdated,
object: nil,
userInfo: ["count": cartItems.count]
)
// Получатель
NotificationCenter.default.addObserver(
self,
selector: #selector(handleCartUpdate(_:)),
name: .cartUpdated,
object: nil
)
@objc func handleCartUpdate(_ notification: Notification) {
let count = notification.userInfo?["count"] as? Int ?? 0
badgeLabel.text = "\(count)"
}
6. Shared state / dependency injection
В более сложных архитектурах (MVC+Store, MVVM, TCA) общее состояние передаётся через ViewModel или Store, который инжектируется в контроллер при создании. Это позволяет избежать прямой зависимости между экранами:
class CartStore: ObservableObject {
var items: [CartItem] = []
}
class CheckoutViewController: UIViewController {
let store: CartStore
init(store: CartStore) { self.store = store; super.init(nibName: nil, bundle: nil) }
required init?(coder: NSCoder) { fatalError() }
}
Подводные камни
- Прямое обращение к свойствам destination-контроллера ДО вызова
viewDidLoadбезопасно только если свойство не завязано наIBOutlet— аутлеты ещё не загружены. - Делегат без
weakиAnyObjectconstraint вызовет retain cycle между родителем и дочерним контроллером. - NotificationCenter-наблюдатели не удалённые в
deinitилиviewWillDisappearмогут вызываться на мёртвых объектах и порождать crash. - Closure с захватом
selfбез[weak self]вonColorSelectedудержит контроллер в памяти после dismiss. - Segue identifier как строка — источник опечаток. Заведите enum или константу для всех идентификаторов.
- Передача данных через UserDefaults или синглтон создаёт неявные зависимости, которые сложно тестировать — используйте только для пользовательских настроек.
- При передаче mutable reference-типов несколько контроллеров могут неожиданно мутировать один и тот же объект — предпочитайте value types (struct) или передавайте копии.
Common mistakes
- Сводить «передавать данные между view controllers в UIKit» к синтаксису и не объяснять Auto Layout.
- Игнорировать жизненный цикл, основной поток или момент освобождения ресурсов в сценарии uikit-22.
- Выбирать API по привычке, не проверяя состояние, ошибки, доступность и платформенные ограничения.
What the interviewer is testing
- Формулирует точную модель для «передавать данные между view controllers в UIKit» и подтверждает ее корректным примером.
- Умеет связать ответ с responder chain, тестированием и отладкой на устройстве.
- Называет ограничения подхода uikit-22, включая производительность, память и сопровождение.