Спецификация iOS 12: основные данные Внешнее хранилище Двоичные данные

Я потратил большую часть рабочего дня, пытаясь решить эту проблему.

Фон

У меня простая базовая модель данных с книгами и сеансами чтения. В книгах есть обложки (изображения), которые хранятся в виде двоичных данных с помощью "Разрешить внешнее хранилище".

На iOS 11.4 и ниже все работает отлично все время. Когда я сохраняю новый сеанс, все обновляется должным образом.

проблема

Начиная с iOS 12, когда я создаю новый сеанс чтения и связываю его с книгой, каждый второй раз основные данные генерируют инструкцию SQL, которая также обновляет поле обложки книги, что иногда приводит к плохой ссылке (на файл на диске), которая часто приводит к тому, что обложка равна нулю при перезапуске приложения и почти всегда создает дубликат копии обложки на диске (как видно _EXTERNAL_DATA папки Simulator _EXTERNAL_DATA).

Контекст и объекты в памяти остаются правильными (и, следовательно, все в пользовательском интерфейсе), пока приложение не будет перезапущено, тогда обложка часто равна нулю.

Спецификация iOS 12

На iOS 12 я могу детерминистически воспроизвести ошибку в симуляторе, на физических устройствах, и пользователи также сообщили об ошибке. Я не могу воспроизвести ошибку на iOS 11.4, и ни одна из пользователей не сообщила об ошибке, предшествующей iOS 12.

Сделанные шаги

  • Я включил " -com.apple.CoreData.ConcurrencyDebug 1 ", поэтому не должно быть, что я получаю доступ к чему-либо из неправильной очереди. Я также включил " -com.apple.CoreData.SQLDebug 3 ", чтобы я мог точно видеть, что написано.

  • Я убедился, что экземпляр книги (и, следовательно, обложка) не изменен моим кодом перед ассоциацией с новым newSession.book = book проверив hasChanges, как раз перед тем, как я сделаю newSession.book = book и context.save().

  • Чтобы быть на 100% уверенным, что я не касаюсь свойства обложки ни на одном потоке, я коротко замыкал мои получатели и сеттеры для этого свойства. Без улучшения.

  • Я попытался использовать objectID для запроса экземпляра книги непосредственно перед ассоциацией и сохранения. Без улучшения.

  • Я даже попробовал вариант, где контекст содержит сильные ссылки на все объекты, просто чтобы убедиться, что это не проблема управления памятью. Без улучшения.

Вопрос

Любые идеи для следующих шагов?

Обновление статуса

Это дефект в iOS 12. См. Принятый ответ ниже для подробного описания резонансного обходного пути.

Ответ 1

Обновление. Основная проблема с основными данными, по-видимому, разрешена в iOS 12.1 (проверена в бета-версии 4). Мы сохраним обходное решение, описанное ниже в нашем приложении, и в ближайшее время не будем рекомендовать использование опции внешнего хранилища.


После разговора с инженерами Apple и подачи радара, упомянутого выше, мы не могли подождать исправления, поэтому мы взяли его и переключили на хранение файлов в файловой системе и сами управляли им.

Другой альтернативой, которую мы рассматривали, была миграция нашей модели, чтобы не допускать использование внешнего хранилища для BLOB, но я не знаю, какое влияние это оказало бы на производительность, и я также беспокоился о миграции модели в момент, когда эта часть iOS кажется быть неустойчивым, особенно после чтения таких историй в прошлом: Основные данные: не хранить большие файлы в виде двоичных данных - Alexander Edge - Medium

Это не было слишком большой проблемой для самообслуживания локального хранилища. Вам просто нужно иметь уникальный идентификатор для каждой записи, который вы можете использовать для создания имени файла, чтобы вы могли сопоставлять файлы с записями. Мы добавили расширение к нашему подклассу Managed Object с методами для чтения, записи и удаления файлов. Теперь вместо вызова, например article.photo = image.pngData(), теперь нам нужно вызвать что-то вроде article.savePhoto(image.pngData()) а затем мы делаем подобное, когда хотим получить изображение. Вы также можете добавить код к этим методам для поддержки обратной совместимости с любыми изображениями, которые в настоящее время хранятся в Core Data.

Удаление было немного сложнее, потому что наши объекты удалены из нескольких мест в коде, включая каскадные удаления. В конце концов я решил сделать это в методе prepareForDeletion управляемого объекта, но он не идеален. Существует много дискуссий о том, как лучше всего реализовать это здесь: cocoa - Как обрабатывать очистку внешних данных при удалении несохраненных объектов Core Data? - Переполнение стека

Наконец, чтобы предотвратить сбой приложения после того, как из-за этой ошибки исчезнет необязательный двоичный атрибут, я переопределяю awakeFromFetch в моем подклассе Managed Object, чтобы гарантировать, что любые необходимые атрибуты не равны нулю, а если они есть, я установил их в placeholder чтобы они могли быть сохранены без сбоев проверки.