Irbisa · Шпаргалка

Состояние в SwiftUI

@State, @Binding, @StateObject, @ObservedObject, @EnvironmentObject, Observation

Middle-разработчик · 24 вопроса

  1. 01

    Чем @State отличается от @Binding в SwiftUI?

    @State объявляет хранилище, которым владеет сама вью: SwiftUI держит значение вне структуры вью и перерисовывает её, когда оно меняется.

    struct CounterScreen: View {  @State private var count = 0            // owned here   var body: some View {    VStack {      Text("\(count)")…
  2. 02

    Что в этой вью означают count, $count и _count?

    struct CounterView: View {  @State private var count: Int   init(start: Int = 0) {    _count = State(initialValue: start)  }   var body: some View {    VStack {      Text("\(count)")      Stepper("Count", value: $count)      Button("Reset") { count = 0 }    }  }}

    count возвращает текущее значение (wrappedValue обёртки), $count возвращает Binding<Int> к нему (projectedValue), а _count ссылается на саму обёртку State<Int>, к которой обращаются только в init.

  3. 03

    Чем отличаются @Environment, @EnvironmentObject и @ObservedObject, и когда брать environment вместо init?

    @ObservedObject получает объект явно через инициализатор, а @EnvironmentObject и @Environment читают то, что внедрено выше по дереву, поэтому промежуточным вью не нужно передавать это дальше (prop drilling).

    @Observablefinal class AppSession {  var userName: String?} @main…
  4. 04

    Какие значения @Environment вы реально используете? Например, как дочерняя вью закрывает свой экран?

    Она читает @Environment(\.dismiss) и вызывает его.

    struct EditNoteScreen: View {  @Environment(\.dismiss) private var dismiss  @Environment(\.scenePhase) private var scenePhase  @State private var text = ""   var body: some View {…
  5. 05

    Кнопка Done на клавиатуре формы ничего не делает, а Next не переводит на следующее поле. Какой property wrapper забыли?

    Не хватает @FocusState (вместе с .onSubmit).

    struct SignUpForm: View {  enum Field { case email, password }   @State private var email = ""  @State private var password = ""  @FocusState private var focused: Field?…
  6. 06

    Родитель меняет start с 0 на 5, а Counter продолжает показывать 0. Почему и как исправить?

    struct Counter: View {  @State private var count: Int   init(start: Int) {    _count = State(initialValue: start)  }   var body: some View {    Button("\(count)") { count += 1 }  }} // In the parent:// Counter(start: selectedPreset.start)

    State(initialValue:) используется только при первом появлении вью с этой identity.

  7. 07

    Когда использовать @StateObject, а когда @ObservedObject, и что сломается, если выбрать не тот?

    @StateObject значит, что вью владеет объектом: SwiftUI создаёт его один раз и держит живым всё время жизни вью.

    final class CartViewModel: ObservableObject {  @Published var items: [String] = []} // Owner: created once, kept for the view's lifetimestruct CartScreen: View {…
  8. 08

    Что делает @Published и как ObservableObject сообщает об изменениях?

    @Published оборачивает свойство и отправляет событие в паблишер objectWillChange объекта непосредственно перед изменением значения; вью, наблюдающие объект (@StateObject, @ObservedObject, @EnvironmentObject), после этого заново выполняют body.

    import Combine final class SearchModel: ObservableObject {  @Published var query = ""  private var bag = Set<AnyCancellable>()…
  9. 09

    Найдите баг: по нажатию товар добавляется в корзину, но счётчик корзины в тулбаре не меняется.

    final class Cart: ObservableObject { @Published var items: [Product] = [] }final class ShopModel: ObservableObject { @Published var cart = Cart() } struct ShopScreen: View {  let products: [Product]  @StateObject private var model = ShopModel()  var body: some View {    List(products) { p in      Button(p.name) { model.cart.items.append(p) }    }    .toolbar { Text("Cart: \(model.cart.items.count)") }  }}

    Cart объявлен как класс, поэтому @Published var cart хранит ссылку, которая не меняется.

  10. 10

    Что меняет макрос @Observable по сравнению с ObservableObject и @Published?

    @Observable делает наблюдаемым каждое хранимое свойство класса без @Published, а SwiftUI отслеживает, какие свойства прочитала каждая вью, поэтому вью перерисовывается только при изменении этих свойств, а не при любом изменении объекта.

    @Observablefinal class FeedModel {  var title = "Feed"  var posts: [Post] = []  @ObservationIgnored var lastRequestID: UUID?   // not tracked}…
  11. 11

    Что такое @Bindable и когда он нужен с моделью @Observable?

    @Bindable превращает @Observable-объект в источник биндингов: $profile.name становится Binding<String> для TextField.

    @Observablefinal class Profile {  var name = ""  var isPublic = false}…
  12. 12

    Вы нажимаете кнопку, а текст не меняется. Почему и как исправить?

    final class Draft {  var title = ""} struct Editor: View {  @State private var draft = Draft()   var body: some View {    VStack {      Text(draft.title.isEmpty ? "empty" : draft.title)      Button("Set") { draft.title = "Hello" }    }  }}

    @State перерисовывает вью, когда заменяется хранимое в нём значение, а draft.title = "Hello" меняет объект за ссылкой, не заменяя саму ссылку, поэтому SwiftUI не видит изменений.

  13. 13

    Как выбрать правильный property wrapper для куска состояния в SwiftUI?

    Состоянием владеет вью, которая его создала: @State для значения или @Observable-модели.

  14. 14

    Как передать дочерней вью Binding, если значение не хранится в @State?

    Binding представляет собой просто геттер плюс сеттер, поэтому его можно собрать вручную через Binding(get:set:), когда значение лежит там, куда не дотянуться через $: за преобразованием типа или внутри опционала.

    struct ProfileEditor: View {  let user: User                 // @Observable class with age: Int  @State private var nickname: String?   var body: some View {    Form {…
  15. 15

    Правки на экране деталей не появляются в списке позади него, хотя оба показывают одну и ту же структуру Order. Что пошло не так?

    Экран деталей получил копию.

    struct Order: Identifiable { let id: UUID; var note: String } // wrong: the detail edits a private copystruct CopyDetail: View {  @State private var order: Order  init(order: Order) { _order = State(initialValue: order) }…
  16. 16

    Когда SwiftUI заново выполняет body вью и когда сбрасывается её @State?

    SwiftUI заново выполняет body, когда изменилось то, что вью прочитала: её состояние, биндинг, значение environment, наблюдаемое свойство или входные данные от родителя.

    struct EditorScreen: View {  @State private var isEditing = false   var body: some View {    // Two branches = two views: switching resets FormView's @State    if isEditing {…
  17. 17

    Состояние «развёрнуто/свёрнуто» у строки сбрасывается, когда пользователь прокручивает её за экран и обратно. Что на самом деле происходит?

    Флаг живёт в @State строки, а List или LazyVStack вправе уничтожать строки, ушедшие далеко за экран.

    struct Feed: View {  let items: [Item]  @State private var expanded: Set<Item.ID> = []   // survives rows being discarded   var body: some View {    List(items) { item in…
  18. 18

    Где во вью SwiftUI место для побочных эффектов и асинхронной работы и что происходит с .task, когда вью исчезает?

    Только не в body: SwiftUI может выполнить его когда угодно и сколько угодно раз.

    struct SearchScreen: View {  @State private var query = ""  @State private var results: [Hit] = []  let api: SearchAPI   var body: some View {…
  19. 19

    Что делают @AppStorage и @SceneStorage и где они перестают быть подходящим инструментом?

    @AppStorage объявляет свойство поверх UserDefaults, которое перерисовывает вью при изменении ключа; @SceneStorage хранит UI-состояние отдельного окна, которое система сохраняет и восстанавливает при перезапуске приложения.

    enum Theme: String, CaseIterable { case system, light, dark } struct SettingsView: View {  @AppStorage("showBadges") private var showBadges = true  @AppStorage("theme") private var theme: Theme = .system     // RawRepresentable  @SceneStorage("draftNote") private var draftNote = ""       // restored per window…
  20. 20

    Как @Observable узнаёт, какие вью инвалидировать, и какие изменения это отслеживание молча пропускает?

    Макрос превращает каждое хранимое свойство в вычисляемое, которое сообщает о чтениях и записях в ObservationRegistrar, а SwiftUI выполняет body под отслеживанием наблюдения, поэтому вью зависит ровно от тех свойств, которых коснулась за этот прогон.

    // Roughly what @Observable generates for one propertyfinal class Model: Observable {  @ObservationIgnored private var _title = ""  @ObservationIgnored private let registrar = ObservationRegistrar()   var title: String {…
  21. 21

    Модель пишет свои свойства из фонового сетевого колбэка, и список обновляется с опозданием, глючит или падает. Чего на самом деле требует SwiftUI?

    Каждая запись, которую может увидеть вью, должна происходить на main actor.

    // wrong: writes an observed property from a background callback@Observable final class BadFeed {  var items: [Item] = []  func load(_ url: URL) {    URLSession.shared.dataTask(with: url) { data, _, _ in      self.items = decode(data)                       // background thread: data race…
  22. 22

    Найдите баг: чат-сервер фиксирует новое подключение при каждой перерисовке родительского экрана, хотя у пользователя открыт один чат.

    // ChatSocket wraps a WebSocket: connect() opens it, disconnect() closes it, messages() streams incoming text@MainActor @Observable final class ChatModel {  var messages: [String] = []  private let socket = ChatSocket(url: supportURL)   init() {    Task {                               // bug: side effects in init      socket.connect()      for await message in socket.messages() { messages.append(message) }    }  }} struct SupportChatScreen: View {  @State private var model = ChatModel()  var body: some View {    List(model.messages, id: \.self) { Text($0) }  }}

    @State вычисляет начальное значение при каждом создании структуры вью, а SwiftUI пересоздаёт SupportChatScreen при каждой перерисовке родителя.

  23. 23

    Экран на SwiftUI подтормаживает, и вы подозреваете слишком частые вычисления body. Как это подтвердить и что менять?

    Сначала докажите, какие вью перерисовываются и почему, потом устраняйте причину.

    struct CartRow: View {  let item: CartItem   var body: some View {    let _ = Self._printChanges()   // debug only: prints `@self`, `@identity` or the changed property    Text(item.title)…
  24. 24

    Как организовать владение состоянием и поток данных в большом SwiftUI-приложении?

    У каждого куска состояния ровно один владелец на самом узком уровне, которому оно нужно.