UserDefaults—  важный функционал в разработке iOS для хранения данных, которые сохраняются даже при перезапуске приложения. Раньше в UIKit для чтения и записи данных обращались к UserDefaults напрямую. Однако этот подход кажется громоздким в SwiftUI, особенно учитывая декларативный характер последнего.

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

UserDefaults было бы неплохо перенять поведение @State: легко считывать и записывать свойства, переотрисовывая представление при изменении значений, но при этом длительно сохраняя данные.

@AppStorage появилась в iOS 14

Эту задачу в Apple решили появлением @AppStorage в iOS 14.

Теперь при изменении сохраняемых значений представление обновляется автоматически. Но, в отличие от @State, с @AppStorage эти значения сохраняются  —  данные остаются даже после перезапуска приложения.

Благодаря беспроблемному взаимодействию с @Binding, @AppStorage полезна и для работы с базовыми компонентами вроде Toggle или TextField, которым требуются параметры Binding<Type>.

Несмотря на удобство, у @AppStorage имеется потенциальная проблема с самим SwiftUI: когда в одно представление упаковано слишком много свойств, сложность увеличивается и управлять зависимостями становится труднее.

Типичный пример чрезмерного использования @AppStorage

Чтобы смягчить последствия этой проблемы, разработчики прибегают к шаблону MVVM, который стал популярен в SwiftUI. Они создают специальный класс ViewModel для конкретных задач, в котором содержатся необходимые свойства и методы.

Также, стремясь упростить применение UserDefaults в SwiftUI, создают пользовательские обертки свойств (как показано ниже) или задействуют свойства @Published с didSet во ViewModel.

Обертки с таким количеством UserDefaults вам не нужны

Но такой подход обычно тяжеловесен и зачастую не нужен. А вы знали, что @AppStorage используется непосредственно во ViewModel, который соответствует ObservableObject? @State не применяется во ViewModel, а вот @AppStorage прекрасно работает вне представления.

Выполним рефакторинг созданного выше представления, у которого слишком много свойств @AppStorage. Начнем с класса UserDefaultsViewModel, которым управляются UserDefaults.

Просто объявляем @AppStorage во ViewModel так же, как в представлениях.

Жестко задаем уникальные ключи для @AppStorage или, чтобы повысить сопровождаемость, управляем ими с помощью вложенного перечисления.

Этот UserDefaultsViewModel располагается на верхнем уровне приложения со @StateObject. Теперь у нас имеется источник истины со всеми UserDefaults приложения.

Чтобы обеспечить легкий доступ к этой ViewModel во всех дочерних представлениях, добавляем ее в иерархию представлений как EnvironmentObject

В этом-то и преимущество @EnvironmentObject  —  ViewModel при необходимости доступна любому дочернему представлению.

Теперь свойства @AppStorage во ViewModel легко считываются и записываются. Аналогично @State и @Published, подключается и binding($).

При таком подходе UserDefaults более адаптированы к SwiftUI благодаря соответствующим чистым API.

Но у этого метода имеются ограничения. Как и с UserDefaults, в @AppStorage не сохраняются пользовательские типы. Проблема решается применением Codable и обработкой процессов кодирования/декодирования, но это уже тема другой статьи.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи Jaeho Yoo: [SwiftUI] Unlocking the Potential of @AppStorage: Managing UserDefaults with ViewModels

Предыдущая статьяЦифры без диаграмм: хаки по JS-диаграммам
Следующая статьяКак последовательно писать аналитические SQL-запросы за 8 шагов