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

Posted

技术标签:

【中文标题】UITabBarController 应用程序中 iCloud 同步的核心数据堆栈实现 (Swift 1.2)【英文标题】:Core Data stack implementation for iCloud sync in a UITabBarController app (Swift 1.2) 【发布时间】:2015-04-13 19:48:57 【问题描述】:

过去 4 天我一直在尝试为我的 Swift 1.2 应用实现一个适当的带有 iCloud 同步的核心数据堆栈,但我真的需要一些帮助。

以前,我使用的是从应用程序的任何地方访问的全局托管上下文;知道这是一个糟糕的实现,现在我添加了 iCloud 同步,我决定摆脱它,即使应用程序运行良好。

到目前为止,我已经实现了一个新的、可工作的 Core Data 堆栈,它在设备之间实现了不错但不完美的云同步。

现在我面临两个问题:

有时,一些对象不同步。 考虑到我的应用程序的特殊结构(稍后我会解释),我不知道我应该如何以及在我的代码中的何处处理用户登录或退出 iCloud 时 Core Data 发送的通知。

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

我的应用结构如下:

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 个NSManagedObject 子类; subclass1 的对象具有 subclass2 的对象作为属性;如果我使用现有的 subclass2 对象作为属性创建一个新的 subclass1 对象,则同步很好;如果我还创建了一个新的 subclass2 对象,请将其保存并立即将其设置为 subclass1 的属性,有时 subclass2 对象不会在其他设备上同步,而 subclass1 会然后错过该属性...我可以稍后再处理)。

在深入研究这个同步问题之前,我真的很想知道到目前为止我对堆栈所做的工作是否有意义,或者它是否很糟糕并且需要被屏蔽

然后,如果上面的所有代码都不可怕,并且偶尔丢失对象同步的原因是我怀疑的原因,那么另一个问题来了,这是一个大问题:在哪里当用户从 iCloud(NSPersistentStoreCoordinatorStoresWillChangeNotificationNSPersistentStoreCoordinatorStoresDidChangeNotification)登录或注销时,我应该放置代码来处理发生的通知吗? 我尝试在我的 AppDelegate 和我的 CoreDataStack 类中基于 Core Data by Tutorials 书将我编写的方法(没有实际功能,目前我只在控制台上记录一些内容以知道我到达了那里),但在两者中案例当我在应用程序运行时从 iCloud 登录或注销时,应用程序在控制台中没有一行就崩溃了,所以我不知道这个问题。

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

任何帮助将不胜感激......

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

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

@cdf1982

【问题讨论】:

我建议您将问题分解成更容易回答的小块。 【参考方案1】:

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

【讨论】:

以上是关于UITabBarController 应用程序中 iCloud 同步的核心数据堆栈实现 (Swift 1.2)的主要内容,如果未能解决你的问题,请参考以下文章

保持 UITabbarController 在每个视图中可见

导航中实例的不确定性:UITabBarController

从 UITabBarController 应用程序中的另一个 UIViewController 调用 UISplitViewController

UISplitViewController 和 UITabBarController 在通用应用程序的 MasterViewController 中

在 UITabBarController 中呈现模态视图

UITabBarController 上的 WYPopoverController 下降