简体   繁体   中英

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

I've spent the last 4 days trying to implement a proper Core Data stack with iCloud sync for my Swift 1.2 app , but I can really use some help.

Before, I was using a global Managed Context accessed from everywhere in the app; knowing that it was a bad implementation, now that I'm adding iCloud sync I decided to get rid of it, even though the app was working fine.

So far, I've implemented a new, working Core Data stack with decent - but not perfect - cloud sync between devices.

Now I face two issues:

  • Sometimes, a few objects don't sync.
  • Given the particular structure of my app, which I'll explain in a moment, I have no idea how and where in my code I should handle the notifications that Core Data sends when the user logs in or out of iCloud.

But, before tackling those problems, I'd really appreciate - if appropriate - some validation of the work I've done so far and it is mainly for some confirmations that I'm writing this: since I've already spent a lot of time changing my Core Data stack, before going forward I'd like to know if I'm propagating the context properly (the structure of my app doesn't conform to any tutorial I found online, so I had to improvise a bit), or if I made some basic mistakes that will compromise reliable syncing or the future development .

My app is structured as follow:

  • UITabBarViewController as initial ViewController
    • 1st tab: UIViewController (shown when the app starts)
    • 2nd tab: a UITableViewController embedded in a UINavigationController
    • 3rd tab: another UITableViewController embedded in another UINavigationController

I have a CoreDataStack.swift class with the following code:

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

In my AppDelegate.swift I added the following code just under 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.

With this structure in place, I instantiate my stack in AppDelegate and from there I propagate it to the 3 elements of the TabBar; from those, I again propagate the context to every other ViewController I present. I logged to the console the context everywhere and I can confirm that it is always the same.

As a matter of fact, the app with this code works.

I can't say it is perfect because, as I said, sometimes a few objects don't sync, but I suspect the cause of those objects not syncing is another (briefly, I have 2 NSManagedObject subclasses; the objects of subclass1 have an object of subclass2 as property; if I create a new subclass1 object using an existing subclass2 object as property, sync is fine; if I also create a new subclass2 object, save it and immediately set it as property of subclass1, sometimes the subclass2 object doesn't sync on the other device, while the subclass1 does and then misses that property... I can work on that later).

Before digging into this sync issue, I'd really love to know if the work I've done so far with the stack makes sense, or if it is horrible and needs to be canned .

Then, if all the code above is not horrible and if the reason of the occasional missed sync of objects would turn out to be the one I suspect, comes the other issue, and it is a big one: where do I put the code to handle the notifications that occurr when the user logs in or out from iCloud ( NSPersistentStoreCoordinatorStoresWillChangeNotification and NSPersistentStoreCoordinatorStoresDidChangeNotification )? I tried to put methods I've written (without actual functionality, at the moment I only log something to the console to know that I got there) based on Core Data by Tutorials book in both my AppDelegate and my CoreDataStack class, but in both cases when I log in or out from iCloud while the app is running, the app crashes without a single line in the console, so I have no idea of the issue.

Maybe I should put the methods to handle these notifications in all the ViewControllers, since the fetch requests happen there and UI is updated from those classes, but I'm not passing the entire coreDataStack objects around, only the context... so I'm missing something. Should I pass the entire stack, not only the context? Is it okay to handle those notifications from my CoreDataStack, or should I do it from AppDelegate?

Any help would really be appreciated...

Thanks in advance and, please, excuse if my question is not clear (I'm quite a beginner and english is not my main language...).

Also, thank you for your time reading this long question!

@cdf1982

I think the problem is that iCloud + CD never worked properly. It's not a developer code issue, the problem is the Apple implementation of iCloud + CD that simply fails.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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