如何使用Core Data和iCloud在两个设备之间同步数据?

16
我希望实现的目标是,当用户在“持久存储”上创建、更改或删除数据时,它将与“iCloud存储”同步,然后更新其他登录到相同iCloud账户的设备。我已经使用Objective-C资源创建了一个“Core Data Stack”,并尝试使用Swift编写自己的代码,但是我在使用两个登录到相同iCloud账户的设备同步数据时遇到了问题。
例如,当“iDevice A”登录到iCloud时,它会将数据备份到iCloud,但是当“iDevice B”登录到iCloud时,应用程序会删除持久存储中已有的任何数据以保存iCloud备份,并且存储设备之间的任何更改不会出现在其他设备上,但似乎保存到了iCloud存储作为最新备份,因此如果该应用程序被删除并重新安装,则会看到由另一台设备制作的最新备份 - 请注意,如果“iDevice B”已经登录,则不会使用“iDevice A”的数据,除非重新安装了应用程序并由其他设备进行了最后备份。
有人知道我在“Core data stack”中的哪里出错了,无法同步使用同一iCloud账户的两个设备之间的数据吗? Core Data Stack:
// MARK: - Core Data stack
// All the following code is in my appDelgate Core data stack 

func observeCloudActions(persistentStoreCoordinator psc:      NSPersistentStoreCoordinator?) {
// Register iCloud notifications observers for;

//Stores Will change 
//Stores Did Change 
//persistentStoreDidImportUbiquitousContentChanges
//mergeChanges

}

//Functions for notifications

func mergeChanges(notification: NSNotification) {
NSLog("mergeChanges notif:\(notification)")
if let moc = managedObjectContext {
    moc.performBlock {
        moc.mergeChangesFromContextDidSaveNotification(notification)
        self.postRefetchDatabaseNotification()
    }
 }
}

  func persistentStoreDidImportUbiquitousContentChanges(notification: NSNotification) {
self.mergeChanges(notification);
 }    

func storesWillChange(notification: NSNotification) {
NSLog("storesWillChange notif:\(notification)");
if let moc = self.managedObjectContext {
    moc.performBlockAndWait {
        var error: NSError? = nil;
        if moc.hasChanges && !moc.save(&error) {
            NSLog("Save error: \(error)");
        } else {
            // drop any managed objects
        }
        moc.reset();
    }

       NSNotificationCenter.defaultCenter().postNotificationName("storeWillChange", object: nil)

 }
}

 func storesDidChange(notification: NSNotification) {
NSLog("storesDidChange posting notif");
self.postRefetchDatabaseNotification();
//Sends notification to view controllers to reload data      NSNotificationCenter.defaultCenter().postNotificationName("storeDidChange", object: nil)

 }

func postRefetchDatabaseNotification() {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
          NSNotificationCenter.defaultCenter().postNotificationName("storeDidChange", object: nil)

 })
 }


// Core data stack 

lazy var applicationDocumentsDirectory: NSURL = {
// The directory the application uses to store the Core Data store file. This code uses a directory named "hyouuu.pendo" in the application's documents Application Support directory.
let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
return urls[urls.count-1] as! NSURL
}()

lazy var managedObjectModel: NSManagedObjectModel = {
// The managed object model for the application. This property is not optional. It is a fatal error for the application not to be able to find and load its model.
let modelURL = NSBundle.mainBundle().URLForResource("AppName", withExtension: "momd")!
NSLog("modelURL:\(modelURL)")
return NSManagedObjectModel(contentsOfURL: modelURL)!
}()

lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator? = {
// The persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.
// Create the coordinator and store
var coordinator: NSPersistentStoreCoordinator? = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)


let documentsDirectory = NSFileManager.defaultManager().URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask).last as! NSURL


let storeURL = documentsDirectory.URLByAppendingPathComponent("CoreData.sqlite")

NSLog("storeURL:\(storeURL)")

let storeOptions = [NSPersistentStoreUbiquitousContentNameKey:"AppStore"]

var error: NSError? = nil
var failureReason = "There was an error creating or loading the application's saved data."

if coordinator!.addPersistentStoreWithType(
    NSSQLiteStoreType,
    configuration: nil,
    URL: storeURL,
    options: storeOptions,
    error: &error) == nil
{
    coordinator = nil
    // Report any error we got.
    let dict = NSMutableDictionary()
    dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
    dict[NSLocalizedFailureReasonErrorKey] = failureReason
    dict[NSUnderlyingErrorKey] = error
    error = NSError(domain: "Pendo_Error_Domain", code: 9999, userInfo: dict as [NSObject : AnyObject])
    // Replace this with code to handle the error appropriately.
    // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
    NSLog("AddPersistentStore error \(error), \(error!.userInfo)")
}

self.observeCloudActions(persistentStoreCoordinator: coordinator)

return coordinator
}()

lazy var managedObjectContext: NSManagedObjectContext? = {
// Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.) This property is optional since there are legitimate error conditions that could cause the creation of the context to fail.
let coordinator = self.persistentStoreCoordinator
if coordinator == nil {
    return nil
}
var managedObjectContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.MainQueueConcurrencyType)
managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}()

2
我建议您尝试下载下面的示例应用程序,看看能否使它们正常工作 - 如果有任何问题,请告诉我,我会帮助您。一旦您让它们正常工作,您可以尝试找出您所做的不同之处并进行更新 - 有很多因素可能会导致问题。请记住,从早期版本开始以避免在开始时过于复杂。 http://ossh.com.au/design-and-technology/software-development/sample-library-style-ios-core-data-app-with-icloud-integration/ - Duncan Groenewald
@DuncanGroenewald 感谢您提供的资源,我一直在研究各种项目文件并学习您的工作 - 您做得非常好!!这是目前最好的资源,苹果没有为新开发者提供帮助。我已经下载了“CoreDataStackManager.swift”文件,但不确定如何在我的项目中使用它,我有一个新的Swift 2.0核心数据应用程序来测试您的代码。我应该用文件核心替换我的核心数据堆栈还是创建实例来使用函数?(我是一名新的iOS开发者) - RileyDev
我已经开始了一个用纯Swift编写的示例应用程序的存储库,地址在https://github.com/duncangroenewald/Core-Data-Sample-App。 - Duncan Groenewald
2个回答

2
这是我的设置方式,它同步了我的核心数据并保持更改同步。

enter image description here

这是来自我的AppDelegate。
    lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator? = {
    // The persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it. This property is optional since there are legitimate error conditions that could cause the creation of the store to fail.
    // Create the coordinator and store
    var coordinator: NSPersistentStoreCoordinator? = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
    let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("APPNAME.sqlite")
    var error: NSError? = nil
    var failureReason = "There was an error creating or loading the application's saved data."
    // iCloud store
    var storeOptions = [NSPersistentStoreUbiquitousContentNameKey : "APPNAMEStore",NSMigratePersistentStoresAutomaticallyOption: true,
        NSInferMappingModelAutomaticallyOption: true]
    // iCloud storeOptions need to be added to the if statement
    do {
        try coordinator!.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: NSURL.fileURLWithPath(url.path!), options: storeOptions)
    } catch var error1 as NSError {
        error = error1
        coordinator = nil
        // Report any error we got.
        var dict = [String: AnyObject]()
        dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
        dict[NSLocalizedFailureReasonErrorKey] = failureReason
        dict[NSUnderlyingErrorKey] = error
        error = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
        // Replace this with code to handle the error appropriately.
        // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
        NSLog("Unresolved error \(error), \(error!.userInfo)")
        abort()
    } catch {
        fatalError()
    }

    return coordinator
}()

    // MARK: - iCloud
// This handles the updates to the data via iCLoud updates

func registerCoordinatorForStoreNotifications (coordinator : NSPersistentStoreCoordinator) {
    let nc : NSNotificationCenter = NSNotificationCenter.defaultCenter();

    nc.addObserver(self, selector: "handleStoresWillChange:",
        name: NSPersistentStoreCoordinatorStoresWillChangeNotification,
        object: coordinator)

    nc.addObserver(self, selector: "handleStoresDidChange:",
        name: NSPersistentStoreCoordinatorStoresDidChangeNotification,
        object: coordinator)

    nc.addObserver(self, selector: "handleStoresWillRemove:",
        name: NSPersistentStoreCoordinatorWillRemoveStoreNotification,
        object: coordinator)

    nc.addObserver(self, selector: "handleStoreChangedUbiquitousContent:",
        name: NSPersistentStoreDidImportUbiquitousContentChangesNotification,
        object: coordinator)
}

非常感谢您的回答,很高兴它是在Swift 2中!但我目前正在使用Swift 1.1,并且仍在努力转换我的代码(150多个错误)。您在哪里调用函数以注册通知? - RileyDev
你的应用程序在App Store中,是否遇到了这些问题?如果没有,我强烈建议现在转移到Swift 2。因为你将不得不重新开始修复一些东西。我提供的代码是你需要同步核心数据的全部内容。Xcode 7/Swift 2和iOS 9将在不到一周内发布。 - MwcsMac
你说得对,我已经开始转换我的项目了,现在我计划在iOS9发布时推出这个应用程序——前提是iCloud的一切都顺利。当你说同步时,如果我在一个设备上删除数据,它会在另一个设备上删除吗?因为它们应该使用相同的云持久存储。还有,你在哪里调用通知函数? - RileyDev
如果添加一个,它会在另一个上显示“是”,如果删除它,则从另一个中删除。 - MwcsMac
我已经创建了我的WillChange、DidChange和handleStoreChanges函数,但是什么是WillRemove Notification?通过苹果文档,我找不到任何示例。 - RileyDev
@JKSDEV 发现了一段可能有用的代码。试着看看这个,还可以观看这个视频 - MwcsMac

1
我看到你的代码和我的做法有所不同:
1)我没有看到你添加了一个观察者来监听NSPersistentStoreDidImportUbiquitousContentChangesNotification,就像下面的代码一样:
let nc : NSNotificationCenter = NSNotificationCenter.defaultCenter();
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
nc.addObserver(self, selector: "dataUpdated:",
        name: NSPersistentStoreDidImportUbiquitousContentChangesNotification,
        object: self.managedObjectContext?.persistentStoreCoordinator)

2) 在我的代码中,捕获通知的函数(在这种情况下为dataUpdated)在更新显示以显示新数据之前,使用以下代码重置托管对象上下文:

let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
if let managedObjectContext = appDelegate.managedObjectContext {
     managedObjectContext.reset()
}

重置托管对象上下文后,我使用以下代码再次获取实体:
let fetchRequest = NSFetchRequest(entityName: "ClassNameHere")
let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as! [ClassNameHere]

你说“你的代码”时,你指的是谁的代码? - MwcsMac
我指的是JKSDEV的代码。你(MwcsMac)的示例代码确实包括了通知的注册。我认为问题最有可能是数据需要重置、重新获取并更新显示。 - Rich G

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接