web-dev-qa-db-ja.com

iOS-メインアプリのコアデータの変更について、今日の拡張機能に通知する

私はNSManagedObjectと呼ばれるEventをホストアプリと今日の拡張機能の間で共有しています。 (ターゲットメンバーシップでは、メインアプリとウィジェットの両方がチェックされます)。

ホストアプリとウィジェットは同じアプリグループ識別子を持ち、両方がData Model(ターゲットメンバーシップでは、メインアプリとウィジェットの両方がチェックされます)。

Xcodeでウィジェットを起動(実行)すると、ホストアプリに既に保存されているすべてのアプリイベント(Event)が表示されます。ただし、新しいイベントを追加すると、ホストアプリには表示されますが、today-widgetには表示されません。ウィジェットを再起動すると、以前はなかった最後のイベントを含むすべてのイベントが表示されます。

これは、イベントをフェッチするメソッドです。ウィジェットのTodayViewControllerで定義されています。

private  func fetchEvents(date: Date) {
    let predicates = NSCompoundPredicate(andPredicateWithSubpredicates: [
        NSPredicate(format: "date = %@",Date().startOfDay as CVarArg),
        NSPredicate(format: "startTime >= %@", Date() as CVarArg)
    ])
    if let ev  = try? TPEvent.fetchAll(predicates: predicates, in: persistentManager.context) {
        events = ev
    }
} 

このイベントはviewWillAppearおよびwidgetPerformUpdateで呼び出されます。

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    fetchEvents(date: Date())
    self.tableView.reloadData()
}


func widgetPerformUpdate(completionHandler: (@escaping (NCUpdateResult) -> Void)) {
    self.fetchEvents(date: Date() )
    self.tableView.reloadData()
    completionHandler(NCUpdateResult.newData)
}

persistentManaged.contextPersistentManager.shared.context(以下のコードを参照)。

ちなみに、今日のウィジェットを表示すると、上記の両方のメソッドが呼び出されます。私はこの問題を解明するために多くの時間を持っていますが、それを行うことができませんでした。

何が問題で、どのように修正するのでしょうか?

さらに情報が必要な場合や質問がある場合はコメントしてください

更新

シングルトンPersistentManagerを持っています。ホストアプリとウィジェットの両方でviewContextを使用します。

 public final class PersistentManager {

    init() {}

    public static let shared = PersistentManager()

    public lazy var persistentContainer: NSPersistentContainer = {

        let container = NSPersistentCloudKitContainer(name: "Event")

        guard let fileContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.event.data") else {
            fatalError("Shared file container could not be created.")
        }

        let storeURL = fileContainer.appendingPathComponent("Event.sqlite")
        let storeDescription = NSPersistentStoreDescription(url: storeURL)
        container.persistentStoreDescriptions = [storeDescription]

        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })

        container.viewContext.automaticallyMergesChangesFromParent = true

        do {
            try container.viewContext.setQueryGenerationFrom(.current)
        } catch {
            fatalError("###\(#function): Failed to pin viewContext to the current generation:\(error)")
        }

        return container
    }()

    public lazy var context = persistentContainer.viewContext

    // MARK: - Core Data Saving support
    public  func saveContext () {
        let context = persistentContainer.viewContext
        if context.hasChanges {
            do {
                try context.save()
            } catch {

                let nserror = error as NSError
                fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
            }
        }
    }
}
6
mahan

問題は、メインアプリとアプリ拡張機能がiOSで2つの異なるプロセスとして機能することです。

CoreDataは、メインのアプリプロセス内でのみ通知を送信するNotificationCenterと連携します。したがって、ここでプロセス間通知を送信する必要があります。

IOSでプロセス間通知を送信する1つのhidden方法は、UserDefaultsオブジェクトでKVOを使用することです。

NSUserDefaults.hヘッダーファイルAppleは、

/ *! NSUserDefaultsDidChangeNotificationは、現在のプロセス内でユーザーのデフォルトが変更されるたびにポストされますが、ユビキタスのデフォルトが変更されたとき、または外部のプロセスがデフォルトを変更したときはポストされません。 Key-Value監視を使用して、関心のある特定のキーのオブザーバーを登録すると、どこから更新されたかに関係なく、すべての更新が通知されます。 * /

これを指定すると、UserDefaultsの特定のキーでKVOを使用することにより、値の変更がアプリからエクステンションに、またはその逆に伝搬されると想定できます。

したがって、メインアプリでの変更ごとに、変更の現在のタイムスタンプをUserDefaultsに保存するというアプローチが考えられます。

/// When the change is made in the main app:
let defaults = UserDefaults(suiteName: "group.<your bundle id>")
defaults["LastChangeTimestamp"] = Date()
defaults.synchronize()

アプリの拡張機能で:

let defaults = UserDefaults(suiteName: "group.<your bundle id>")

func subscribeForChangesObservation() {
    defaults?.addObserver(self, forKeyPath: "LastChangeTimestamp", options: [.new, .initial], context: nil)
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    // Process your changes here.
}

deinit {
    defaults?.removeObserver(self, forKeyPath: "LastChangeTimestamp")
}
3
Eugene Dudnyk