2
3

More than 1 year has passed since last update.

Core Dataでの書き込み時にnewBackgroundContextに対する命令はperform/performAndWait内でないといけない

Last updated at Posted at 2021-10-18

はじめに

Core Dataでの書き込み時にnewBackgroundContextメソッドで書き込み用コンテキストを利用することがあると思いますが、そのコンテキストに対してperform/performAndWait外からfetchcountなどでリクエストを送ってはいけません。perform/performAndWaitで実行が保護されるキュー内でコンテキストにリクエストを送るべきのようです。

AppleのサンプルコードであるHandling Different Data Types in Core Dataを駄目な感じに改変し、挙動を確認してみます。

スクリーンショット 2021-10-14 18.46.38.png

もしかしたら私が勘違いしてるのかもしれないのでそういうのがあればコメント、それが無理なら他のブログなどでご意見ください。

公式の説明

デバッグオプション

デバッグオプションでアサート-com.apple.CoreData.ConcurrencyDebug 1を有効にするのをおすすめします。

下記はWWDCの動画から。
スクリーンショット 2021-10-18 14.22.59.png

And while the sanitizers are useful in all contexts, I also wanna highlight that Core Data provides a special runtime flag that you can enable to get more domain-specific help. By enabling this option, Core Data will turn on a number of useful assertions to validate internal locks and confirm appropriate use of various Core Data types.

サニタイザーはあらゆる場面で役立ちますが、Core Dataは特別なランタイムフラグを提供しており、これを有効にすることでよりドメインに特化したヘルプを得られることも強調しておきたいと思います。このオプションを有効にすると、Core Dataは内部ロックの検証や様々なCore Data型の適切な使用を確認するために、多くの有用なアサーションを有効にすることができる。

このほかにもスレッドサニタイザーをオンにすると良いとのこと

スクリーンショット 2022-01-04 13.13.28.png

Further, NSManagedObjectContext is not the only type in Core Data that supports performing tasks within its protected concurrency domain. We're also adding similar API to both NSPersistentContainer and NSPersistentStoreCoordinator. The general shape and behavior of these APIs are quite similar to what I've already described. But with all that concurrent power, I would be remiss to not offer the advice of using existing debugging tools available at your disposal. Of course, the Xcode-provided address and thread sanitizers are incredibly helpful for catching bugs you might not even know existed. These can both be found in the Diagnostics pane of the scheme editor's Run settings. Each sanitizer detects different kinds of issues, including validating safe memory use assumptions and appropriate use of data from multiple threads. It's always a good idea to qualify your applications and their associated tests with both sanitizers before you release your software to your community of users.

さらに、NSManagedObjectContextは、Core Dataの中で、保護された並行処理領域内でタスクを実行することをサポートする唯一の型ではありません。NSPersistentContainerとNSPersistentStoreCoordinatorの両方にも同様のAPIを追加している。これらのAPIの一般的な形と動作は、すでに説明したものとよく似ています。しかし、これだけの同時実行力を持つのですから、自由に使える既存のデバッグ・ツールを使うというアドバイスをしないのは不注意でしょう。もちろん、Xcodeが提供するアドレスサニタイザーとスレッドサニタイザーは、あなたが存在することさえ知らないかもしれないバグをキャッチするために信じられないほど便利です。これらは両方とも、スキーム・エディタの実行設定の診断ペインで見つけることができます。それぞれのサニタイザーは、安全なメモリ使用の仮定や複数のスレッドからのデータの適切な使用の検証を含む、異なる種類の問題を検出します。ソフトウェアをユーザーのコミュニティにリリースする前に、両方のサニタイザーでアプリケーションとその関連テストを検証することは常に良いアイデアです。

private Queue

NSManagedObjectContextConcurrencyType.privateQueueConcurrencyTypeによって作成されるプライベートキューコンテキストは、初期化時に独自のキューを作成し、そのキューでのみ使用することができます。キューはプライベートで NSManagedObjectContext インスタンスの内部にあるため、perform(_:) および performAndWait(_:) メソッドを通じてのみアクセスできます。

実例

まずメソッドの呼び出し元のAppDelegateで次のようにnewBackgroundContextからコンテキストを生成しgenerateSampleDataIfNeededに渡しているのはそのままです。

SampleData.generateSampleDataIfNeeded(context: container.newBackgroundContext())

該当する部分、正しく動作しているそのままん状態が下記です。

struct SampleData {
    ...

    static func generateSampleDataIfNeeded(context: NSManagedObjectContext) {
        context.perform {
            // これはサンプルそのまま 👍
            guard let number = try? context.count(for: Book.fetchRequest()), number == 0 else {
                return
            }

            for day in stride(from: 1, to: 365, by: 7) {
                dateComponents.day = day
                self.generateOneNewBook(with: dateComponents, context: context)
            }
            do {
                try context.save()
            } catch {
                print("Failed to saving test data: \(error)")
            }
        }
    }
}

これを改変し、コンテキストへの命令であるcountメソッド呼び出しをperformの外に出す駄目な例です。

struct SampleData {
    ...

    static func generateSampleDataIfNeeded(context: NSManagedObjectContext) {
        // performAndWaitの外に出してcontextに対してfetchRequestを渡す 👎
        guard let number = try? context.count(for: Book.fetchRequest()), number == 0 else {
            return
        }

        context.perform {
            for day in stride(from: 1, to: 365, by: 7) {
                dateComponents.day = day
                self.generateOneNewBook(with: dateComponents, context: context)
            }
            do {
                try context.save()
            } catch {
                print("Failed to saving test data: \(error)")
            }
        }
    }
}

上記に述べたデバッグオプションを有効にすると実行時にクラッシュさせてくれるので開発時に気がつけます。

ちなみに、
このコンテキストへの命令自体がperform/perfromAndWaitの外に置いてるのが駄目なのであって、Book.fetchRequest()自体はどこでリクエストを組み立てようと関係はありません。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3