Специфическая проблема iOS 12: повреждение двоичных данных во внешнем хранилище основных данных - PullRequest
0 голосов
/ 01 октября 2018

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

Справочная информация

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

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

Проблема

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

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

для iOS 12

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

Предпринятые шаги

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

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

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

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

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

Вопрос

Есть идеи для следующих шагов?

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

Это ошибка в iOS 12. Подробное описание приемлемого обходного пути см. В принятом ответе ниже.

1 Ответ

0 голосов
/ 03 октября 2018

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


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

Другой альтернативой, которую мы рассмотрели, былоМиграция нашей модели не позволяет использовать внешнее хранилище для больших двоичных объектов, но я не знаю, какое влияние это окажет на производительность, и я также беспокоился о миграции модели в то время, когда эта часть iOS кажется нестабильной, особенно после чтения.истории, подобные этой в прошлом: Основные данные: не храните большие файлы в виде двоичных данных - Александр Эдж - Средний

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

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

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

...