私はこれを解決するために平日の大部分を費やしました。
背景
本と読書セッションを備えた、シンプルなコアデータモデルがあります。本には、「外部ストレージを許可する」でバイナリデータとして保存されるカバー(画像)があります。
IOS 11.4以前では、すべてが常に正常に機能します。新しいセッションを保存すると、すべてが正しく更新されます。
問題
IOS 12以降、新しい読書セッションを作成してそれを本にリンクすると、約second時間ごとに、コアデータは更新するSQLステートメントを生成します本の表紙のフィールド。時々、(ディスク上のファイルへの)不正な参照が発生し、アプリの再起動時にしばしば表紙がnilになります。常にディスクのカバーの複製コピーを作成します(シミュレーターの_EXTERNAL_DATA
フォルダーで確認できます)。
ただし、メモリ内のコンテキストとオブジェクトは正しいままです(したがって、UIのすべてがOKです)。アプリが再起動されるまで、多くの場合nil 。
iOS 12固有
IOS 12では、物理デバイスでシミュレーターでエラーを確定的に再現でき、ユーザーもエラーを報告しています。 iOS 11.4でエラーを再現できず、iOS 12より前のエラーを報告したユーザーはいません。
実行した手順
「-com.Apple.CoreData.ConcurrencyDebug 1
」を有効にしたので、間違ったキューから何かにアクセスしているのではないはずです。また、「-com.Apple.CoreData.SQLDebug 3
」を有効にして、書き込まれた内容を正確に確認できるようにしました。
newSession.book = book
とcontext.save()
。
100%確実にするために、そのプロパティのゲッターとセッターを短絡させたスレッドのカバープロパティに触れないようにします。改善なし。
objectID
を使用して、関連付けの直前に本のインスタンスを要求し、保存しようとしました。改善なし。
何らかのメモリ管理の問題ではないことを確認するために、コンテキストがすべてのオブジェクトへの強力な参照を保持するオプションを試しました。改善なし。
質問
次のステップのアイデアはありますか?
ステータス更新
これはiOS 12の欠陥です。妥当な回避策の詳細な説明については、以下の承認済みの回答を参照してください。
更新:根本的なコアデータの問題は、iOS 12.1で解決されたようです(beta 4で検証済み)。アプリで以下に説明する回避策を維持し、すぐにExternal Storageオプションを使用することは推奨しません。
Appleエンジニアと 上記のレーダー をファイリングした後、修正を待つことができなかったので、ヒットを取得し、ファイルの保存に切り替えました。ファイルシステムと直接管理します。
私たちが検討した別の選択肢は、BLOBの外部ストレージを許可しないようにモデルを移行することでしたが、パフォーマンスにどのような影響があったかわかりませんし、iOSのこの部分がそうであるときにモデルの移行についても心配しました特に過去に次のような記事を読んだ後は不安定になります。 コアデータ:大きなファイルをバイナリデータとして保存しない– Alexander Edge –中
ローカルストレージを自分で実装するのはそれほど苦痛ではありませんでした。ファイルにレコードをマップできるように、ファイル名の作成に使用できる各レコードの一意の識別子が必要です。ファイルの読み取り、書き込み、削除のためのメソッドを備えた拡張機能を管理オブジェクトサブクラスに追加しました。今、例えばを呼び出す代わりにarticle.photo = image.pngData()
、今はarticle.savePhoto(image.pngData())
のようなものを呼び出す必要があり、画像を取得したい場合は同様のことを行います。これらのメソッドにコードを追加して、現在Core Dataに保存されている画像との後方互換性をサポートすることもできます。
カスケード削除を含むコード内の複数の場所からオブジェクトが削除されるため、削除はもう少し注意が必要でした。最終的に、管理対象オブジェクトのprepareForDeletion
メソッドで行うことを選択しましたが、理想的ではありません。これを実装する最善の方法については、ここで多くの議論があります。 cocoa-unsavedCore Dataオブジェクトを削除する際の外部データのクリーンアップの処理方法-スタックオーバーフロー
最後に、このバグのために非オプションのバイナリ属性が消えたときにアプリがクラッシュするのを防ぐために、管理オブジェクトサブクラスでawakeFromFetch
をオーバーライドして、必要な属性がnilでないことを確認します。検証に失敗することなく保存できるように、プレースホルダー画像に設定します。