Core Data with iCloud design - ios

Im on iOS 7 and want to use core data with iCloud, this is working well, the issue I wanted some input on is the following.
Some of my Core Data objects have related images/large text files which I was storing as files on the file system and just setting the url of these items as string properties. Now obviously this wont just work with iCloud Core Data sync because iCloud has no notion of these files. I was wondering if you had any suggestions on how to handle this?
1) I could store the image data and large text files as core data properties directly but it does not seem to be the best way to go
2) I could use iCloud to also store the documents and then somehow try to keep sync between the files in different iOS devices, but this could potentially get messy.
3) Some other solution.
Any Suggestions?
Regards
Daniel

If you are setting up the model programmatically, add a message to setAllowsExternalBinaryDataStorage: to allow Core Data to decide when to save it as a record or separate file automatically. Core Data manages the external files for you when it decides to use them, so it should "just work" in iCloud as well.
NSAttributeDescription * imageAttribute;
//Initialise, setup, add to model, etc
[imageAttribute setAttributeType: NSBinaryDataAttributeType];
[imageAttribute setAllowsExternalBinaryDataStorage:YES];
There is a corresponding checkbox for the attribute in the model editor UI as well.
https://developer.apple.com/library/ios/documentation/Cocoa/Reference/CoreDataFramework/Classes/NSAttributeDescription_Class/reference.html#//apple_ref/doc/uid/TP30001175-SW26

Place the files in your iCloud document sandbox also and they will keep synced across all your devices.
You can use a relative reference to the items as they will appear in the file system at the same relative point.
<ubiquitous container URL>/Documents/something.jpg
A minor issue is that the sync isn't seamless and you will have to explicitly request the item to be downloaded from iCloud if it isn't on device already.
So at its most primitive...
NSDictionary *dict = [urlToIcloudResource resourceValuesForKeys:#[NSURLUbiquitousItemIsDownloadedKey] error:&error];
NSNumber *isDownloaded = dict[NSURLUbiquitousItemIsDownloadedKey];
if (![isDownloaded boolValue]) {
BOOL res = [[NSFileManager defaultManager] startDownloadingUbiquitousItemAtURL:urlToIcloudResource error:&error];
}
So you can keep using your current strategy of dumping them in the file system and just using a string reference to them. iCloud will keep them synced.
The suitability of this solution does depend on whether those large blobs are write-once-read-many objects (very suitable) or read-write-often type objects (trickier).
One advantage is that for text objects like txt or json if you keep the extension on the file iCloud should "just work" for keeping multiple device edits in sync.
Raw data objects like jpg or pdf will be last edit wins.

Related

iCloud data backup and restore strategy

Don't know if this type of question is ok here but I'll ask anyway.
I have an iOS iCloud Core Data application that works decently well (though I think it does need some synchronization tweaking) and am considering allowing the user so save a copy of the database to iCloud drive in a UIDocument. This would allow the user to possibly segment the database manually and load only the portions they want to use at the moment as well as provide peace of mind considering that the data may include some insurance claims data.
I'm weighting several alternatives to allow this to happen.
Option 1 is to just create an iOS based save and restore process which, when triggered, would dump the database (maybe at max 1000 objects) into an NSArray of NSDictionary objects (one dictionary per row) and then JSONize the NSArray into NSData and save it in a new UIDocument on iCloud drive. A restore process would list possible archives to restore and then allow the user to pick one. The restore process would do the reverse of the save process.
Option 2 would be to create Mac App to handle the save/restore process which would allow the user to save the archives on the user's Mac. This seems a lot less difficult though it would require a set of apps working together. The mechanism would be similar as option 1 but the data would go to a file specified in a standard Mac file system pick screen.
Personally, I'd like to do both of them but before I start I want to make sure that there's not an easier way than dumping the entire SQL database into an NSArray of NSDictionaries.
A complicating factor is that the database contains images which are in the database as NSData. I could string-ize the NSData, add it into the NSDictionary build, then let the JSON process do it's thing.
I'm just fine with proceeding this way but I wanted to make sure I wasn't overlooking some type of Apple method of doing this in an easier way.
Another thought that came to me was maybe using some sort of SQLite utility to do save and restore locally in one session which would then replicate the results across all of the devices via iCloud. Again, sounds like more of a Mac answer to me but I don't really know.
Again, any suggestions would be great.
iCloud supports synchronizing CoreData databases. See this article for some more information.
Also, you might consider looking at CloudKit for storing this data directly in iCloud, so it can be accessed on multiple devices.

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.

How to seed initial data to Core Data + iCloud?

I'm working on a new app that uses Core Data and iCloud. I'm following the iCloudCoreDataStack demo and the iCloud Design Guide.
So far the synchronization between devices is working well, but I haven't figured out how to seed a small amount of data the first time the app is used on the user's first device and skip the seeding if the app is used on a second device (since it should download from iCloud instead).
This should be easy, just ask the iCloud Container if it has any data. Download the data if it exists or create new data if it doesn't.
But I couldn't find a way to do this :-(
I can think of three ways to solve this:
Use migratePersistentStore:toURL:options:withType:error:
I have a very small amount of data, so for this case this feels like overkill
Store a value on NSUbiquitousKeyValueStore to mark if the initial synchronization has been made
I tried using NSUbiquitousKeyValueStore, but sometimes it would take too long to get the value from
the UbiquitousKeyValueStore, so the seed data would be created even when not needed, resulting in duplicates.
Use a sentinel file to have the same effect of #2 (I'm not sure how to implement this)
The App is iOS 7 only and new, so there's no need to migrate old user data.
Every relevant tutorial and book that I found seemed to be using the pre-iOS7 super complex way of doing things (e.g. using a fallback store) that is not necessary on iOS 7.
Either I'm missing something (often the case) or this is more complicated than it should be.
I appreciate any suggestions and pointers.
It is never a good idea to seed a distributed datastore with an initial dataset. Generally this initial data can be packaged into a store file that is shipped with the application, and added as a second persistent store to the coordinator used by your application's managed object context.
That said, it is possible, although unwise to seed based on the completion of Core Data's initial import.
You need to wait for NSPersistentStoreCoordinatorStoresDidChangeNotification with NSPersistentStoreUbiquitousTransitionTypeKey set to NSPersistentStoreUbiquitousTransitionTypeInitialImportCompleted.
If there is no data in the store you can seed your initial data set at that point.
However it is important to realize that multiple devices could receive the initial import notification without importing the seeded data and thus seed it again. There is no way to avoid this.
On the point of shipping a second persistent store with your application, to serve as seed data.
This is accomplished as Marcus points out below by adding it as a read only store to the persistent store coordinator that is in use by your app's managed object context.
NSDictionary *options = #{ NSReadOnlyPersistentStoreOption: #YES };
[_psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:seedStoreURL
options:options
error:&localError];
NSDictionary *iCloudOptions = #{ NSPersistentStoreUbiquitousContentNameKey: #"storeName" };
[_psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:iCloudStoreURL
options:iCloudOptions
error:&localError];
_moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[appMOC setPersistentStoreCoordinator:_psc];
This way your application's managed object context has access to data from both stores and will fetch both sets from fetch requests. The managed object context is also smart enough to automatically insert new objects into the correct store (because one of them is read only).
The only trick is that if you want your user to be able to modify data from the seed store you'll need to move those objects to the iCloud store.
This is a much easier approach than trying to migrate the dataset wholesale because ideally your user will only be using a single device at a time. In the case of a collision you'll at most have to resolve a few duplicate records as opposed to trying to detect duplication across the entire dataset.
Try this approach:
Before creating the local store check if one has already been created in iCloud by looking for the presence of a directory with the 'NSPersistentStoreUbiquitousNameKey' name in the iCloud /CoreData directory.
If one exists then this means that some other device has already created a store and shared it in iCloud so when creating your local store on the device don't add seed data because this will already exist in iCloud.
If one does not exist then no other device has shared the store in iCloud yet so you can probably create your seed data and sync to iCloud.
NOTE that the following scenarios are not catered for:
A user is running the app on a device that does not have iCloud
enabled - if the user chooses to now sync with iCloud from this
device you will have to deal with issues arising from trying to merge
data from this device with data already in iCloud. Once again you
can check for the presence of data in iCloud and then ask the user
whether they want to try merging data from the device or whether they
want to replace the data with data from iCloud.
A user is running the app on a device that is not connected to the
network - and has not had any data synced from iCloud yet so thinks
there is no file already in iCloud. The app will then create seed
data and when it gets a network connection Core Data will merge the
data - you app may have to deal with the ensuing problems.
For these scenario's you may need to try user education.

How to store many images and .mp3 files in core data for iphone?

I am very new to core data and am trying to learn how to store large files, like images and short .mp3s. My app has about 300 different images and short .mp3s that are triggered to be displayed/played by user interaction, and as far as I can tell, I should use core data for storing these files.
I added a core data .xc file to my xcode project, and created an entity with attributes. I chose an attribute named "binary data" and checked the box "Allows External Storage," which from what I've read allows me to easily persistently sore files on 5.x or newer iOS phones. I know that I can program the app to get and set these attributes, but how do I just store my images and music files here?
Please help with me with code/understanding. It seems to me I should be able to somehow use the core data interface to set up a bunch of entities containing attributes of "binary data" that are my images and .mp3s, and whenever I need to display a pic or play a sound, I could call the entity and its attributes.
Thanks for the help!
I store compressed images (jpegs) with Core Data and it seems fast enough. But these are compressed to < 100K.
If you want to store items bigger than 100K, you might want to just save the file to "disk" doing something along the lines of Write a file on iOS
Then, save the file path to a string in Core Data. To load the file, get the file path from core data then read the file in.
I think Apple recommends the cutoff at 100K.

iOS: using iCloud document storage for a small XML based database

Just wanted to know if this is a good idea:
I want to use iCloud to sync data between different devices in my iOS app. It's just a list of small objects without connections. But storing this list in the key/value store won't work because it's space is restricted to 1 MB or so and the list might get bigger (not much, but could...). Core data seems like an overkill to me and there is also the problem of possible duplicates.
So I wonder if it makes sense to subclass UIDocument to handle the XML file. Every object has an ID, so merging different versions of the file should be no problem.
The choice of XML depends on the format of the data store (monolithic or transactions) and the volume of updates. If the entire file (1 MB+) is constantly being written to by your app (and hence sync'ed to iCloud) or if a small change causes the entire store to be sync'ed to iCloud then I would use Core Data. The advantage of core data is that only the transaction logs you require (or have changed) are synced.

Resources