How to create sharable Core Data .sqlite backups locally and in iCloud using current NSPersistentStore methods - ios

I have been having quite a time of figuring out the correct way to create a backup of a Core-Data backed .sqlite file and storing that backup, both locally and/or in iCloud (whatever the user prefers), for download, restore or sharing. Let me state up front that I am not talking about moving the persistent store to iCloud to be used by the app as its datasource. I am simply asking about creating backup files in this question.
In 2014, Apple changed their default journaling mode for Core Data SQLite stores to WAL.
https://developer.apple.com/library/content/qa/qa1809/_index.html
With that change, they recommended:
To safely back up and restore a Core Data SQLite store, you can do the
following:
Use the following method of NSPersistentStoreCoordinator class, rather
than file system APIs, to back up and restore the Core Data store:
- (NSPersistentStore *)migratePersistentStore:(NSPersistentStore *)store toURL:(NSURL *)URL options:(NSDictionary *)options withType:(NSString *)storeType error:(NSError **)error
Note that this is the option we recommend.
Prior to this, I had been using NSFileManager to create backups. With this recommendation, I believe that the correct way to create a backup locally is to add a new persistent store and then to migrate that persistent store to the desired backup location, using
the NSPersistentStoreCoordinator methods addPersistentStoreWithType:configuration:URL:options:error, and migratePersistentStore:toURL:options:withType:error, respectively.
My questions are two-fold:
I previously would zip my data to NSData before exporting it, and
would write the NSData directly to a file. My file extension would
be custom to my app, for sharing the zipped data via email or other
iOS sharing methods. With the
migratePersistentStore:toURL:options:withType:error method, I now
end up with a .sqlite file (and its corresponding WAL file, etc) in
the desired location, but I cannot figure out how to share this file
now. If I zip the file, won't I be in danger of losing the data I
worked hard to preserve by using
migratePersistentStore:toURL:options:withType:error in the first
place? I believe so, but I do want to enable my users to share/save their
backups vie email, etc, and I don't know how to best approach this now.
I am having a hard time understanding how
migratePersistentStore:toURL:options:withType:error can be used to
backup the file to iCloud. Much like the sharing example above, I
see that I can use
addPersistentStoreWithType:configuration:URL:options:error, and
migratePersistentStore:toURL:options:withType:error to get the
desired .sqlite copy locally, but if I then try to upload that local
file to iCloud, I fear I will lose the data I worked to preserve by
using migratePersistentStore:toURL:options:withType:error in the
first place. I have been trying to see if there is a way that I
could/should use
migratePersistentStore:toURL:options:withType:error to move the
newly created persistentStore directly to iCloud, but I haven't been
able to find any documentation on how to do that or if it should be
done at all. Is there a specific url I would need to use to indicate that the destination for the persistentStore is iCloud?
I would greatly appreciate any insights that can be shared regarding the answers to these questions.

ZIP should be safe way IMO since it has CRC data for data integrity validation.
You have to be careful though regarding shared CoreData store version. Suppose two users run different versions of the app and share the CoreData store between each other. CoreData doesn't support progressive migrations out of box. https://www.objc.io/issues/4-core-data/core-data-migration/
Maybe sharing a portion of data in JSON and re-creating CoreData entities from it would be safer and easier strategy for sharing data as opposed to sharing entire graph.
You can only copy file into iCloud container and share it across devices but you can't really use it directly from container or have incremental updates coming via iCloud. NSFileManager has setUbiquitous that allows to move file into iCloud container.

Related

Store Core Data Sqlite in NSLibraryDirectory or NSApplicationSupportDirectory?

What is the difference between NSLibraryDirectory and NSApplicationSupportDirectory? Which one is the best place to store the Core Data .sqlite file if I am planning on using a syncing service?
I currently store my .sqlite file in NSLibraryDirectory. If NSApplicationSupportDirectory turns out to be the better choice, how will changing this affect my current users? Can it easily be changed, and what are the steps I should take to ensure a seamless transition for my existing users?
If doesn't matter for syncing service where you store your data file. But if you will change its location then you have to move your data file of previous users to new location.

Storing CoreData to RackSpace

I am developing an app on xCode 5, iOS 7. I have some data stored in CoreData. My requirement is to upload that data to RackSpace. Whats the best way to do this?
Where can I find .sqlite file associated with CoreData?
The SQLite file is wherever you put it. There's no magic to it, you have to tell Core Data exactly where you want the file. You do this when you call addPersistentStoreWithType:configuration:URL:options:error:. The URL argument is the location of the SQLite file.
If you try and use the file directly, make sure that:
You shut down your Core Data stack completely before doing so, to make sure that all unsaved data has been flushed to disk. That means no managed objects, managed object contexts, or persistent store coordinators in memory anywhere.
Make sure to get the SQLite journal files. If your store file were named Foo.sqlite, they will be named Foo.sqlite-wal and Foo.sqlite-shm and will be located in the same directory. If you don't get these files, most or all of your data will be missing.
However simply uploading the file is not a good solution for syncing data. To sync data, you'd have to download a copy of the data, load that, and compare every object in the file with every object that's already on the phone. It's not impossible but it's definitely making things much more difficult than necessary. There are many options that can simplify the process, including full service providers like Parse, SDKs that let you use one of a variety of back ends like Ensembles.io, and others.

Preventing NSUserDefaults from iCloud Backup

There are three ways to write persistently to the device in iOS that I'm aware of:
NSUserDefaults
Custom Objects - Archived and Written to a PATH in NSDocuments
SQLite
Apple provide a mechanism to prevent iCloud backups with #2 i.e.
- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
I use NSUserDefaults to store three images but to adhere to iOS Data Storage Guidelines -
How do I prevent Backups with iCloud with NSUserDefaults?
This question has been asked several times on SO but there is no clear comprehensive answer yet.
Is there a such a function or do I have to change storing images using method #2. I was hoping something convenient like:
- (BOOL)addSkipBackAttributeForStandardUserDefaultsKey:(NSString *)
exists.
There's no mechanism for this. NSUserDefaults is not intended to be used for saving app data. It's there to hold user preferences and other settings the app needs, but isn't intended as a full data persistence system.
It's not accurate to say that this "...[Apple's] mechanism for storing data can't adhere to it's own guidelines" because that's not the purpose of this API. In this case specifically, if those images should not be backed up, then that's absolute evidence that user defaults is the wrong place to store them.
You should save the images to files. If you have UIImage, this is simple. UIImage conforms to NSCoding, so it's easy to convert to/from an NSData. And NSData includes convenience methods to read/write objects to files. If the file names might change, you could reasonably put the file names in user defaults. If the file names are known in advance and can't change, then there's no reason to store them.

iCloud with Core Data and File sync

I'm working on an Application that uses Core Data with iCloud (with the great improvement given by iOS7). This application stores data to describe a task with this information:
name a NSString
date a NSDate
image a NSString which describes a path to the picture
The pictures could be stored in Documents or Library Directory (I have to decide which is the more convenient folder), by the way, in the same folder with a unique name.
Now I'd like to activate iCloud sync for the images too otherwise the experience of the user will be incomplete (I just sync DB data.. no images, a strange/wrong behaviour for an app).
I'm really confused by Apple Documentation. I can't find a way to understand exactly how iCloud data works for this kind of needs. I just want to sync every file of a folder as soon as they will be created. So my questions are:
Could you share some good resources to learn how to use iCloud for file sync
Have I to use UIDocument and other iCloud API? or is there something "automatic". Quite a new bye/stupid question, I know :(
Are there any problem using Core Data and Document based iCloud synch in the same app?
Note: I know that I can sync data just by adding file in the document folder and hoping that users activate document sync... but this is not what I want obviously.
It is pretty straight forward to use both Core Data transaction log synchronisation and file based synchronisation in the same app to achieve what you want to achieve.
So you would set up your Core Data stack to use iCloud options and synchronise data changes via iCloud. At the same time you would store your images in the Apps iCloud container so they get synchronised as well. Just remember you need to use a relative reference to the images in your Core Data fileURL because the full pathname will vary depending on the device the app is running on. So for example you would just store the image filename in Core Data and use a standard directory such as 'iCloudContainer/Documents/Images/' to store them. 'iCloudContainer' being the URL you get by calling the [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:] API.
I have uploaded sample Core Data apps that use iCloud for transaction log synchronisation (i.e. synchronising data in Core Data) and that use file synchronisation for storing Core Data backup files in iCloud which can then be accessed by any device. You should be able to use the same code for moving backup files to and from iCloud for your images. Just remember you have to trigger download of files from iCloud before you can use them either by doing a coordinated read or by initialising the download using NSFileManager.
http://ossh.com.au/design-and-technology/software-development/sample-library-style-ios-core-data-app-with-icloud-integration/
http://ossh.com.au/design-and-technology/software-development/sample-library-style-ios-core-data-app-with-icloud-integration/sample-apps-explanations/
Download and run the sample apps and use the built in Backup File Manager to make backup files and to copy them to and from iCloud using different devices. Then just use the same code when storing your image files.
Your App does have to handle things like the user changing iCloud account, logging into or out of iCloud etc. and them move the core data file and image files accordingly.
The only way to have this happen automatically is to create a binary data attribute in your model for the images. If you do this, you will probably want to check the external binary storage allowed option, so the photos end up stored as files and not in the database.
If you would rather store the photos external to your store, you will have to do more work. You will need to migrate the photos into the iCloud container yourself, using the NSFileManager methods, for example. You could also use a class like iCloud Access if you find that easier.
The downside to handling the photos yourself is that you can never be sure that they have all arrived on your device when the Core Data store syncs, so it could be one or more photos are missing, even though there are entries for them in the store. You would have to make sure your app could handle this scenario, perhaps showing a placeholder image until the real photo was accessible.
There are no issues using Core Data and Document syncing in one app. In fact, they are exactly the same under the covers. From iClouds point of view, they are all just files to be transferred.

iCloud + Preloaded CoreData

I have database with some default content. How can I use iCloud to sync changes in database on different devices?
I know how sync devices if CoreData was empty first and I can migrate from external sqlite file to CoreData.
If you have an existing Core Data store and you want to add iCloud support, you'll need to transfer all of your existing data to a new data store and save the result. This is necessary because iCloud only generates transactions when you save changes-- so you need to effectively save changes for everything to jumpstart the syncing process.
In most cases you can do this in a single step by using NSPersistentStoreCoordinator's migratePersistentStore:toURL:options:withType:error:. Pass in the same iCloud options that you would use when calling addPersistentStoreWithType:configuration:URL:options:error:. Use this alternate approach only when moving from a non-iCloud data store to one that does use iCloud.
Also, beware of using iCloud with Core Data. It has not earned a reputation for reliability. Pay close attention to Apple's documentation and sample projects, and even then be prepared for it to just not work sometimes.

Resources