NSIncrementalStore - Using local and remote data - iphone

I've read a few articles about NSIncrementalStore and I'm still confused about the whole concept. In this post we can read that:
Essentially you can now create a custom subclass of NSPersistentStore,
so that instead of your NSFetchRequest hitting a local SQLite
database, it runs a method you define that can do something arbitrary
to return results (like make a network request).
Up to this point I thought that NSIncrementalStore was a perfect solution for accessing remote data and saving/caching it locally. Now, I deduce that it's a solution only for accessing remote data.
If I am right I will be thankful for any piece of advice on some work-around.
If I am wrong, where is the magic and how to implement it ? Every post/article/tutorial on NSIncrementalStore shows how easily it is to pull data from server but none of them has given even a single clue about caching things for offline viewing.
Answering, let's consider a common scenario that an app is supposed to download some data from the Internet, display it and save locally so that users may use the app offline.
Also, I am not committed to use NSIncrementalStore or something. I am just looking for the best solution and this class was described as one by some of the best experts in this field.

I was confused too for about 4 or 5 hours :)
So.
Your inherited class of NSPersistentStore is "representation" of your remote data storage.
So, for the accessing remote data and saving/caching it locally you need to do the following
1) Create subclass of NSPersistentStore and setup it.
Like that:
YOURIncrementalStore *incrementalStore = [coordinator addPersistentStoreWithType:[YOURIncrementalStore type] configuration:nil URL:nil options:nil error:&error];
where coordinator your main NSPersistentStoreCoordinator
2) Then, you need other NSPersistentStoreCoordinator, that will "coordinate local representation(incrementalStore) and context of the external storage" and provide your local storage representaton (like SQLite DB URL) to it:
[incrementalStore.backingPersistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]
But dont forget, that your new persistent store must know all your previous local state. So options dict will be:
NSDictionary *options = #{ NSInferMappingModelAutomaticallyOption : #YES,
NSMigratePersistentStoresAutomaticallyOption:#YES }
So, imho, i understand all internal work this way:
You request some data from external API. Parse it, then save to the context of your backingPersistentStoreCoordinator, then merge to the main one. So the states of all the contexts will be equal.
All of the previous text is based on work with AFIncrementalStore workaround.
My code to implement AFIncrementalStore with MagicalRecord:
- (void)addMRAndAFIS {
[MagicalRecord setupCoreDataStack];
NSURL *storeURL = [NSPersistentStore urlForStoreName:[MagicalRecord defaultStoreName]];
NSPersistentStoreCoordinator *coordinator = [NSPersistentStoreCoordinator defaultStoreCoordinator];
NSError *error = nil;
NSArray *arr = coordinator.persistentStores;
AFIncrementalStore *incrementalStore = (AFIncrementalStore*)[coordinator addPersistentStoreWithType:[PTIncrementalStore type] configuration:nil URL:nil options:nil error:&error];
NSDictionary *options = #{ NSInferMappingModelAutomaticallyOption : #YES,
NSMigratePersistentStoresAutomaticallyOption:#YES };
arr = coordinator.persistentStores;
if (![incrementalStore.backingPersistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
If we need to discuss the easiest way, you need just subclass NSIncrementalStore, setup it correctly(like i wrote), parse data, then create some context, save date to it, then save it and merge to parent context.
So you will have 2 Stores and 2 contexts, and 1 StoreCoordinator.
If i've made mistake somewhere, please refer to it.
ALSO, try: https://gist.github.com/stevederico/5316737

Related

Preparation for app release with coredata

i am coming to an end with creating version 1.0 of my new project. for the first time i am using coredata.
the application only uses 1 model, all data will be supplied by the user (so i do not load any data with the application).
of course i already working on updates for the application on different branches and see some changes in the datamodel in the future. the changes on the model will only consist of:
addition of entities
addition of attributes to existing entities
the entities do not have any relation with each other.
i have read through: iPhone app with CoreData
from there i went on to: Lightweight Migration, where i read about coredatas ability to update its model automatically if changes are minor (if i read correctly my changes are included there).
in the apple migration doc i found the code for automatic migration:
NSError *error = nil;
NSURL *storeURL = <#The URL of a persistent store#>;
NSPersistentStoreCoordinator *psc = <#The coordinator#>;
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
BOOL success = [psc addPersistentStoreWithType:<#Store type#>
configuration:<#Configuration or nil#> URL:storeURL
options:options error:&error];
if (!success) {
// Handle the error.
}
my questions are the following:
where would i put this code? i found now additional information on it
do i assume that this code will only be necessary in the updated version of the app?
do i need any other preparations on my version 1.0 app to allow later motifications & updates to coredata, or do i not have to think about this in the first release?
I have this code in the method
-(NSPersistentStoreCoordinator *)persistentStoreCoordinator
There should already be code like
if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error])
if you have let Xcode create the core data methods.
This code is only necessary in the update which introduces a new model.
As far as I know, no. This is all.

Core Data: Pre-populate SQLite Entity with Image

I am already pre-populating data for my application by first creating the database through core data, then populating that initialized file with SQLite Manager. Is it possible to pre-populate images in a SQLite table for use in core data as well?
My initial thought is to insert the images as a blob through SQLite Manager. Then based on this post, it looks like I would need to set the type to binary and import with UIImage initWithData:.
Is this doable, and if so, is this the appropriate method?
Pre-populating images to a SQLite database for use with Core Data turns out to be fairly trivial.
First configure your Core Data application, implementing your attribute to contain images as a "Binary" type. Build the application and navigate to your view utilizing Core Data within the application simulator. This will initialize the SQLite database as required for use with Core Data (assuming you've implemeted the persistentStoreCoordinator as follows).
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator != nil) {
return persistentStoreCoordinator;
}
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"YourDBName.sqlite"];
// Set up the store.
// For the sake of illustration, provide a pre-populated default store.
NSFileManager *fileManager = [NSFileManager defaultManager];
// If the expected store doesn’t exist, copy the default store.
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"YourDBName" ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
NSURL *storeUrl = [NSURL fileURLWithPath:storePath];
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
NSError *error;
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
// Update to handle the error appropriately.
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
return persistentStoreCoordinator;
}
Navigate to the application data at "Users//Library/Application Support/iPhone Simulator/User/Applications/". If you sort the folder by "Date Modified," your application will have the latest date (assuming you haven't built any other applications in the mean time). Enter the application folder and the initialized < YourDBName.sqlite > will reside in the Documents folder. Copy the SQLite database to another location (like your desktop) and delete the original file (this is necessary to allow Core Data to reload the pre-populated SQLite database you are about to create).
Open < YourDBName.sqlite > with your favorite SQLite editor (the SQLite Manager plugin for Firefox is an adequate, and free, option). Add entries to your table, inserting any images as a "BLOB."
Within XCode add < YourDBName.sqlite > as an existing file. Core Data will copy this file to the application data folder the next time you launch your application if it does not already exist there (you deleted the original right?).
Access your pre-populated images within your code with [UIImage imageWithData:< DataObject >.< ImageAttributeName >
How big are your images going to be? If they are fairly large you may be better served by storing the image in the file system and keeping a reference to its location in core data.
If the images will always exist in your app then you can package them with your bundle. If not (e.g. a user can remove unwanted images) you may have to rely on pulling the images in on first use.

how to make an iPhone app tolerant of a CoreData schema change

I have an app that uses the CoreData APIs extensively. I'm developing an updated version that adds a few fields to an entity. My app crashes upon load (unless if I blow away the private storage and start again) because of the schema changes.
The problem is when customers upgrade to the new version, I wouldn't mind running an upgrade procedure on their data the first time the app loads, but I can't even get it to load because CoreData doesn't like that the schema changes.
Is there any way to sort of tell CoreData "Its ok.. don't worry about the schema change"? Because I have only added fields and haven't renamed or deleted anything.
You should probably get a copy of Marcus Zarra's Core Data book and read up on migration (Ch. 5). But, failing that, there are some basics that are good to know. First, you need both your old model (schema) and your new model in your updated app. Second, you need to make sure that the new model is tagged as being the "current model". Third, you need to make sure that you create your NSPersistentStoreCoordinator in such a way that it automatically maps from existing model (as loaded from disk) to new model.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (persistentStoreCoordinator)
return persistentStoreCoordinator;
NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"MyDataStore.sqlite"]];
NSError *error = nil;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
// Use mapping model
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, nil];
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeUrl
options:options
error:&error])
{
[NSApp presentError:error];
}
return persistentStoreCoordinator;
}
Update
Your old model in your new app needs to be exactly the same as the model in your old app. If you are unsure that this is the case, then there are some steps that you can take to make sure. The way I do it is a bit involved - but I will outline it if/when you think that that would be helpful.

NSEntityMigrationPolicy subclass methods not being called

I am trying to migrate from one .xcdatamodel file to another. I have a NSEntityMigrationPolicy subclass, the name of which I have entered in xcode-> .xcmappingmodel file -> entity -> "custom Policy" field.
I run my app which successfully opens and runs the previous version of my data so I can only assume basic migration has worked. HOWEVER my NSEntityMigrationPolicy subclass methods are not being called so that I can run further migration code.
#implementation TestMigrationPolicy
- (BOOL)beginEntityMapping:(NSEntityMapping *)mapping manager:(NSMigrationManager *)manager error:(NSError * *)error
{
NSLog(#"this log is never shown!!!!");
return YES;
}
Does anyone have any ideas why my it might not be getting called? I am new to core data migration and I'm currently at a loss as to why this is not behaving as I feel it should.
If it helps, I am creating the persistent store like this..
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithBool:YES],
NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES],
NSInferMappingModelAutomaticallyOption,
nil];
NSError *error = nil;
persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
NSLog(#"storeUrl %#", storeUrl);
if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeUrl options:options error:&error]) {
I know this question is old but this may help others!
It's because you have the option NSInferMappingModelAutomaticallyOption set - which means lightweight migration is being run, rather than using your mapping model. Remove this option, leaving * NSMigratePersistentStoresAutomaticallyOption* in place and all should work.
I've faced the same issue. In my case this happens because Core Data is not able to found my compiled data mapping file (a file with 'cdm' extension) in the resulting application bundle. When I manually moved that file from nested bundle to the root of application bundle (MyApp.app\NestedBundle.bundle\MyMapping.cdm -> MyApp.app\MyMapping.cdm) everything worked fine. But such file layout violates current logic of application bundle structure, so I will try to make Core Data to see my cdm file in nested bundle as well.
UPD: It seems that the best solution is to use custom initialization for migration process. Very nice example can found here - http://media.pragprog.com/titles/mzcd/code/ProgressiveMigration/AppDelegate.m. I've adopted that code for searching in all bundles and it works fine.

Is a Persistent Store a requirement for Core Data on the iPhone?

I'm looking to use Core Data within my iPhone app.
The app doesn't really need to store the data that is used, but it needs to be managed and queryable.
Can Core Data be used for datasets that exist purely in memory and are not persisted to the disk?
Absolutely, just set the store type to NSInMemoryStoreType. More specifically, you set it up like this:
NSError *error = nil;
//Ignore that it is called an "NSPersistentStore", it is not persisted
NSPersistentStore *inMemoryStore = [persistentStoreCoorindator addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:&error];
if (inMemoryStore && !error) {
//It is setup
}