繁体   English   中英

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

[英]Core Data stack implementation for iCloud sync in a UITabBarController app (Swift 1.2)

最近4天,我一直在为Swift 1.2应用程序尝试通过iCloud sync实现适当的Core Data堆栈 ,但是我真的可以使用一些帮助。

以前,我使用的是从应用程序中各处访问的全局托管上下文。 知道这是一个不好的实现,现在,我添加了iCloud sync,我决定摆脱它,即使该应用程序运行良好。

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

现在我面临两个问题:

  • 有时,一些对象不同步。
  • 鉴于我的应用程序的特殊结构(我将在稍后解释),我不知道当用户登录或注销iCloud时,我应该如何以及在代码中处理Core Data发送的通知。

但是,在解决这些问题之前,如果可以的话,我非常感谢您对我到目前为止所做的工作进行一些验证,这主要是为了确认我正在编写的代码:因为我已经花了很多时间更改核心数据堆栈时,在继续之前, 我想知道我是否在适当地传播上下文(我的应用程序的结构与我在网上找到的任何教程都不符,因此我不得不作一些即兴创作),或者如果我犯了一些基本错误,将会影响可靠的同步或未来的发展

我的应用程序的结构如下:

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

我有一个带有以下代码的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?下添加了以下代码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

我认为问题在于iCloud + CD无法正常工作。 这不是开发人员代码问题,问题在于iCloud + CD的Apple实施完全失败。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM