太多通知CoreData/CloudKit同步



我有几个使用CoreData/iCloud同步的应用程序,它们在启动时都会收到大量更新/更改/插入/删除等通知,有时在运行时没有对底层数据进行任何更改。当添加或删除一个新项目时,我似乎会再次收到所有内容的通知。甚至通知的数量也不一致。

我的问题是,我如何避免这种情况?是否有一个截止日期,一旦我确定我已经在每个设备的基础上更新了所有内容。

持久性

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)
}
}
}
}
}
}

ContentView

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[$0] }.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)
}
}

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

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

对于生产版本,我的理解是目的是让我的消息更容易被找到(而不是去强调/隐藏其他消息),通过始终如一地使用如下内容:

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 ?? "")

如果我这样做,那么我可以很容易地过滤系统的控制台消息,找到与我的应用相关的东西。

有更多关于如何使用这个和控制台来过滤应用程序的消息在这里的系统控制台。

对于调试构建和Xcode控制台,可以使用来自我的应用程序的相同一致的应用程序日志消息,例如,我的应用程序的调试消息总是以"一些容易找到的字符串或其他"开头。我不相信有一种方法可以选择性地限制/切断反应。但是完全关闭来自许多嘈杂子系统的调试消息是完全可能的(一旦它们可靠地工作时)

对于提到的Core Data和CloudKit案例,如果我使用-com.apple.CoreData.Logging.stderr 0-com.apple.CoreData.CloudKitDebug 0启动参数运行Debug构建,那么这会使Xcode的控制台成为lot安静:-)。很好的说明如何在这里的SO答案中设置

我的问题是CoreData ->CloudKit集成一遍又一遍地重新同步相同的项目,因此通知。我发现我需要为所有实体上的modifiedTimestamp添加一个排序索引。现在事情快多了,几乎没有重新同步的项目。

最新更新