【Swift/Core Data】perform/performAndWaitの使い方!newBackgroundContext

【Swift/Core Data】perform/performAndWaitの使い方!newBackgroundContext

この記事からわかること

  • Swift/Core Dataperform/performAndWaitメソッド使い方
  • newBackgroundContext参照するときの注意
  • マルチスレッド対応

index

[open]

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

みんなの誕生日

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

posted withアプリーチ

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

環境

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

newBackgroundContextとは?

公式リファレンス:newBackgroundContext

func newBackgroundContext() -> NSManagedObjectContext

Core Dataでデータを操作する際に長時間かかる処理や大量のデータを処理する時などメインスレッドで実行したくない(UIをブロックしたくない)場合にはスレッドを分けて処理を実装することも多いと思います。しかしCore Dataはスレッドセーフではないためマルチスレッドでデータを扱う際には異なるスレッドから同一のContext(NSManagedObjectContext)を操作しないように注意が必要になります。Contextを異なるスレッドから参照しないようにする方法はいくつかあるようですがnewBackgroundContextメソッドもその1つです。

/// メインスレッドで使用するContext
private func makeContext() -> NSManagedObjectContext {
    let context = persistentContainer.viewContext
    context.automaticallyMergesChangesFromParent = true
    return context
}

/// バックグラウンドスレッドで使用するContext
private func makeBackgroundContext() -> NSManagedObjectContext {
    let context = persistentContainer.newBackgroundContext()
    context.automaticallyMergesChangesFromParent = true
    return context
}

newBackgroundContextメソッドはNSPersistentContainerの持つメソッドでバックグラウンドスレッド(プライベートキュー)用のContext(NSManagedObjectContext)を作成する役割を持っています。ここで生成されるContextはメインで使用しているContextとは別物なので、データを変更しても即座にメインのContextにマージされないため、適切なタイミングで明示的に変更を保存する必要があります。

またnewBackgroundContextで生成したContextからデータ取得や追加を行う際はperformまたはperformAndWaitブロックの中で操作する必要があるようです。

performメソッド

公式リファレンス:NSManagedObjectContext.performメソッド

func perform(_ block: @escaping () -> Void)

performNSManagedObjectContextが持つメソッドで指定されたコンテキストでブロック内の処理を実行する役割を持っています。非同期で処理を行い、指定されたコンテキストのスレッドで実行されます。

public func newEntity<T: NSManagedObject>(context: NSManagedObjectContext, completion: @escaping (T) -> Void) {
    context.perform {
        let entity = NSEntityDescription.entity(forEntityName: String(describing: T.self), in: context)!
        let newObject = T(entity: entity, insertInto: context)
        completion(newObject)
    }
}

performAndWaitメソッド

公式リファレンス:NSManagedObjectContext.performAndWaitメソッド

performAndWaitメソッドはperformと同じ役割を持っていますが、異なるのは同期的に実行される部分です。そのためブロック内の処理が完了するまで、現在のスレッドがブロックされるのでメインスレッドでの使用には注意が必要です。ただ利点として先ほど完了ハンドラーで受け取っていた結果を返り値として受け取ることができるようになります。

public func newEntity<T: NSManagedObject>(context: NSManagedObjectContext) -> T {
    var result: T!
    context.performAndWait {
        let entity = NSEntityDescription.entity(forEntityName: String(describing: T.self), in: context)!
        result = T(entity: entity, insertInto: context)
    }
    return result
}

マルチスレッドに対応したCore Data管理クラスを実装してみる

マルチスレッドに対応したCore Data管理クラスを実装してみました。DispatchQueueなどを使用してスレッドを変更してアプリがクラッシュしないか試してみてください。至らぬ点やもっとこうした方が良い点がありましたら教えていただけると助かります。

class CoreDataRepository {
    
    static let shared = CoreDataRepository()
    
    private static let persistentName = "MyCoreDataTest"
    
    private lazy var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: CoreDataRepository.persistentName)
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        container.viewContext.automaticallyMergesChangesFromParent = true
        return container
    }()
    
    /// メインスレッドで使用するContext
    private func makeContext() -> NSManagedObjectContext {
        let context = persistentContainer.viewContext
        context.automaticallyMergesChangesFromParent = true
        return context
    }
    
    /// バックグラウンドスレッドで使用するContext
    private func makeBackgroundContext() -> NSManagedObjectContext {
        let context = persistentContainer.newBackgroundContext()
        context.automaticallyMergesChangesFromParent = true
        return context
    }
    
    /// 新規作成
    public func newEntity<T: NSManagedObject>(onBackgroundThread: Bool = false, completion: @escaping (T) -> Void) {
        let context = onBackgroundThread ? makeBackgroundContext() : makeContext()
        context.perform {
            let entity = NSEntityDescription.entity(forEntityName: String(describing: T.self), in: context)!
            let newObject = T(entity: entity, insertInto: context)
            completion(newObject)
        }
    }
    
    /// 追加処理
    public func insert(_ object: NSManagedObject, onBackgroundThread: Bool = false) {
        let context = onBackgroundThread ? object.managedObjectContext ?? makeBackgroundContext() : makeContext()
        saveContext(context)
    }
    
    /// 削除処理
    public func delete(_ object: NSManagedObject, onBackgroundThread: Bool = false) {
        let context = onBackgroundThread ? object.managedObjectContext ?? makeBackgroundContext() : makeContext()
        context.delete(object)
        saveContext(context)
    }
    
    /// Contextに応じたSave
    private func saveContext(_ context: NSManagedObjectContext) {
        guard context.hasChanges else { return }
        do {
            try context.save()
            
        } catch let error as NSError {
            fatalError("Unresolved error \(error), \(error.userInfo)")
        }
    }

    /// 取得処理:fetchはContextを切り分ける必要がない?
    public func fetch<T: NSManagedObject>(completion: @escaping ([T]) -> Void) {
        let context = makeContext()
        let fetchRequest = NSFetchRequest<T>(entityName: String(describing: T.self))

        context.perform {
            do {
                let fetchedObjects = try context.fetch(fetchRequest)
                completion(fetchedObjects)
            } catch let error as NSError {
                print("Could not fetch. \(error), \(error.userInfo)")
                completion([])
            }
        }
    }
}

この管理クラスはGithubに挙げているので参考にしてください。

おすすめ記事:MyCoreDataTest

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

searchbox

スポンサー

ProFile

ame

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

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

New Article

index