Почему не использовать UserDefaults для хранения большого объема данных
Что проверяют
Понимаете ли вы, что UserDefaults это файл настроек, а не база данных, и знаете ли, чем его заменить. Следом обычно спрашивают, где хранить токен доступа и что выбрать для списка на десятки тысяч записей.
Как отвечать
Опишите механику, из неё следуют все ограничения. UserDefaults это property list в Library/Preferences, который обслуживает системный демон cfprefsd. Домен кешируется в памяти целиком, а запись идёт не поштучно: изменение одного ключа означает перезапись всего файла, пусть и отложенную.
Отсюда проблемы:
- память: весь домен поднимается в память и живёт там всё время работы приложения, вместе с вашими мегабайтами JSON или картинок;
- старт: чем толще plist, тем дольше первое обращение, это видно на времени запуска;
- запись: сериализация всего домена на каждое изменение, при частых записях это заметная нагрузка;
- нет выборки: нельзя достать одну запись из массива или отфильтровать, только прочитать объект целиком;
- нет схемы и миграций: набор типов ограничен, а изменение структуры данных вы разруливаете руками;
- нет секретности: значения лежат открытым текстом и попадают в резервную копию, паролям там не место.
Что предлагать взамен: Keychain для секретов; файл в Documents или Caches с Codable и JSONEncoder, когда данные читаются целиком; SQLite напрямую или через GRDB, Core Data либо SwiftData, когда нужны выборка и частичная загрузка; отдельные файлы через FileManager для картинок. NSUbiquitousKeyValueStore под объём тоже не подходит, там жёсткий лимит порядка мегабайта на приложение.
Закончите тем, для чего UserDefaults хорош: флаги, выбранная тема, признак пройденного онбординга, в том числе через @AppStorage в SwiftUI.
Где спотыкаются
Кладут туда base64 картинки или ответ API «временно, потом перенесём». Временное живёт годами, а приложение начинает медленнее стартовать.
Считают хранилище защищённым, раз есть песочница. Файл читается как обычный plist, попадает в бэкап и не шифруется сам по себе.
Обещают решить проблему частым вызовом synchronize(). Метод давно не нужен, система сама сбрасывает изменения на диск, и на объём данных это никак не влияет.