Компания Apple представила новый фреймворк Observation, основанный на макрофункциях языка Swift. Новый фреймворк Observation в сочетании с функциями Swift Concurrency позволяет заменить Combine, который выглядит устаревшим по мнению Apple. В этой статье мы узнаем, как использовать фреймворк Observation для обработки потоков данных в наших приложениях.
Использовать новый фреймворк Observation очень просто. Все, что вам нужно сделать, — это пометить свой класс макросом @Observable
.
@Observable final class Store<State, Action> { typealias Reduce = (State, Action) -> State private(set) var state: State private let reduce: Reduce init(initialState state: State, reduce: @escaping Reduce) { self.state = state self.reduce = reduce } func send(_ action: Action) { state = reduce(state, action) } }
Как видно из приведенного примера, мы используем макрос @Observable
для аннотирования нашего типа Store
. После этого мы можем наблюдать любую переменную в типе Store
. В нему у нас есть только одна переменная, которая определяет состояние хранилища. Другое поле — это константа let
, которая никогда не изменяется.
withObservationTracking { render(store.state) } onChange: { print("State changed") }
Для наблюдения за экземпляром типа Store
необходимо вызвать функцию withObservationTracking
с двумя замыканиями. В первом закрытии мы можем прочитать все необходимые свойства наблюдаемого типа. Второе закрытие фреймворк Observation вызывает один раз, как только изменяется любое затронутое свойство наблюдаемого типа.
func startObservation() { withObservationTracking { render(store.state) } onChange: { Task { startObservation() } } }
Фреймворк Observation запускает функцию onChange
только один раз, поэтому для постоянного наблюдения за изменениями ее следует вызывать рекурсивно. Еще один момент, о котором следует помнить, — замыкание onChange
выполняется до фактического применения изменения. Поэтому мы откладываем действие onChange
, запуская новую задачу.
В SwiftUI для наблюдения за изменениями не нужно использовать функцию withObservationTracking
. SwiftUI автоматически отслеживает изменения любого свойства типа observable, используемого внутри тела представления.
struct ProductsView: View { let store: Store<AppState, AppAction> var body: some View { List(store.state.products, id: \.self) { product in Text(product) } .onAppear { store.send(.fetch) } } }
Как видно из приведенного примера, мы не используем никаких оберток свойств для наблюдения за хранилищем. SwiftUI делает это автоматически. Как только свойство state
хранилища изменяется, SwiftUI обновляет представление. Нам не нужна обертка свойств @ObservedObject
для отслеживания изменений в наблюдаемых типах, но нам все еще нужна альтернатива @StateObject
, чтобы пережить жизненный цикл SwiftUI.
Apple упрощает набор оберток свойств, которые мы должны использовать с новым фреймворком Observation. Вместо обертки свойств @StateObject
мы теперь можем использовать @State
. Обертка свойства @State
теперь работает для простых типов значений и любых наблюдаемых типов.
struct ContentView: View { @State private var store = Store<AppState, AppAction>( initialState: .init(), reduce: reduce ) var body: some View { ProductsView(store: store) } }
Аналогичный подход применяется и к свойству environment фреймворка SwiftUI. Теперь нет необходимости в обертке свойства @EnvironmentObject
. Теперь можно использовать обертку свойства @Environment
и модификатор представления environment
с observable типами.
struct ContentView: View { @State private var store = Store<AppState, AppAction>( initialState: .init(), reduce: reduce ) var body: some View { ProductsView() .environment(store) } } struct ProductsView: View { @Environment(Store<AppState, AppAction>.self) var store var body: some View { List(store.state.products, id: \.self) { product in Text(product) } .onAppear { store.send(.fetch) } } }
И последнее, что вас может интересовать, — как вывести биндинг из наблюдаемого типа. Для этого случая в SwiftUI предусмотрена обертка свойства @Bindable
, которая работает только с наблюдаемыми типами.
@Observable final class AuthViewModel { var username = "" var password = "" var isAuthorized = false func authorize() { isAuthorized.toggle() } } struct AuthView: View { @Bindable var viewModel: AuthViewModel var body: some View { VStack { if !viewModel.isAuthorized { TextField("username", text: $viewModel.username) SecureField("password", text: $viewModel.password) Button("authorize") { viewModel.authorize() } } else { Text("Hello, \(viewModel.username)") } } } }
С помощью обертки свойств @Bindanble
можно легко создавать привязки из свойств любого наблюдаемого типа. Иногда для создания привязок может потребоваться инлайн @Bindable
внутри тела представления.
struct InlineAuthView: View { @Environment(AuthViewModel.self) var viewModel var body: some View { @Bindable var viewModel = viewModel VStack { if !viewModel.isAuthorized { TextField("username", text: $viewModel.username) SecureField("password", text: $viewModel.password) Button("authorize") { viewModel.authorize() } } else { Text("Hello, \(viewModel.username)") } } } }
Мне нравится, как новый фреймворк Observation упрощает работу с потоком данных в SwiftUI. Надеюсь, вам понравился этот пост. Спасибо, что прочитали, и до встречи на следующей неделе!