@StateObject: що це, створення та управління ObservableObject

Автор: IT Sectr Опубліковано: 2026-06-26 Час читання: 9 хв

@StateObject — це property wrapper у SwiftUI, який створює та володіє екземпляром ObservableObject протягом усього життєвого циклу View. Коли View вперше з'являється на екрані, @StateObject ініціалізує об'єкт і зберігає його доти, доки View не буде видалено з пам'яті. Це гарантує, що дані не скинуться при перебудові інтерфейсу — наприклад, при зміні теми або оновленні батьківського View. За даними Apple Developer Documentation (2025), @StateObject має використовуватися як основне джерело істини (source of truth) для ObservableObject в ієрархії SwiftUI, тоді як дочірні View отримують уже створений об'єкт через @ObservedObject або @EnvironmentObject.

Головне

  • @StateObject — property wrapper для створення та володіння ObservableObject всередині View.
  • Одноразове створення — об'єкт ініціалізується один раз за час життя View і не перестворюється при перебудовах.
  • Джерело істини — @StateObject є джерелом істини в ієрархії, на відміну від @ObservedObject.
  • Життєвий цикл — об'єкт живе, поки View існує в пам'яті, і знищується разом із нею.
  • Ініціалізація — @StateObject потребує початкового значення при створенні, зазвичай через init з параметрами.

Що таке @StateObject у SwiftUI

@StateObject — це property wrapper, представлений в iOS 14, який дозволяє View створювати та володіти екземпляром класу, що відповідає протоколу ObservableObject. На відміну від @State, який працює з value-типами (структурами), @StateObject призначений для reference-типів — класів, які можуть сповіщати SwiftUI про зміни своїх властивостей.

Коли View використовує @StateObject var viewModel: MyViewModel, SwiftUI автоматично створює екземпляр MyViewModel при першому відображенні View і зберігає його в спеціальному сховищі фреймворку. При кожному оновленні View (наприклад, при зміні батьківського стану) SwiftUI не перестворює об'єкт — він використовує існуючий екземпляр доти, доки View не буде видалено з ієрархії.

За даними Apple WWDC Session 10137 (2024), @StateObject вирішує проблему втрати даних, яка існувала в iOS 13 при перебудові View, коли розробникам доводилося створювати ObservableObject у батьківському View і передавати його через ініціалізатор. Це призводило до дублювання коду та ризику випадкового перестворення об'єкта.

swift
import SwiftUI

class CounterViewModel: ObservableObject {
    @Published var count: Int = 0
    
    func increment() {
        count += 1
    }
}

struct CounterView: View {
    @StateObject var viewModel = CounterViewModel()
    
    var body: some View {
        VStack {
            Text("Count: \(viewModel.count)")
            Button("Increment", action: viewModel.increment)
        }
    }
}

Як працює @StateObject

Механізм @StateObject ґрунтується на інтеграції SwiftUI з Combine framework. Коли ObservableObject позначає свої властивості атрибутом @Published, SwiftUI автоматично підписується на зміни через publisher, вбудований у протокол ObservableObject. При зміні опублікованої властивості об'єкт надсилає сигнал через objectWillChange publisher, що тригерить перемалювання всіх View, які спостерігають за цим об'єктом.

SwiftUI зберігає екземпляр ObservableObject у спеціальному сховищі, прив'язаному до конкретного екземпляра View. Це сховище створюється один раз при першому рендерингу та існує до знищення View. Саме тому @StateObject гарантує стабільність посилання — SwiftUI керує пам'яттю автоматично, не покладаючись на ініціалізатор View.

За даними objc.io — Thinking in SwiftUI (2025), внутрішня реалізація @StateObject використовує механізм, подібний до @State, але для reference-типів: SwiftUI створює boxing-обгортку навколо об'єкта та керує її життєвим циклом через власний алокатор, оптимізований для частих перебудов ієрархії View.

Життєвий цикл @StateObject

  • Створення — при першій появі View на екрані SwiftUI викликає ініціалізатор об'єкта та зберігає посилання.
  • Перебудова — при оновленні батьківського View об'єкт не перестворюється, використовується існуючий екземпляр.
  • Знищення — коли View залишає екран і видаляється з ієрархії, SwiftUI викликає deinit об'єкта.

@StateObject vs @ObservedObject: ключові відмінності

Головна відмінність @StateObject від @ObservedObject полягає в тому, хто володіє об'єктом. @StateObject створює та зберігає об'єкт — він є власником. @ObservedObject лише спостерігає за об'єктом, який був створений десь в іншому місці та переданий через ініціалізатор або властивість.

Характеристика@StateObject@ObservedObject
ВолодінняСтворює та володіє об'єктомЛише спостерігає
ІніціалізаціяВсередині View через init/defaultЗзовні, передається через параметр
Життєвий циклПрив'язаний до життєвого циклу ViewНе контролюється View
ПерестворенняНе перестворюється при оновленніМоже бути замінений ззовні
Версія iOSiOS 14+iOS 13+

Правило просте: якщо View створює ObservableObject — використовуй @StateObject. Якщо View лише отримує вже готовий об'єкт від батька — використовуй @ObservedObject. Порушення цього правила призводить або до втрати даних (якщо використовувати @ObservedObject для володіння), або до надмірного створення об'єктів (якщо використовувати @StateObject для спостереження).

Коли використовувати @StateObject

@StateObject слід використовувати в тих View, які є джерелом істини для певного набору даних. Типові сценарії включають екрани з власною view model, кореневі екрани навігаційних стеків і модальні представлення, що керують власним станом.

  • Екран з view model — кожен екран, який керує власними даними та логікою, повинен створювати свою view model через @StateObject.
  • Кореневий View — в ієрархії NavigationStack або TabView кореневий елемент створює дані, а дочірні отримують їх через @ObservedObject.
  • Модальні вікна — .sheet та .fullScreenCover часто потребують власного @StateObject для керування формою або процесом.
  • Список з редагуванням — кожен рядок списку, що містить форму редагування, повинен мати власний @StateObject.
swift
struct ProfileView: View {
    @StateObject var viewModel = ProfileViewModel()
    
    var body: some View {
        NavigationStack {
            Form {
                TextField("Name", text: $viewModel.name)
                TextField("Email", text: $viewModel.email)
                Button("Save") {
                    viewModel.saveProfile()
                }
            }
            .navigationTitle("Profile")
        }
    }
}

Ініціалізація @StateObject з параметрами

Ініціалізація @StateObject з параметрами потребує спеціального синтаксису, оскільки SwiftUI керує створенням об'єкта самостійно. Не можна просто передати параметри в ініціалізатор — потрібно використовувати escape-замикання або окремий фабричний метод.

За даними Swift by Sundell (2024), найбільш чистий спосіб — використовувати фабричний метод або замикання, яке SwiftUI викличе при першому створенні об'єкта. Альтернативний підхід — ініціалізувати ObservableObject у батьківському View і передати його через @StateObject з використанням стандартного ініціалізатора.

swift
class UserViewModel: ObservableObject {
    @Published var user: User
    
    init(user: User) {
        self.user = user
    }
}

struct UserDetailView: View {
    @StateObject var viewModel: UserViewModel
    
    init(user: User) {
        _viewModel = StateObject(wrappedValue: UserViewModel(user: user))
    }
    
    var body: some View {
        Text(viewModel.user.name)
    }
}

Важливо пам'ятати, що ініціалізатор View з @StateObject повинен використовувати підкреслення перед іменем властивості (_viewModel) для доступу до самого property wrapper, а не до його значення. Це стандартний патерн Swift для роботи з property wrappers в ініціалізаторах.

Типові помилки з @StateObject

Найпоширеніша помилка — використання @ObservedObject замість @StateObject для View, яка повинна володіти об'єктом. У цьому випадку при кожній перебудові батька об'єкт буде створюватися заново, що призведе до втрати всіх накопичених даних. Ця помилка особливо підступна в складних ієрархіях з NavigationStack або TabView.

  • Втрата даних при навігації — якщо дочірній екран використовує @ObservedObject для власної view model, при поверненні назад і повторному відкритті дані скинуться.
  • Витік пам'яті — створення @StateObject у батьківському View, яке ніколи не видаляється, може призвести до накопичення об'єктів, якщо кожен дочірній екран також створює @StateObject без контролю.
  • Дублювання об'єктів — передача одного ObservableObject кільком @StateObject у різних View створює кілька незалежних екземплярів, які не синхронізуються між собою.

Щоб уникнути цих проблем, дотримуйся простого правила: один @StateObject на одне джерело істини. Якщо дані мають бути спільними для кількох екранів — створи @StateObject один раз у кореневому View і передавай через @ObservedObject або @EnvironmentObject дочірнім елементам.

swift
// ❌ Wrong: @ObservedObject for owning an object
struct BadView: View {
    @ObservedObject var vm = ViewModel() // will be recreated on each update!
}

// ✅ Correct: @StateObject for owning
struct GoodView: View {
    @StateObject var vm = ViewModel() // created once for View lifetime
}

Часті запитання

У чому різниця між @StateObject і @State?

@State працює з value-типами (структури, рядки, числа) та зберігає значення безпосередньо у сховищі SwiftUI. @StateObject працює з reference-типами — класами, що відповідають ObservableObject. @State підходить для простих локальних станів, @StateObject — для складних об'єктів з логікою та опублікованими властивостями.

Чи можна використовувати @StateObject в iOS 13?

Ні, @StateObject доступний лише з iOS 14 і вище. Для iOS 13 використовуйте @ObservedObject і створюйте ObservableObject у батьківському View через @State з ручним керуванням життєвим циклом. Альтернатива — використовувати @State з struct замість class для даних, які не вимагають reference semantics.

Що станеться, якщо використовувати @StateObject у дочірньому View, куди об'єкт передається з батька?

Дочірній View створить власну копію ObservableObject, повністю незалежну від батьківської. Зміни в одному не відобразяться на іншому. Це майже завжди помилка: використовуйте @ObservedObject для отримання об'єкта від батька та @StateObject лише для створення нового об'єкта всередині View.

Коли знищується об'єкт, створений через @StateObject?

Об'єкт знищується, коли View, яке його створило, повністю видаляється з ієрархії SwiftUI. Для екрана в NavigationStack це відбувається при pop з навігаційного стека. Для модального вікна — при його закритті. Для TabView — при перемиканні вкладки, якщо View не кешується.

Як передати параметри в @StateObject при ініціалізації?

Використовуй кастомний init з доступом до property wrapper через підкреслення: _viewModel = StateObject(wrappedValue: MyViewModel(param: value)). Цей патерн дозволяє передати будь-які параметри в ObservableObject, зберігаючи при цьому гарантію єдиного створення об'єкта за час життя View.

Підсумки

  • @StateObject — property wrapper для створення та володіння ObservableObject всередині View, доступний з iOS 14.
  • Гарантія єдиного створення — об'єкт ініціалізується один раз і не перестворюється при перебудові View.
  • Джерело істини — @StateObject є джерелом істини, а @ObservedObject — лише спостерігачем.
  • Життєвий цикл — об'єкт живе, поки View існує в ієрархії SwiftUI, і знищується при її залишенні.
  • Ініціалізація з параметрами — потребує доступу до property wrapper через _viewModel та StateObject(wrappedValue:).
  • Помилка володіння — використання @ObservedObject для створення об'єкта призводить до втрати даних при перебудові.
  • Один об'єкт — один @StateObject — для спільних даних створюй @StateObject у кореневому View і передавай дочірнім через @ObservedObject.

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також