На
WWDC26 компания
Apple перевела
@State с
property wrapper на
macro. Для разработчиков это значит, что код останется таким же, как и раньше, но внутри все работает иначе. Основное изменение касается ленивой инициализации observable-объектов, хранящихся в
@State.
Что изменилось:
Раньше
@State был
property wrapper. Теперь это
attached macro. В документации указано:
@attached(accessor, names: named(init), named(get), named(set))
@attached(peer, names: prefixed(`_`), prefixed(__), prefixed(`$`))
macro State()
На практике синтаксис не меняется. Код, который вы писали раньше, продолжает работать. Разница только в том, как
SwiftUI управляет хранением состояния.
Изменения на практике:
Самое важное - ленивая инициализация для observable-классов в
@State.
Раньше, если вы писали так:
@Observable
class ViewModel {
var index = 0
init() {
print("Init")
}
}
struct MyView: View {
@State private var viewModel = ViewModel()
var body: some View {
Button("Index: \(viewModel.index)") {
viewModel.index += 1
}
}
}
Инициализатор
ViewModel мог вызываться каждый раз, когда
SwiftUI пересоздавал структуру вью. Даже если сам объект хранился и не терялся, его инициализация все равно выполнялась. Это приводило к лишней работе, особенно если в инициализаторе были тяжелые операции.
Теперь, с
macro версией
@State, объект создается один раз - когда
SwiftUI создает хранилище состояния для вью. И больше не пересоздается при каждом обновлении родителя.
Когда это особенно заметно:
Это изменение особенно полезно, когда вы храните в
@State тяжелые объекты: модели с подписками, кэшами, сетевыми запросами при инициализации. Раньше приходилось использовать обходные пути - например, хранить опциональный объект и создавать его в
.task.
// Раньше приходилось делать так, чтобы избежать повторной инициализации
@State private var viewModel: ViewModel?
var body: some View {
MyView(viewModel: viewModel)
.task {
viewModel = ViewModel()
}
}
Теперь это больше не нужно. Можно просто писать:
@State private var viewModel = ViewModel()
И быть уверенным, что объект создастся только один раз.
Важно:
Есть одна тонкость, которая осталась. Если вы инициализируете
@State внутри
init() вью, а не прямо в объявлении, конструктор может вызываться несколько раз. Это не значит, что состояние сбросится, но сам
init выполнится, и все побочные эффекты произойдут.
init(id: ID) {
_viewModel = State(initialValue: ViewModel(id: id))
}
Если объект зависит от данных, которые приходят извне и могут меняться, лучше использовать другой подход -
task(id:) или передавать зависимости явно через параметры вью.
Вывод:
Apple сделала
@State macro, чтобы улучшить производительность в типичных сценариях. Синтаксис остался прежним, код не требует изменений, но observable-объекты в
@State теперь инициализируются один раз. Это избавляет от лишнего кода и делает поведение более предсказуемым.
Переход на
macro для такого базового механизма - это важный сигнал.
Apple уверена в своей macro-системе и готова переносить на нее ключевые
API. А для разработчиков это просто означает, что
SwiftUI становится чуть более эффективным и предсказуемым.