I'm syncing a list of table view items via iCloud. When would be best to perform the sync. Here is my understanding of the various options.
didFinishLaunchingWithOptions - this will only get called when the app is launched. As most users send the app to background, rather than quit, it is likely this method won't be called very often.
applicationWillEnterForeground - this will happend every time the app is opened from a background state, if the internet connection is slow, this could cause a pause is the UI displaying?
applicationDidEnterBackground - I believe we only have 5 seconds to perform any actions, so a slow connection might mean we can't sync.
What are your thoughts best time to sync?
Well, besides the fact that a document saves every so odd amount of seconds, the best time to sync with iCloud is very dependent on the circumstance.
For example, if you have created a brand-new object that would be lost if not stored to iCloud, it's probably a good idea to do a sync with iCloud right away.
On the contrary, if you have created a brand-new object that wouldn't be lost if not stored to iCloud due to it being saved within Core Data, then maybe you can combine the save into one elsewhere given that you're concerned about the speed and CPU that the sync will take up.
Related
I'am planning to have a main OSX application, which the user can launch and a background process, which starts on OSX startup and runs prior to the main application
I need a CoreData database, to keep track of some changes... this database should be the same for the background task and foreground app...
What are the options?
Is it possible, that both access the same sqlite (which will be
located in app bundle?)? By setup with the same .sqlite file?
Should they have two identical databases, which they synchronize?
can this synchronisation be automated?
Should there be one database for the background process and the
main application should communicate with the background process?
Using a background process to update the datastore is fighting the frameworks. Also, your background process (and your main process, for that matter) won't be able to update the .sqlite file that lives in your bundle.
Instead of using a background process, use a background queue (via Grand Central Dispatch, and NSManagedObjectContext -performBlock:. Keeping the logic within one application will make your life easier. Your communication happens within the application, instead of having to use XPC.
Don't forget to handle the case of a partial, interrupted, update. The sequence I suggest is:
Application launches.
Background queue launches, pulls updated info from server, creates updated datastore using a temporary name.
If background update succeeds, main queue of application closes the old version of the datastore, then replaces it with the updated datastore. There is API to do this atomically.
Main thread reopens datastore and refreshes UI as needed.
If background update fails, reschedule an update attempt based on failure reasons (bad credentials, server unreachable, partial download, corrupt .sqlite).
If you're absolutely dead set on using two different processes, then you should still assume that the update might fail. So don't write to the live copy until you know you have a complete, valid, replacement.
Apple achieve this in the Notes app using what they call a "cross process change coordinator". This allows the accountsd daemon and the Notes app to access the same CoreData sqlite database. Their class ICNotesCrossProcessChangeCoordinator in the NotesShared framework works by using NSDistributedNotificationCenter to post the notification of changes between the processes and merge them into each others' context. There are many more implementation details of the technique but this should point you in the right direction.
I am wondering whether it is possible to determine at which time my local Core Data store was synchronized with iCloud. From iCloud is trivial. You can just take the time of the last NSPersistentStoreDidImportUbiquitousContentChangesNotification. However, I could not find any method to check when my local changes were completely transmitted to iCloud.
Any ideas?
You can't find this out. The only information available is the transaction logs, but those don't tell you when the data was actually synced. Transaction logs are created when you save changes. At some later time (probably soon, but there's no guarantee) they get synced to the iCloud service. You don't get notified of this.
It might be possible to infer sync timing via out-of-band communication from other devices. For example, when you receive the did-import notification, write the current time to NSUbiquitousKeyValueStore. Then monitor that key to see when changes are received at the other end. That would at least tell you that some changes had been received by some other device. At best though, it would notify you of when changes had been downloaded at the other end, not when they had been successfully uploaded to the iCloud service.
I am using iCloud to store sync user preferences between devices. On the device, these are stored in an array of 'Favorite Teams' in NSUserDefaults, and I am using MKiCloudSync to mirror them to the NSUbiquitousKeyValueStore. Changes on one device are propagating to the second device well.
But I am not sure how to prevent the cloud data from being wiped on the first launch after a new install. Here is what's happening:
Device A launches for the first time. App finds nothing in the cloud. User adds multiple items to the array in NSUserDefaults. Changes are synced immediately to cloud.
Device B launches for the first time, but is offline. User adds a single item to the NSUserDefaults array, then remembers the app supports iCloud, so finds some wifi instead.
Device B pushes its version of the defaults to the cloud (with only one item). Device A pulls it, effectively wiping out all of the teams added on Device A.
Is this a limitation of iCloud or is my implementation naive? The docs address a similar issue where a 'highest level' is synced, and adds application logic to never overwrite this value with a smaller value. That's fine when there is some clear business logic to adhere to (higher level is always the one to keep), but when data is more arbitrary, I don't see how I can determine what to do.
Or is it because I am using an array in NSUserDefaults for 'Favorite Teams' and replacing it wholesale? If I used separate keys for each team, perhaps they will be synced independently, based on time code?
Any time you sync a value for a specific key, you run the risk that it will be changed by a different device using the same account. The iCloud service chooses the winning value for you, makes updates, and notifies you when it's done. This is a limitation of iCloud and of your app, and is a simple example of why syncing is hard. What if your step 2 above looked like this:
Device B launches for the first time, and iCloud is available. The app downloads the current data from device A. The user changes their mind and deletes all the data they created on device A. Then they a single new item.
Well, what then? Step 3 still happens exactly as you describe it, except that this time the incoming data is what the user wants. You could refactor your data but the same kind of situation will still be possible.
One option is to keep a non-syncing local copy of the data, so that you can compare incoming changes with the previous local state. What to do when they're different is up to you. Just don't forget that even dramatic changes might well be exactly what the user wants, and not a syncing issue that needs to be fixed. Or, they might be something that would lose data the user wants to keep. Resolving this conflict is your job.
I have been getting my feet wet with Core Data. I'm writing a card game and I'm able to store and retrieve game statistics. I'm also storing the game's state after each move to allow the application to resume a game that was in progress when the application quit and to also facilitate my home-brew undo system.
Unfortunately, the longer I play my game the slower it feels. I think this is because after each move I'm storing 52 cards and their specific states in SqlLite. I suspect that this just gets slower the more data I cram into the DB.
Because of this, I plan to try using the built-in undo management in Core Data. (I didn't remember this was there until it was too late on my initial implementation.) My question is, if the app is closed mid game, can it be restarted with the undo management in the same state?
IE: Imagine a user makes ten moves in this game. They would be able to undo ten times. If they quit the app and close it entirely and then restart the app, can I return Core Data to a state where the user will still be able to do the ten undo steps?
A little bit of research suggests I might be able to simply use NSCoding to persist the NSManagedObjectContext to a serialized file when the app is closed and then restore it's state from this file when the app is restarted.
Am I on the right path? Any suggestions?
Thanks!
NO UndoManager is not persistent.
Yes you may use NSCoding or even a Plist for saving the state.
For more information on this topic you may refer
http://www.cimgf.com/2011/10/11/core-data-and-the-undo-manager/
I use Core Data to maintain a persistent store, and the database can grow quite large. My users with larger databases on iPad 1s don't complete the lightweight migration in time for the process to complete before the app is killed by the iOS for hanging.
What I want to do is every time the server starts up, check to see if the database needs to be migrated (I can't find a method for this on NSPersistentStoreCoordinator), if it does hold the server startup process until the database is upgraded and display a spinner on screen, then move forward with the server startup process once it is. The best way to do this seems to be to add a - (BOOL) upgradeStoreNeedsUpgrade method in the server startup method, but I can't find a way to check. I don't see methods on NSPersistentStoreCoordinator or NSPersistentStore to check the compatibility of a a database at a given URL with a given managed object model.
Is my solution the right way, and if so, how can I check if the managed object model is compatible with the SQLite file at a given URL?
You could try wrapping the core data lightweight migration code in a dispatch block. This should spin it off to a background thread so you can get past the Application Start Watchdog thats probably killing your app. Its either that or you are running the device out of memory.