UITabBarController应用程序中的iCloud同步的核心数据堆栈实现(Swift 1.2)

时间:2015-04-13 19:48:58

标签: swift core-data sync icloud nsmanagedobjectcontext

我花了4天时间尝试使用iCloud同步为我的Swift 1.2应用程序实现正确的核心数据堆栈,但我真的可以使用一些帮助。

之前,我正在使用从应用程序中的任何位置访问的全局托管上下文;知道这是一个糟糕的实现,现在我添加iCloud同步我决定摆脱它,即使应用程序工作正常。

到目前为止,我已经实现了一个新的,有效的核心数据堆栈,它在设备之间具有不错但不完美的云同步。

现在我面临两个问题:

  • 有时,一些物品不会同步。
  • 鉴于我的应用程序的特定结构,我稍后会解释,我不知道我的代码中应该如何以及在何处处理用户登录或退出iCloud时Core Data发送的通知

但是,在解决这些问题之前,我非常感谢 - 如果合适的话 - 对我迄今为止所完成的工作进行了一些验证,这主要是为了确认我写的这些:因为我已经花了很多时间来改变我的核心数据堆栈,所以在前进之前我想知道我是否正确地传播了上下文(我的应用程序的结构没有#&# 39; t符合我在网上找到的任何教程,所以我不得不即兴发挥,或者如果我犯了一些会影响可靠同步或未来发展的基本错误

我的应用程序结构如下:

  • UITabBarViewController作为初始ViewController
    • 第一个标签:UIViewController(应用启动时显示)
    • 第二个标签:嵌入UITableViewController
    • UINavigationController
    • 第三个标签:另一个UITableViewController嵌入另一个UINavigationController

我有一个 CoreDataStack.swift 类,其代码如下:

import CoreData

@objc class CoreDataStack : Printable {

    let context : NSManagedObjectContext
    let psc : NSPersistentStoreCoordinator
    let model : NSManagedObjectModel
    let store : NSPersistentStore?

    var description : String {
        return "context: \(context)\n" + "model: \(model)"
    }

    var applicationDocumentsDirectory : NSURL = {
        let fileManager = NSFileManager.defaultManager()
        let urls = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask) as! [NSURL]
        return urls[0]
        }()

    init() {

        let modelURL = NSBundle.mainBundle().URLForResource("MyDataModel", withExtension:"momd")
        model = NSManagedObjectModel(contentsOfURL: modelURL!)!
        psc = NSPersistentStoreCoordinator(managedObjectModel: model)
        context = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.MainQueueConcurrencyType)
        context.persistentStoreCoordinator = psc
        let documentsURL = applicationDocumentsDirectory
        let storeURL = documentsURL.URLByAppendingPathComponent("MyApp.sqlite")

        let options = [NSPersistentStoreUbiquitousContentNameKey: "MyApp", NSMigratePersistentStoresAutomaticallyOption: true, NSInferMappingModelAutomaticallyOption: true]
        var error: NSError? = nil
        var failureReason = "There was an error creating or loading the application's saved data."
        store = psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: options, error:&error)

        if store == nil {
            let dict = NSMutableDictionary()
            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 as [NSObject : AnyObject])
            println("Error adding persistent store: \(error), \(error!.userInfo)")
            abort()
        }
    }

    func saveContext() {
        var error: NSError? = nil
        if context.hasChanges && !context.save(&error) {
            println("Could not save: \(error), \(error!.userInfo)")
        }
    }

    var updateContextWithUbiquitousContentUpdates: Bool = false {
        willSet {
            ubiquitousChangesObserver = newValue ? NSNotificationCenter.defaultCenter() : nil
        }
    }

    private var ubiquitousChangesObserver : NSNotificationCenter? {
        didSet {
            oldValue?.removeObserver(self, name: NSPersistentStoreDidImportUbiquitousContentChangesNotification, object: psc)
            ubiquitousChangesObserver?.addObserver(self, selector: "persistentStoreDidImportUbiquitousContentChanges:", name: NSPersistentStoreDidImportUbiquitousContentChangesNotification, object: psc)
        }
    }

    func persistentStoreDidImportUbiquitousContentChanges(notification: NSNotification) {
        println("Merging ubiquitous content changes")
        context.performBlock {
            self.context.mergeChangesFromContextDidSaveNotification(notification)
        }
    }
}

在我的 AppDelegate.swift 中,我在var window: UIWindow?下添加了以下代码:

lazy var coreDataStack = CoreDataStack()

coreDataStack.updateContextWithUbiquitousContentUpdates = true

// The following code is the way I found to propagate the managed context of the stack instantiated above in all the ViewControllers of the UITabBarController, including those embedded in the two NavigationControllers;
// since in the future I'll probably need some flexibility  in term of adding / rearranging the VCs in the TabBar, I kind of like this way to pass around the context.
// I could have also passed the context to the CustomTabBarViewController and from there do the same thing, but I figured I could just pass the context from AppDelegate, since I already can access all the ViewControllers from here with the following code.
var tabBarController = self.window!.rootViewController as! CustomTabBarViewController
for eachViewController in tabBarController.viewControllers! {
    if eachViewController.isKindOfClass(CustomViewController){
        (eachViewController as! CustomViewController).passedManagedContext = coreDataStack.context // Context is passed to the VC of 1st tab
    }
    if eachViewController.isKindOfClass(UINavigationController){
        var firstNavController = tabBarController.viewControllers![1] as! UINavigationController
        for tvc in firstNavController.viewControllers! {
            if tvc.isKindOfClass(FirstCustomTableViewController) {
                (tvc as! FirstCustomTableViewController).passedManagedContext = coreDataStack.context // Context is passed to the TableVC inside the NavigationController in tab 2
            }
        }
        var secondNavController = tabBarController.viewControllers![2] as! UINavigationController
        for tvc in secondNavController.viewControllers! {
            if tvc.isKindOfClass(SecondCustomTableViewController) {
                (tvc as! SecondCustomTableViewController).passedManagedContext = coreDataStack.context // Context is passed to the TableVC inside the NavigationController in tab 3
            }
        }
    }
}

// Of course, in applicationDidEnterBackground: and applicationWillTerminate: I save the context; obviously, I also save the context, when appropriate, from the other ViewControllers.

有了这个结构,我在AppDelegate中实例化我的堆栈,并从那里将它传播到TabBar的3个元素;从那些,我再次将上下文传播到我出现的每个其他ViewController。我在任何地方都记录了上下文,我可以确认它始终是相同的。

事实上,使用此代码的应用程序可以正常工作。

我无法说这是完美的,因为正如我所说,有时一些物体不会同步,但我怀疑这些物体不同步的原因是另一个(简而言之,我有2 {{子类;子类1的对象具有子类2的对象作为属性;如果我使用现有的subclass2对象作为属性创建新的子类1对象,则同步很好;如果我还创建新的子类2对象,则保存并立即设置它作为subclass1的属性,有时子类2对象不会在另一个设备上同步,而子类1会做,然后错过该属性......我可以在以后处理它。)

在深入研究这个同步问题之前,我真的很想知道我到目前为止用堆栈完成的工作是否有意义,或者它是否很糟糕且需要固定< /强>

然后,如果上面的所有代码都不可怕,并且如果偶然错过同步对象的原因会变成我怀疑的那个,那么另一个问题就出现了,这是一个很大的问题:我是否设置代码来处理用户从iCloud(NSManagedObjectNSPersistentStoreCoordinatorStoresWillChangeNotification)登录或退出时发生的通知? 我尝试在我的AppDelegate和我的CoreDataStack类中基于Core Data by Tutorials书,编写了我已经编写的方法(没有实际功能,目前我只是在控制台上记录了一些东西,知道我到了那里),但是在这两种情况下当我在应用程序运行时登录或退出iCloud时,应用程序在控制台中没有一行崩溃,所以我不知道这个问题。

也许我应该把方法放在所有ViewControllers中处理这些通知,因为获取请求发生在那里并且UI从这些类更新,但是我没有传递整个coreDataStack对象,只传递上下文。 ..所以我错过了什么。 我应该通过整个堆栈,而不仅仅是上下文?可以从我的CoreDataStack处理这些通知,还是应该从AppDelegate处理?

真的很感激任何帮助......

提前致谢,请原谅我的问题不清楚(我是一个初学者,英语不是我的主要语言......)。

另外,感谢您抽出时间阅读这个长期的问题!

@ cdf1982

1 个答案:

答案 0 :(得分:0)

我认为问题在于iCloud + CD从未正常工作。它不是开发人员代码问题,问题是Apple实现的iCloud + CD只是失败了。