Site icon AppTractor

Осваиваем фреймворк Observation в Swift

Компания 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. Надеюсь, вам понравился этот пост. Спасибо, что прочитали, и до встречи на следующей неделе!

Источник

Видео

Exit mobile version