A few days ago I was ready to give up. I was so depressed over trying to get Core Data sync working. My personal feeling right now is LEARNING CORE DATA IS A CHORE!!! I guess at this point I know enough to be dangerous. Swapping in and out of cloud syncing almost seems to be impossible. Not to mention adding Swift and MagicalRecord to the mix just made it even more challenging. So continuing on:
After contemplating tossing all my devices in the trash and going fishing, I decided to try the simplest thing that could work. I created a new test app and decided to just do the most minimalist Core Data (CD) app I could and add syncing in as I went. I think at this point there may be a light at the end of this very narrow, long and twisty tunnel. I found this blog post which I thought was really interesting:
Martin Craft's Core Data Stack
I really like Marcus’ assessment of the state of information about getting a proper CD stack setup. He absolutely nailed it, it is all over the place and there are A LOT of red herrings. I also found this link to a CD book that looked promising (but I have not read it yet):
Tim Roadly Core Data Book
But there appears to be a new version being worked on using Swift. So if I do decide to buy it I’ll wait for that version.
One side issue I ran into was I had attached listeners to all the events that CD throws and in my listeners I was logging them using NSLog and trying to print out the notification using Swift’s inline string parameterization (I forget the technical term for this and am too lazy to look it up right now). It worked fine on the simulator but when I moved to a real device, I got BAD_EXEC errors. Switching to use “println” instead seemed to solve the problem. So I have added that gem of information to my list of items to try when Swift seems to be misbehaving.
Another thing that helped was to mark my class that was handling the CD stack with “@objc” this seemed to calm down a lot of errors I was dealing with when listening for events coming from CD itself.
So for review here is my original list of requirements:
- Make an app agnostic class to manage a Core Data stack in Swift
- Be able to switch from a local store to a cloud store
- Be able to switch from a cloud store back to a local store
- Be able to remove the cloud store all together
- Be able to blow away local changes and rebuild from the cloud store
- A simple way to hook into CD notifications
- Support schema migrations
My status so far is this:
Item 1 - Done. I take a model name in the initializer for the class and then build everything else from there.
Item 2 - Partially done. I can now make the switch to iCloud from a local store. What doesn’t work is after doing this the first time then switching back to a local store and then switching back to the cloud store. I end up with duplicate records. That makes sense to me as when I switch to the local store the device loses knowledge that those items existed in the cloud. When going back to the cloud the device dutifully merges it’s objects with those in the already existing cloud store so I end up with duplicates. This is the step in Apple’s iCloud with Core Data store talking about deduplication of records.
1: func switchToCloudStore(completion: ((Void) -> Void)?) {
2: let cloudOptions = cloudStoreOptions
3: let storeURL = cloudStoreURL
4: if (storeURL == nil) {
5: println("Could not switch to store because store URL not available")
6: return
7: }
8: var error: NSError?
9: let newStore = psc.migratePersistentStore(store!, toURL: storeURL!, options: cloudOptions, withType: NSSQLiteStoreType, error: &error)
10: if let _newStore = newStore {
11: println("Cloud store URL is: \(psc.URLForPersistentStore(_newStore))")
12: self.store = _newStore
13: context.reset()
14: if let _completion = completion {
15: _completion()
16: }
17: }
18: else if let _error = error {
19: println("Error in migrating to cloud store: \(_error)")
20: }
21: else {
22: println("Returned newStore was nil when trying to migrate to cloud store")
23: }
24: }
Item 3 - Partially done. This has the same problem as item 2 where I move back to a local store (that already existed and had records in it). I end up with the records from the cloud store as well as the set of records that were already existing in the local store.
1: func switchToLocalStore(completion: ((Void) -> Void)?) {
2: var localOptions = localStoreOptions
3: localOptions.updateValue(true, forKey: NSPersistentStoreRemoveUbiquitousMetadataOption)
4: var error : NSErrorPointer = nil
5: if let _newStore = psc.migratePersistentStore(store!, toURL: localStoreURL, options: localOptions, withType: NSSQLiteStoreType, error: error) {
6: self.store = _newStore
7: context.reset()
8: if let _completion = completion {
9: _completion()
10: }
11: }
12: }
Item 4 - Partially works - This appears to work, but it appears I need to remove the store from all devices before it completely goes away. This doesn’t totally surprise me, but it is still a little unnerving. I can live with it though.
1: func resetStore(completion: ((Void) -> Void)?) {
2: let storeURL = cloudStoreURL
3: if (storeURL == nil) {
4: println("Could not reset store because store URL not available")
5: return
6: }
7: let options = [NSPersistentStoreUbiquitousContentNameKey: "\(modelName)CloudStore"]
8: var error : NSErrorPointer = nil
9: let result = NSPersistentStoreCoordinator.removeUbiquitousContentAndPersistentStoreAtURL(storeURL!, options: options, error: error)
10: if (result) {
11: if let _completion = completion {
12: _completion()
13: }
14: }
15: }
Item 5 - I haven’t started on this yet, but it seems straight forward.
Item 6 - Done. I implemented two methods on my class, attachInternalListeners() and attachListenerForChangeEvents(AnyObject,Selector). The first method is internal for the stack manager to do the right things when changes come in. The second method is for external consumers of the stack manager so they can attach to change events and do the right thing when those happen. It is intended for things like a table view controller to get updates when the underlying store changes due to the ubiquitous store changing. I thought my view controller would automatically update because it has a NSFetchedResultsController but so far I haven’t got that to work. More work to be done here.
Item 7 - I don’t think there is anything to do on this one. Reading Apple’s documentation it sounds like syncing will be disabled on a device until it gets to the current version in iCloud. Now, how all this works depending on the sync state of the device is anyone’s guess. I suspect I will have to use items 4 or 5 to fix any issues
So right now, I feel like I have made progress. I still need to deal with threading issues so my code is a “good citizen” of the CPU. I’m planning on basically setting my contexts up like Marcus Zarra did in his article (this is mostly how I think MagicalRecord worked and how I tried to roll my own in the past)
The other issue I need to deal with is the deduplication of records as I transition to and from the cloud store. This might be a little more harder than I thought. My plan is to go back and look at how Apple suggests dealing with that.
Once I am done with all that I’ll probably end up posting the code somewhere so everybody can tell me what I did was wrong or possibly learn something from my ordeal.
Till next time, hoping for more progress.
No comments:
Post a Comment