Storing user and application core data differently - ios

In my IOS app, I store two types of data in CoreData. One type is user generated, the other type is a pregenerated database. Currently this is all in the same store, which goes into the documents folder. But this is not a good approach, so I will split into 2 separate stores.
There are two problems:
The pregenerated database should not be backed up by iCloud.
When an update of the app is provided, the user data should be kept, but the pregenerated database should be overwritten. (The pregenerated database will have updated content, even if the data model is unchanged.)
For problem 1, I can either put the pregenerated database store in the cache directory, or keep it in the documents directory flagged for skipping backup. As the cache directory can be emptied any time, using the documents folder without backup seems better. However, does that solve problem 2? That is, will the pregenerated database be overwritten after an update?
If not, are there any other solutions?

You can keep pregenerated rated database in document folder and flag it for skipping backup in iCloud. However you have to write some script to overwrite the data Or on update you can delete your pregenerated store completely and create it again with updated data.

Related

Passing/Export core data database from one device to another

I'm trying to create an app which will store data locally using coredata. Then I want to share/export that data(from coredata) to be used by other devices using same app.
Is it possible to send a coredata from one device to another? Can I just copy the *.sqlite file and overwrite the coredata in the other device?
I saw this post how to export Core Data to CSV that allow core data to be exported as csv, But what I'm trying to achieve is to pass core data itself.
Thank You!
You can use migratePersistentStore:toURL:options:withType:error: of NSPersistentStoreCoordinator to save the store to a file. Once you have the file you can copy it to the other devices.
Other solutions might include iCloud if you're wanting data synchronisation.
This blog post suggests a good way to export the database as one file. Besides using the migratePersistentStore:toURL:options:withType:error: method, it also creates a copy of the NSPersistentStoreCoordinator to keep the current database consistent, and disables the sqlite write ahead and log to reduce all database to just one file.
In the device receiving the sqlite file, the following worked fine for me:
1 - Save the URL of the persistent store (persistentContainer.persistentStoreDescriptions.first?.url)
2 - Delete the current persistent store, calling persistentStoreCoordinator.remove on your store instance inside persistentStoreCoordinator.persistentStores
3 - Saving the new database in the URL of the old one, using FileManager.default.replaceItem with the URL saved on the first step and the received file.
4 - Call persistentStoreCoordinator.addPersistentStore to add the store to the list of stores that core data will manage.
Also, before executing this whole process it might be good to reset all contexts (context.reset) to ensure no reference was kept, and in my case I executed everything inside the persistentStoreCoordinator.performAndWait method to avoid concurrency problems.

CoreData:Migrate data from bundled db

Our app is having a coredata store which is based on a single coredata model. There are read-only data as well as read-write data.
The read-only data are pre-loaded and bundled along with the app, so that on a fresh install, this database is copied to the application sandbox and from there on the data base will be updated via webservice (ie only the changed data will get updated from webservice so that less data is transferred).
Now we have situation where we need to add more attributes to the read-only entities.
Light weight migration will help in upgrading the schema easily, but the problem is about the new data, since we are adding new attributes to all the read-only entities, all the data records are changed and a webservice sync might take a lot of time to download and update data. To avoid this we are bundling the updated data along with the app ( this will solve the issue for a fresh install). But for the users which are upgrading the app is there a standard mechanism to copy the read-only entities from the bundled db and update those to the existing database in the sandbox so that they will get the updated read-only data and also their read-write data remains intact.
UPDATE
Here is the scenario,
I have X.sqlite bundled with the proj (which has new schema), if X.sqlite is not there in doc dir I then copy it and from there everything works OK. Now in the App update scenario, X.sqilte will be already present in doc dir and won’t be copied and the migration assistant will migrate the schema. So now we have X.sqlite with the new schema in doc dir but old data (without new attributes). Now what I want to know is if there is a way to merge the data from bundled X.sqlite with the one which is there in the doc dir. I want to know if there is a process for merging.
To be more precise
Below are the entities
*Store - ReadOnly
*Products - ReadOnly
*ProductGroups - ReadOnly
*ShopList - User based
All are in the same model and in the same store.
Now Store/ Products / ProductGroups have extra attributes.
Lightweight migrator will migrate the schema of X.Sqlite so that the DB will have the new attribute columns.
Now what I am concerned is the next step,
Lets take Store as an example. Store has two new attributes latitude and logitude. Now the question how to copy the data? the steps
Copying the bundled DB to doc dir with diff name?
Create a new persistance co-ordinator?
Read the bundled data and get the objects?
then iterate through the existing db?
If I have understood your question: you want to update read-only data during app update--while leaving read-write data that user has changed intact.
There are several ways to achieve this:
Have two separate databases. One database can have the read-write
data and another with read-only data. They can relate to each other
using fetched properties. During update, replace or update the
read-only database--while leaving the read-write one intact.
Update the database using a background thread. The update code will
have it's own ManagedObjectContext--but sharing the same
PersistentStore. Synch the main ManagedObjectContext from the
background thread using some protocol.
The second option of updating from a background thread might work well if you choose to update from your web service.
If I haven't understood your issue, please clarify.
Ok So finally after a lot of research I achieved my goal, below are the trails and solutions that i did
Sol 1
Have the read-only and read-write data in separate databases, so that I can safely delete the readonly db if there is any master data update and I can safeguard user's data, but considering the timelines and constraints that I have, it wont be possible for me. Posting here so that it might help others.
Sol 2
I thought rather trying to merge the new data from bundled DB to the existing DB, I thought of merging the user data from existing db to the new db. Below are the steps done.
--> Created a new datacontext.
--> Created a new persistent co-ordinator
--> Renamed the bundled db with _v2 and copied it to the Doc directory, now we have 2 DB in the doc dir
I took some app Importing large data sets
--> Now using the ManagedObject clone category, I copied all the user info data from the existing db to the new db _v2. Found the category here NSManagedObject+Clone
--> Worked fine, now I got my _v2 database with new readonly data and the user data from the old database.
--> Now I need to give control back to the default datacontext
--> I tried to change the PSC of the old context to the new PSC but system didnt allow me to do that.
--> I then tried to change the persistence store of the old context to the new store but I got error saying that database already exists. (migratePersistentStore:toURL:options:withType:error:)
--> I ran out of ideas here.
Sol 3
I then discussed my problems with some of my other colleagues and they suggested to provide the new data in a different format and that striked. As I already mentioned, my app has logic to download new data as JSON and merge it to core data, why can I provide a JSON file with the new data, along with my app?
I collected the new response from thew webservice and created a JSON (not big just 1.5MB) and attached with app bundle, and for users that update the app, instead of core data merging I will read the JSON data locally and do the initial merging to the core data DB, there by the data base will have the new readonly data and also user data intact. After the initial merge, everything will be taken care by online sync.

Shipping a new SQLite database in app update

Our app will ship with a pre-populated database which continually downloads updates from our production server's database to stay up to date. When we make updates to our app's codebase and push those updates out to the AppStore we'll also include an updated db.sqlite file.
My questions is: when we do this I assume it will overwrite the users "old" database file that already exists?
I assume (and hope) the answer is yes, but just wanted to double check - I couldn't find any answers in Apple's documentation, but might have simply overlooked, or have been looking for the wrong terminology.
When you say "which continually downloads updates from our production server's database" I assume that means the app will download the data and update the database? In which case the database must exist in the Documents folder and it won't get into the Documents folder until you copy it there.
Normally this is done by the app if the database file doesn't already exist, however if you want to overwrite the current database you'll need a mechanism to discover if the one in the Documents folder is out-of-date, compared to the one in the app bundle. This probably means having a "metadata" table (with two columns name/value) containing a version number or some such. You read both databases and decide whether or not to copy.

App rejected due to violating iCloud storage guidelines

My app was rejected recently due to the fact that it installs a database within a directory which will be backed up to iCloud. As the database comes with a lot of prepopulated data and the app stores user generated data into the same file.
So mixing up user-generated-content with prepopulated data is not was Apple wants us to do.
So far so good.
Separate my database into two and mark the store file with the prepopulated data with NSURLIsExcludedFromBackupKey = YES.
But what happens if the user wants to modify the data in that store, because he found a failure and wish to modify it.
Or myself make an online update available which modifies values with that store.
How do I cope with that.
Do I have to delete the store file, create a new one (now with NSURLIsExcludedFromBackupKey = NO) or store the database under /tmp or /Library/caches right from the beginning and move it into /Application Support (which is backed up automatically) but with the threat that my database is being removed by the system for some reason what is the case for /Library/caches?
It is a bit annoying that Apple will not allow you to backup prepopulated data if your app is of the kind where you could actually change the prepopulated data in the app. If the prepopulated database is big, I can however understand that they don't want your app to waste the user´s iCloud space with information that is already in the AppStore.
Woody has a good idea on the approach, but I'm not sure Apple would look away from the fact that you are actually wasting just as much space if you copy the prepopulated data to the user-backed-up DB on app startup.
What about something like this:
A: DB with pre-populated data, not backed up
B: DB with user added
data, backed up
When user make changes to object in A, crete a new row in B that "overrides" the row in A, for example by using the same ID or by having a column in the DB that tells the app which object in A should be replaced by the new row in B.
Whenever you need to update your app, you will replace DB A with new content and that's it. This could lead to conflicts with the data that the user has changed. You will have to decide whether the user data is more important than the updated data, and how to handle these conflicts (for example by trying to keep them both).
If you need to change the structure of DB B in an update, for example if you need to add a column, you will have to include an update routine in your app that detects that the user is having an old DB version and write code to migrate the user data to the new database on first startup after the update.
When you startup, if the user database is unpopulated you could copy the data across from the pre-populated datatabse, and maybe give the user an option to reset defaults which does the same again?

How to split an iOS sqlite db?

I have an existing iOS app that uses core data for app data and user data. My problem is that updating app data is a nightmare (my first app, so I didn't do it ideally the first time). I would like to split the app data and user data into 2 separate sqlite dbs (or stores, correct me if my terminology is wrong).
Any tips would be appreciated.
Having two sqlite files is a good idea. The pain is splitting them now.
Create a new store that only exists in your app bundle. Make sure the data is unchanged from when you first released the app.
You are going to need to walk the "user" store and find all of the data that is identical to what exists in the "reference" store and delete it. If the user has changed that data then I would leave it and let the user sort out duplicates.
Once that is complete your app can resume normal function and load up both stores. I would set a flag somewhere so that you know this has been done and you don't run the check on every launch. The "user" store's metadata is a good place.
Note, this will need to be done before the user can "use" your app. This probably means changing your launch routines so that if a migration and filter is needed you tell the user what is going on.
I don't think having multiple persistent stores is the right solution. You can simply have separate entities within a single persistent store. Core Data will handle it properly.

Resources