@ObservedObject — это property wrapper в SwiftUI, который позволяет View наблюдать за изменениями в ObservableObject, созданном в другом месте иерархии. В отличие от @StateObject, @ObservedObject не создаёт объект — он только подписывается на его publisher objectWillChange и перерисовывает View при обновлении published-свойств. Это делает @ObservedObject правильным выбором для дочерних View, которые получают данные от родителя через инициализатор. По данным статьи Paul Hudson — Hacking with Swift (2025), типичная архитектура SwiftUI-приложения строится так: корневой View использует @StateObject для создания view model, а все дочерние View получают её через @ObservedObject, что обеспечивает единый источник истины без дублирования данных.
Главное
@ObservedObject — это property wrapper, который подписывает View на изменения ObservableObject. Когда объект, помеченный @ObservedObject, изменяет любое из своих свойств, объявленных с @Published, SwiftUI автоматически перерисовывает View. @ObservedObject не создаёт объект — он лишь устанавливает связь между уже существующим экземпляром ObservableObject и View, которое должно реагировать на его изменения.
Ключевое отличие @ObservedObject от @StateObject — владение. @ObservedObject предполагает, что объект создан и хранится где-то выше по иерархии View. Дочернее View получает ссылку на этот объект через инициализатор и просто наблюдает за ним. Если дочернее View будет пересоздано, оно получит ту же самую ссылку от родителя — данные не потеряются.
По данным Apple Developer Documentation — SwiftUI (2025), @ObservedObject доступен начиная с iOS 13, что делает его единственным вариантом для наблюдения за ObservableObject в проектах, поддерживающих старые версии iOS. В iOS 14+ предпочтительнее @StateObject для создания объектов, но @ObservedObject остаётся актуальным для передачи существующих объектов.
Механизм @ObservedObject основан на протоколе ObservableObject из Combine framework. Каждый класс, соответствующий ObservableObject, автоматически получает publisher objectWillChange, который отправляет сигнал перед изменением любого @Published свойства. SwiftUI подписывается на этот publisher через @ObservedObject и при получении сигнала помечает View как требующую перерисовки.
class TaskViewModel: ObservableObject {
@Published var tasks: [Task] = []
@Published var isLoading = false
func loadTasks() async {
isLoading = true
// fetch data
isLoading = false
}
}
struct TaskListView: View {
@ObservedObject var viewModel: TaskViewModel
var body: some View {
List(viewModel.tasks) { task in
Text(task.title)
}
.task { await viewModel.loadTasks() }
}
}
Когда родительский View передаёт viewModel в TaskListView через инициализатор, SwiftUI создаёт связь между объектом и View. При изменении массива tasks или флага isLoading SwiftUI перерисовывает TaskListView. Объект при этом остаётся неизменным — он хранится в родительском View через @StateObject.
Разница между @ObservedObject и @StateObject — это разница между наблюдателем и владельцем. @StateObject создаёт объект и управляет его жизненным циклом. @ObservedObject только наблюдает за объектом, который был создан и хранится в другом месте. Выбор между ними определяется ответственностью View за данные.
| Сценарий | Рекомендация | Причина |
|---|---|---|
| View создаёт данные | @StateObject | View владеет объектом и отвечает за его жизненный цикл |
| View получает данные | @ObservedObject | View только наблюдает, объект живёт в родителе |
| Поддержка iOS 13 | @ObservedObject | @StateObject недоступен, используйте @ObservedObject с ручным управлением |
| Переиспользуемый компонент | @ObservedObject | Компонент не должен создавать данные — он получает их извне |
Главное правило: если View создаёт объект — @StateObject. Если View принимает объект — @ObservedObject. Нарушение этого правила в сторону @ObservedObject для создания объекта приводит к потере данных при перестроении View. Нарушение в сторону @StateObject для принятия объекта создаёт дублирующийся экземпляр, независимый от родительского.
Типичный сценарий использования @ObservedObject — список задач, где корневой View создаёт view model, а ячейка списка получает её через @ObservedObject. Каждая ячейка может вызывать методы view model, а изменения автоматически отображаются во всему списке, поскольку все ячейки наблюдают за одним и тем же объектом.
struct TaskRow: View {
@ObservedObject var viewModel: TaskViewModel
let task: Task
var body: some View {
HStack {
Text(task.title)
Spacer()
Button("Done") {
viewModel.completeTask(task)
}
}
}
}
struct TaskListContainer: View {
@StateObject var viewModel = TaskViewModel()
var body: some View {
List(viewModel.tasks) { task in
TaskRow(viewModel: viewModel, task: task)
}
}
}
В этом примере TaskListContainer создаёт viewModel через @StateObject, а каждая TaskRow получает её через @ObservedObject. Когда пользователь нажимает "Done" в любой строке, viewModel.completeTask изменяет published-свойство, и все View, наблюдающие за этим объектом, обновляются автоматически.
Самая распространённая ошибка — использование @ObservedObject для создания объекта внутри View. Когда View перестраивается (например, при изменении state), SwiftUI создаёт новый экземпляр ObservableObject, что приводит к потере всех накопленных данных. Эта ошибка особенно болезненна в NavigationStack, где пользователь может заполнить форму и потерять данные при возврате назад.
// ❌ Data loss: @ObservedObject does not retain object
struct FormView: View {
@ObservedObject var formVM = FormViewModel()
// New formVM created on each View rebuild!
}
// ✅ Correct: @StateObject retains the object
struct FormView: View {
@StateObject var formVM = FormViewModel()
// Object created once per View lifetime
}
Если дочернее View объявляет тот же ObservableObject через @StateObject, оно создаёт независимую копию. Изменения в родительском объекте не будут видны в дочернем, и наоборот. Всегда используй @ObservedObject для дочерних View, которые получают объект извне.
В современной SwiftUI существует несколько альтернатив @ObservedObject, каждая со своими преимуществами. Выбор зависит от архитектуры приложения, версии iOS и конкретного сценария использования.
Выбор между @ObservedObject и @EnvironmentObject — это вопрос стиля и архитектуры. @ObservedObject явно показывает зависимости View через инициализатор, что делает код более предсказуемым. @EnvironmentObject удобен для глубоких иерархий, но скрывает зависимости, что может затруднить отладку.
Часто задаваемые вопросы
Да, если объект создаётся и хранится вне SwiftUI — например, в AppDelegate или синглтоне. В этом случае @ObservedObject просто подписывается на изменения существующего объекта. Однако для объектов, создаваемых внутри SwiftUI-иерархии, всегда нужен @StateObject где-то выше.
Наиболее вероятная причина — свойство изменяется не через @Published или изменяется не сам объект, а его внутренняя структура без вызова objectWillChange. Для коллекций используй присваивание новой копии: array.append() недостаточно — нужно присвоить сам массив заново через array = array + [element].
@ObservedObject сам по себе не создаёт значительной нагрузки. Проблемы возникают при частых изменениях @Published свойств — каждое изменение триггерит перерисовку всех наблюдающих View. Для оптимизации используй EquatableView, уменьшай количество published-свойств и избегай лишних обновлений.
@ObservedObject наблюдает за целым классом ObservableObject и перерисовывает View при любом изменении его published-свойств. @Binding создаёт двустороннюю связь с конкретным значением (String, Int, Bool) и позволяет читать и записывать его. @Binding легче и не требует ObservableObject.
Да, это стандартный паттерн. @Published внутри ObservableObject автоматически интегрируется с @ObservedObject. Каждое @Published свойство добавляет наблюдателя к objectWillChange publisher. При изменении любого из них все View с @ObservedObject для этого объекта перерисовываются.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также