通知太多 CoreData/CloudKit 同步

Too Many Notifications CoreData/CloudKit Sync

我有几个使用 CoreData/iCloud 同步的应用程序,它们在启动时都会收到大量 update/change/insert/delete/etc 通知,有时 运行 没有对基础数据进行任何更改。添加或删除新项目时,似乎我又收到了所有内容的通知。连通知的数量也不一致

我的问题是,如何避免这种情况?一旦我确定我的设备上的所有内容都是最新的,是否有一个可以应用的截止日期。

坚持

import Foundation
import UIKit
import CoreData

struct PersistenceController {
    let ns = NotificationStuff()
    static let shared = PersistenceController()

    static var preview: PersistenceController = {
        let result = PersistenceController(inMemory: true)
        let viewContext = result.container.viewContext
        for _ in 0..<10 {
            let newItem = Item(context: viewContext)
            newItem.stuff = "Stuff"
            newItem.timestamp = Date()
        }
        do {
            try viewContext.save()
        } catch {
            let nsError = error as NSError
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        }
        return result
    }()

    let container: NSPersistentCloudKitContainer

    init(inMemory: Bool = false) {
        container = NSPersistentCloudKitContainer(name: "TestCoreDataSync")
        if inMemory {
            container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
        }
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        
    }
}

class NotificationStuff
{
    var changeCtr = 0
    
    init()
    {
        NotificationCenter.default.addObserver(self, selector: #selector(self.processUpdate), name: Notification.Name.NSPersistentStoreRemoteChange, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(contextDidSave(_:)), name: Notification.Name.NSManagedObjectContextDidSave, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(contextObjectsDidChange(_:)), name: Notification.Name.NSManagedObjectContextObjectsDidChange, object: nil)
    }
    
    @objc func processUpdate(_ notification: Notification)
    {
        //print(notification)
        DispatchQueue.main.async
        { [self] in
            observerSelector(notification)
        }
    }
    
    @objc func contextObjectsDidChange(_ notification: Notification)
    {
       DispatchQueue.main.async
        { [self] in
            observerSelector(notification)
        }
    }
    
    @objc func contextDidSave(_ notification: Notification)
    {
        DispatchQueue.main.async
        {
            self.observerSelector(notification)
        }
    }
    
    func observerSelector(_ notification: Notification) {
        
        DispatchQueue.main.async
        { [self] in
            if let insertedObjects = notification.userInfo?[NSInsertedObjectsKey] as? Set<NSManagedObject>, !insertedObjects.isEmpty
            {
                print("Insert")
            }
            
            if let updatedObjects = notification.userInfo?[NSUpdatedObjectsKey] as? Set<NSManagedObject>, !updatedObjects.isEmpty
            {
                changeCtr = changeCtr + 1
                print("Change \(changeCtr)")
            }
            
            if let deletedObjects = notification.userInfo?[NSDeletedObjectsKey] as? Set<NSManagedObject>, !deletedObjects.isEmpty
            {
                print("Delete")
            }
            
            if let refreshedObjects = notification.userInfo?[NSRefreshedObjectsKey] as? Set<NSManagedObject>, !refreshedObjects.isEmpty
            {
                print("Refresh")
            }
            
            if let invalidatedObjects = notification.userInfo?[NSInvalidatedObjectsKey] as? Set<NSManagedObject>, !invalidatedObjects.isEmpty
            {
                print("Invalidate")
            }
            
            
            let mainManagedObjectContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
            guard let context = notification.object as? NSManagedObjectContext else { return }
            
            // Checks if the parent context is the main one
            if context.parent === mainManagedObjectContext
            {
                
                // Saves the main context
                mainManagedObjectContext.performAndWait
                {
                    do
                    {
                        try mainManagedObjectContext.save()
                    } catch
                    {
                        print(error.localizedDescription)
                    }
                }
            }
        }
    }
}

内容视图

import SwiftUI
import CoreData

struct ContentView: View {
    @State var stuff = ""
    @Environment(\.managedObjectContext) private var viewContext

    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
        animation: .default)
    private var items: FetchedResults<Item>

    var body: some View {
        VStack
        {
            TextField("Type here", text: $stuff,onCommit: { addItem(stuff: stuff)
                stuff = ""
            })
            List {
                ForEach(items) { item in
                    Text(item.stuff ?? "??")
                }
                .onDelete(perform: deleteItems)
            }
        }.padding()
    }

   private func addItem(stuff: String) {
        withAnimation {
            let newItem = Item(context: viewContext)
            newItem.timestamp = Date()
            newItem.stuff = stuff

            do {
                try viewContext.save()
            } catch {
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
    }

    private func deleteItems(offsets: IndexSet) {
        withAnimation {
            offsets.map { items[[=12=]] }.forEach(viewContext.delete)

            do {
                try viewContext.save()
            } catch {
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
    }
}

private let itemFormatter: DateFormatter = {
    let formatter = DateFormatter()
    formatter.dateStyle = .short
    formatter.timeStyle = .medium
    return formatter
}()

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
    }
}

数据库有一个带有时间戳字段和名为 stuff 的字符串字段的 Item 实体。

这取决于它是分别用于在系统控制台或 Xcode 的控制台中检查生产版本还是调试版本。

对于生产版本,我的理解是通过始终如一地使用类似以下内容使我的消息更容易找到(而不是 de-emphasising/hiding 其他消息):

let log = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "YourCategorisationOfMessagesGoingToThisHandle")

然后在代码中我可能有类似

的东西

log.debug("My debug message") log.warning("My warning etc")

fwiw:我倾向于根据文件所在的文件对内容进行分类,因为这是确定性的并且可以帮助我找到文件,所以我的源文件往往以

开头

fileprivate let log = Logger(subsystem: Bundle.main.bundleIdentifier!, category: #file.components(separatedBy: "/").last ?? "")

如果我这样做,那么我可以轻松地过滤系统的控制台消息以找到与我的应用程序相关的内容。

here.

上有更多关于如何使用它和控制台在系统控制台中过滤应用消息的信息

对于调试版本和 Xcode 控制台,可以使用来自我的应用程序的相同一致的应用程序日志消息,例如我的应用程序的调试消息总是以“一些容易找到的字符串或其他”开头。我认为没有办法有选择地 throttle/cut-off 回复。但是绝对有可能完全关闭来自许多嘈杂子系统的调试消息(一旦它们可靠地工作就很高兴)

对于提到的核心数据和 CloudKit 案例,如果我 运行 Debug 使用 -com.apple.CoreData.Logging.stderr 0-com.apple.CoreData.CloudKitDebug 0 启动参数构建,那么 Xcode的控制台更安静 很多 :-)。关于如何在

的 SO 回答中进行设置的很好的说明

我的问题是 CoreData -> CloudKit 集成是 re-synching 一遍又一遍的相同项目,因此通知。我发现我需要为所有实体的 modifiedTimestamp 添加一个排序索引。现在事情变得更快了,而且几乎没有 re-synched 项。