【Swift】Core Dataをマルチスレッドで扱う方法!クラッシュチェック方法

この記事からわかること

  • Swift UICore Data利用する方法
  • マルチスレッドでの使い方
  • Managed Object Context(MOC)の役割定義方法
  • -com.apple.CoreData.ConcurrencyDebug 1とは?
  • アプリクラッシュ(EXC_BAD_ACCESS)する際の原因解決方法

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

環境

Core Dataはスレッドセーフではない?

参考文献:公式リファレンス:Core Data プログラミングガイド〜Concurrency〜

Core Dataに限らず長時間かかる処理や大量のデータを処理する時などメインスレッドで実行したくない(UIをブロックしたくない)場合にはスレッドを分けて処理を実装することも多いと思います。しかしCore DataのContext(NSManagedObjectContext)はスレッドセーフではありません。そのためデータの参照や追加などをメインやバックグラウンドなど異なるスレッドから操作するとアプリがクラッシュする可能性があります。

解決方法としては「スレッドごとにContext(NSManagedObjectContext)を作成しスレッドごとのNSPersistentContainerを作成する」方法が公式で推奨されています。クラッシュを解決したいだけで、パフォーマンスに影響がなさそうならスレッドを統一するのも1つの手かもしれません。

マルチスレッド解決方法

  1. スレッドごとにContextを作成しスレッドごとのNSPersistentContainerを作成する
  2. スレッドを統一する

スレッドセーフとは?

そもそもスレッドセーフとはマルチスレッドが有効でない実行環境で、特定の処理を複数のスレッドで並行して実行しても、問題が生じない仕様や設計になっていることを指します。SwiftではGCD(Grand Central Dispatch)と呼ばれるマルチスレッド処理を行うための機能が提供されています。

Core Dataのマルチスレッド対応方法

参考文献:【Swift】Core Dataをバックグラウンドで使う
参考文献:Core Data and Concurrency

マルチスレッドに対応したContextを生成する方法は上記の記事たちを参考にさせていただきました。その方法は大きく分けて3つあるようです。

  1. NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
  2. newBackgroundContextメソッド
  3. performBackgroundTaskメソッド

NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)

NSManagedObjectContextのイニシャライザを使用してスレッドごとのContextを生成することができます。引数concurrencyTypeには生成したいContextの種類NSManagedObjectContextConcurrencyType型で指定します。

mainQueueConcurrencyType

メインスレッドで動作するContextを生成します。

let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)

privateQueueConcurrencyType

バックグラウンドスレッドで動作するContextを生成します。

let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)

この方法で生成したContextではデータを永続化させるためpersistentStoreCoordinatorまたはparentプロパティに適切な設定する必要があり、両方とも設定することは想定されていないようです。

context.persistentStoreCoordinator = persistentContainer.persistentStoreCoordinator
// または
context.parent = makeBackgroundContext()

persistentStoreCoordinatorを設定した場合はそのContextsaveが呼ばれた際に、データはすぐに永続化されますが、parentに親のContextを指定している場合は子のContextsaveは親にマージされ、親のContextsaveが呼ばれた際に永続化されるようです。

newBackgroundContextメソッド

NSPersistentContainernewBackgroundContextメソッドを使用することで簡単にバックグラウンド用のコンテキストを取得することも可能です。実装方法や使い方などは以下の記事を参考にしてください。

persistentContainer.newBackgroundContext()

performBackgroundTaskメソッド

NSPersistentContainerperformBackgroundTaskメソッドを使用することでバックグラウンド用のコンテキストの生成と処理をまとめて実装することが可能です。ここで生成、使用したContextはクロージャーを抜けると破棄されるため単発で処理を行いたい時に活用できます。

persistentContainer.performBackgroundTask { context in
    // バックグラウンドで行いたい処理
}

使い訳

NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)

親子関係を持たせたContextを生成したいとき

newBackgroundContextメソッド

シンプルに再利用できるバックグラウンドContextを生成したいとき

performBackgroundTaskメソッド

単発の処理だけバックグラウンドで行うためその時限りのContextを生成したいとき

マルチスレッドになっていないかチェックする方法

Core Dataを導入しているプロジェクトでマルチスレッドでコンテキストが利用されているかをチェックするためにはXcodeのスキームの引数に-com.apple.CoreData.ConcurrencyDebug 1を追加して有効にした状態でビルドします。

【Swift UI】Core Dataをマルチスレッドで扱う際の注意点!Managed Object Context

アプリを操作すると異なるスレッドでContextが参照された際にEXC_BREAKPOINTなどのエラーが発生して教えてくれるようになりました。

マルチスレッドで実行すると発生するエラー

マルチスレッドでコンテキストが利用されている場合に必ずエラーが発生するわけではないようで、正常に動作する場合やアプリがクラッシュする場合など状況に応じて変化するようです。

クラッシュはしないがXcodeのログに以下のようなエラーが表示されたり

CoreData: error: NULL _cd_rawData but the object is not being turned into a fault

以下のエラーが発生しアプリがクラッシュすることもありました。

EXC_BAD_ACCESS (code=1, address=0xb91088020)

ご覧いただきありがとうございました。

searchbox

スポンサー

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑
今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index