0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Swift、Background Tasks】AppRefreshTasksでCoreDataを使う方法

Last updated at Posted at 2023-12-12

はじめに

この記事は、表題の通り、AppRefreshTasksを利用する際に、CoreDataのエンティティ取得など、データベース操作を行う際に自身がハマってしまったところを記載し、忘備録として記したものです。

理由など、突き詰めて記載できていませんが、他にドンピシャな記事がなく、自分自身色々時間がかかってしまったので、同じように躓いているどなたかの参考になればと思います。

AppRefreshTasksとは

バックグラウンドでの処理のことです。

普通にバックグラウンドでの処理を思い浮かべると、アプリがフォアグラウンドの状態からホーム画面に移動した際の、バックグラウンド時ことかと考えると思いますが、このAppRefreshTasksは事前にOSに実行タイミングを登録しておき、定期的に処理することをいいます。

以下に、他の方が詳しく記載されていますので、ご参考にどうぞ。

1. https://developer.apple.com/documentation/backgroundtasks

2. BackgroundTasks(AppRefreshTasks & ProcessingTasks)

3. https://grandbig.github.io/blog/2019/09/22/backgroundtasks/

本題

さて、本題ですが、このAppRefreshTasksの処理内でCoreDataのデータベースを参照したいな思った際は、大体以下のようなコードを記載するかと思います。

まずは、スケジュールの設定

AppDelegate.swift
private func scheduleAppProcessing() {
    let request = BGProcessingTaskRequest(identifier: "com.Sample.refresh")
    request.requiresNetworkConnectivity = false
    request.requiresExternalPower = true

    do {
        // スケジューラーに実行リクエストを登録
        try BGTaskScheduler.shared.submit(request)
    } catch {
        print("Could not schedule app processing: \(error)")
    }
}

func applicationDidEnterBackground(_ application: UIApplication) {
    // バックグラウンド起動に移ったときにルケジューリング登録
    scheduleAppProcessing()
}

続いて、Core DataはDAOでこんなように実装している方は多いのではないでしょうか?

DataDAO.swift
import Foundation
import CoreData
import UIKit

class DataDAO {
    
    /**
     CoreDataとのコネクション
     */
    private var manageObjectContext: NSManagedObjectContext
    
    init() {
        self.manageObjectContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
    }

    func getAll() -> [Data} {
        ・・・
        return dataList
    }

}

上記の状態で、実際のAppRefreshTasksの処理を記述するとこんな感じかと、

BGAppRefreshTasks.swift

static func handleAppRefresh(
        task: BGAppRefreshTask,
    ) async {
        
        // バックグラウンド処理が途中で打ち切られた場合
        task.expirationHandler = {
            
            // 予約されているタスクの確認
            BGTaskScheduler.shared.getPendingTaskRequests { requests in
                if requests.count == 0 {
                    
                    // 当日中の再バックグラウンド処理をシステムに登録
                    self.schedule(isCancel: true)
                }
            }
        }

        // 処理したいもの
        let dataList = DataDAO().getAll()
        
        ・・・

        // 再スケジューリング
        self.schedule()
        
        task.setTaskCompleted(success: true)
        
    }

上のように記述をすると、AppRefreshTasksの処理はバックグラウンドで処理をするため、Main Threadで処理するようエラーメッセージが出力されバックグラウンド処理がクラッシュしてしまいます。

Main Threadで怒られているのは以下の箇所

DataDAO.swift
    init() {
        self.manageObjectContext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
    }

エラーコードでググったりするとすぐ出てくるが、Core Dataをバックグラウンドで扱うときには、
performBackgroundTask(_:)

newBackgroundContext()
これらを使用するようにというドキュメントに辿り着くでしょう。

https://developer.apple.com/documentation/coredata/using_core_data_in_the_background

performBackgroundTask(_:)
https://developer.apple.com/documentation/coredata/nspersistentcontainer/1640564-performbackgroundtask

newBackgroundContext()
https://developer.apple.com/documentation/coredata/nspersistentcontainer/1640581-newbackgroundcontext

ではこれらを実際利用する場合、どう使うかというとパッと考えられるのは以下。

DataDAO.swift
import Foundation
import CoreData
import UIKit

class DataDAO {
    
    /**
     CoreDataとのコネクション
     */
    private var manageObjectContext: NSManagedObjectContext
    
    init() {
        self.manageObjectContext = (UIApplication.shared.delegate as! AppDelegate).persistentCloudContainer.viewContext
    }
    
    init(context: NSManagedObjectContext) {
        self.manageObjectContext = context
    }

}
BGAppRefreshTasks.swift

static func handleAppRefresh(
        task: BGAppRefreshTask,
    ) async {
        
        // バックグラウンド処理が途中で打ち切られた場合
        task.expirationHandler = {
            
            // 予約されているタスクの確認
            BGTaskScheduler.shared.getPendingTaskRequests { requests in
                if requests.count == 0 {
                    
                    // 当日中の再バックグラウンド処理をシステムに登録
                    self.schedule(isCancel: true)
                }
            }
        }

        // -------------
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentCloudContainer.newBackgroundContext()
        // -------------

        // 処理したいもの
        let dataList = DataDAO(context: context).getAll()
        
        ・・・

        // 再スケジューリング
        self.schedule()
        
        task.setTaskCompleted(success: true)
        
    }

しかし、これだと結局変わらず、今度はまた同じ箇所でMain Threadエラーを引き起こします。

BGAppRefreshTasks.swift
        // -------------
        let context = (UIApplication.shared.delegate as! AppDelegate).persistentCloudContainer.newBackgroundContext()
        // -------------

こんな状態で、さて、newBackgroundContextを使えと言われたものの、どう使えばいいんだ?状態に陥り、色々試行錯誤してました。

ですが、ある日、悩みながら別のコードをいじっていたところ、ふとなんとなく今の箇所に戻ってきて、なんとなくこんなようなコードを書いたところ、エラーが解消されましたwwww

それはこの箇所

AppDelegate.swift
@main
class AppDelegate: NSObject, UIApplicationDelegate {
    
    var window: UIWindow?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {

        ・・・

        // 第一引数: Info.plistで定義したIdentifierを指定
        // 第二引数: タスクを実行するキューを指定。nilの場合は、デフォルトのバックグラウンドキューが利用されます。
        // 第三引数: 実行する処理
        BGTaskScheduler.shared.register(forTaskWithIdentifier: "com.Sample.refresh", using: .main) { task in
            let context = self.persistentContainer.newBackgroundContext()
            Task {
                // バックグラウンド処理したい内容
                await BGAppRefreshTasks.handleAppRefresh(
                    task: task as! BGAppRefreshTask,
                    context: context
                )
            }
        }

        ・・・
        
        return true
    }

そうです、AppDelegate.swiftの起動時に設定したバックグラウンドの実行する処理を書くところでした。

0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?