Я рассматриваю восстановление состояния в приложении SwiftUI. Я нашел этот пример кода от Apple, который использует ObservableObject и сохраняет навигационные данные в SceneStorage при каждом изменении объекта.
Мне интересно, как лучше всего подойти к этому с помощью класса Observable (макрос из iOS 17). Могу ли я перестроить это, используя поток .values необходимых свойств, или есть более умный способ. Вот фрагмент кода, о котором я говорю:
// In Navigation: ObservableObject
var objectWillChangeSequence: AsyncPublisher<Publishers.Buffer<ObservableObjectPublisher>> {
objectWillChange
.buffer(size: 1, prefetch: .byRequest, whenFull: .dropOldest)
.values
}
// In View
.task {
if let data = data {
navModel.jsonData = data
}
for await _ in navModel.objectWillChangeSequence {
data = navModel.jsonData
}
}
Учитывая, что моя модель навигации имеет более одного пути навигации, я теоретически мог бы построить поток, используя pathOne.values и объединитьLatest с pathTwo.values, но я хотел бы знать, есть ли более простой способ. Конечно, я знаю, что теоретически все еще могу использовать ObservableObject, как это делает пример кода, но я бы предпочел использовать новый макрос Observable, если это работает.





Я не думаю, что образец Apple имеет правильный дизайн источника истины или даже типы данных, например, он говорит: «Сначала я инкапсулирую свое состояние навигации в типе NavigationModel. Это позволяет мне сохранять и восстанавливать его как единое целое, чтобы оно всегда было единообразным». однако инкапсуляция обеих переменных в собственную структуру состояния является обычным способом сделать это, и обычной причиной для объектов является асинхронная загрузка/сохранение, однако .task заменяет необходимость в этом. Курт даже раньше использовал пользовательские структуры в своих выступлениях в Книжном клубе, так что, возможно, он не пишет код для своих слайдов. При этом, чтобы преобразовать модель из Joint в Observable с потоком изменений, попробуйте следующее:
.task {
let navModelJsonDataDidChange = AsyncStream {
await withCheckedContinuation { continuation in
withObservationTracking {
let _ = navModel.jsonData // let _ required to fix compilation error
} onChange: {
continuation.resume()
}
// return navModel.jsonData // this would insert the new value in the stream but we won't bother because it would make our simple loop more messy.
}
}
var iterator = navModelJsonDataDidChange.makeAsyncIterator()
repeat
{
data = navModel.jsonData
}
while await iterator.next() != nil // loops until task is cancelled which causes the stream to return nil.
}
Где navModel хранится @Observable class в необязательном @State, то есть init Once, где-то вроде onAppear. Цикл повторения/пока позволяет нам получить начальное значение и любые изменения в одной строке.
Другие варианты: .task(id:) или просто .onChange. Я не думаю, что navModel должен быть классом, поскольку он не делает ничего асинхронного.
Ух ты withObservationTracking работает внутри withCheckedContinuation? Я думал, что withObservationTracking обнаруживает только изменения в замыкании { navModel.jsonData }, которое вообще ничего не меняет, и поэтому onChange не будет вызываться. Можете ли вы объяснить, как это работает?
Вы перечисляете свойства, которые хотите отслеживать, вызывая их методы получения, которые имеют длительный срок службы, как и объект, сохраняемый в потоке. Когда эти свойства установлены чем-либо еще, onChange вызывается до изменения значения. Затем геттер после ожидания вставляет новое значение в поток. Кстати, это отсюда malcolmhall.com/2024/05/04/…
Спасибо за подробный ответ. Я внимательно рассмотрю это, но это, по сути, то, о чем я просил. Утверждая, что «другие параметры — это задача (id:)», вы имеете в виду размещение NavigationModel.jsonData в качестве идентификатора, который, по сути, означает запуск задачи при каждом изменении идентификатора (jsonData), верно? Я думаю, в этом случае асинхронный поток не нужен? Для меня это звучит намного проще, если я правильно понимаю
Да, проще, но поскольку ожиданий нет, onChange имеет больше смысла. Возможно, проверьте, в каком потоке выполняется задача в примере Apple.
Вычисленная привязка для навигационного пути может быть лучшей идеей, использующей SceneStorage в качестве источника истины. Вы увидите это в докладах команды Core Data по SwiftUI.
@Observableне очень хорошо работает с издателями. Если вам нужен операторbuffer, просто продолжайте использоватьObservableObject. В противном случае вы можете сопоставить класс@ObservableсEquatableи наблюдать за ним с помощьюonChange.