Hoping you can help.
I've got an iOS app that was built 3 years ago with CoreData. The opportunity arose to make some significant updates and it was deemed easier to simply start a new Xcode project while continuing to use the existing bundle identifier so that the app remained an actual update for iTune users.
My question is; does creating a new project in Xcode and manually creating a replication of the Model/Entity/Attributes from the first version prevent the data to be available in the new project?
I've got some quick code in the first version of the app that simply returns the number of records in the data (see below), but when the same code is used in the new project it returns nothing, as though there's no data in the app. iCloud was also used in the first version.
I thought that if I had the first version of the app installed, created some records, and then installed the new version, the data would still be available.
Have I misunderstood how migration works with CoreData, and am I better off getting the old project, refactoring for ARC and manually moving over the new code so that the original Entity is still used?
Any advice would be appreciated.
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *entityName = #"MyObjects";
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:entityName];
request.sortDescriptors = [NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"row_id"
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)]];
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"MyObjects" withExtension:#"mom"];
NSManagedObjectModel *managedObject = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
NSString *iCloudEnabledAppID = #"com.xxxx.myobjects";
NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:managedObject];
NSString *dataFileName = #"MyObjects.sql";
NSString *iCloudDataDirectoryName = #"Data.nosync";
NSURL *iCloud = [fileManager URLForUbiquityContainerIdentifier:nil];
NSString *iCloudData = [[[iCloud path]
stringByAppendingPathComponent:iCloudDataDirectoryName]
stringByAppendingPathComponent:dataFileName];
NSMutableDictionary *options = [NSMutableDictionary dictionary];
[options setObject:iCloudEnabledAppID forKey:NSPersistentStoreUbiquitousContentNameKey];
[coordinator lock];
[coordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:[NSURL fileURLWithPath:iCloudData]
options:options
error:nil];
[coordinator unlock];
NSManagedObjectContext *moc = nil;
if (coordinator != nil) {
moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[moc setPersistentStoreCoordinator: coordinator];
}
NSFetchedResultsController *fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request
managedObjectContext:moc
sectionNameKeyPath:nil
cacheName:nil];
[fetchedResultsController performFetch:nil];
NSLog(#">>>>>> %#", [fetchedResultsController fetchedObjects]); // returns 2 records
First, I'm not sure why you tagged this with MagicalRecord. Your sample code is all raw core data.
Second, if I were you, I would take the existing model file from the old app, and make that the first version of the data model in the new app. You could recreate the data model with the attributes and entities, etc and eventually end up with the same version hashes, but you don't need to go through that trouble if you've already got the starting point. Just start with the existing data model and move forward. Xcode and Core Data do not tie data model files to Xcode projects, and you don't have any lock in that way. The Managed Object Model files are just text files like all other code in your app.
Third, iCloud setup requires far less code in iOS7. It's pretty simple as the Core Data team decided to take care of all the setup and fallback store busywork. I suggest looking at the "What's new in Core Data" talk from WWDC2013.
And finally, have you made sure that you have actually duplicated the model with the existing store? If you have a model that is not compatible with the store, your Core Data stack will not have a persistent store attached. That is, from the top level (your NSManagedObjectContext) it'll appear to be initialized, but the attached NSPersistentStoreCoordinator will not have any stores. With no store, no data will save, and no data will load. The Managed Object Model version MUST match the version information in the store file. You can check this yourself with a calls to:
-[NSPersistentStoreCoordinator metadataForPersistentStore:]
or
+[NSPersistentStoreCoordinator metadataForPersistentStoreOfType:URL:error:]
in combination with
-[NSManagedObjectModel isConfiguration:compatibleWithStoreMetadata:]
You also mention migrations. Unless you have more than one data model, you are not actually migrating your data. But when you do have versions to migrate, you will also need to set the migration options for lightweight migrations (auto migrations) to act when you attach a store to a coordinator. I suggest reading over the Apple Documentation on Versioning and Migration.
Related
I've kinda implemented a today view extension with CoreData sharing in my app, I have multiple problems (like only one object showing when I have three) and a big one, "Terminating app due to uncaught exception 'NSObjectInaccessibleException', reason: 'CoreData could not fulfill a fault for '0xd0000000001c0004". Now, this only happens when I have the application open in the background, which leads me to believe that my app is leaving the store in a bad state, but that really shouldn't be happening. I'm using Persistent Stack, external 'library' to manage all of the CoreData (instead of AppDelegate) which is readable on https://gist.github.com/mluisbrown/7015953
CoreData Fetching from TodayView Extension:
-(void)viewDidLoad{
self.persistentStack = [[PersistentStack alloc] initWithStoreURL:self.storeURL modelURL:self.modelURL];
self.managedObjectContext = self.persistentStack.managedObjectContext;
}
- (NSURL*)storeURL
{
//NSURL* documentsDirectory = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:YES error:NULL];
// return [documentsDirectory URLByAppendingPathComponent:#"WhatIOwe.sqlite"];
NSURL *directory = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:#"group.com.bittank.io"];
NSURL *storeURL = [directory URLByAppendingPathComponent:#"WhatIOwe.sqlite"];
return storeURL;
}
- (NSURL*)modelURL
{
return [[NSBundle mainBundle] URLForResource:#"WhatIOwe" withExtension:#"momd"];
}
- (NSFetchedResultsController *)fetchedResultsController {
self.persistentStack = [[PersistentStack alloc] initWithStoreURL:self.storeURL modelURL:self.modelURL];
self.managedObjectContext = self.persistentStack.managedObjectContext;
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"OweInfo" inManagedObjectContext:managedObjectContext];
[fetchRequest setEntity:entity];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
initWithKey:#"details.date" ascending:NO];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sort]];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext sectionNameKeyPath:nil
cacheName:#"Root"];
self.fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
As suggested, I've tried a merge policy in my persistent stack:
[self.managedObjectContext.persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:self.storeURL
options:#{ NSPersistentStoreUbiquitousContentNameKey : #"WhatIOwe",
NSMigratePersistentStoresAutomaticallyOption : #YES,
NSInferMappingModelAutomaticallyOption : #YES,
NSMergeByPropertyObjectTrumpMergePolicy : #YES}
error:&error];
Another observation, on configuring my NSManagedObjectContext, passing:
[psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:self.storeURL options:nil error:&error]; allows the extension to read the store (but still throw the error I'm having), but passing
[psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:self.storeURL options:#{ NSPersistentStoreUbiquitousContentNameKey : #"iCloudStore",
NSMigratePersistentStoresAutomaticallyOption : #YES,
NSInferMappingModelAutomaticallyOption : #YES,
} error:&error]; will result in the extension not reading any data whatsoever.
Side-note: psc is passed as
__weak NSPersistentStoreCoordinator *psc = self.managedObjectContext.persistentStoreCoordinator;
self.persistentStoreCoordinator = self.managedObjectContext.persistentStoreCoordinator;
What is happening here is that you have two different processes accessing the same Core Data store, each with it's own NSPersistentStoreCoordinator.
One process has modified the store - most likely a delete. The other process has no way of knowing that this delete occurred, and already had an object in memory that pointed to the now deleted data. When that process tries to access that object it must go to the store to get the data ("firing a fault"), which no longer exists.
Core Data is not designed for this kind of use, and the capabilities of extensions are very different than applications. If your extension is able to write to the same data that the application can you may reconsider your approach and make the extension only able to read the data, and never hold the data in memory for long. This will at least mitigate the most common ways to run into these problems.
Replying to above answer and the question:
"Core Data is not designed for this kind of use"
It totally is. The assessment is correct: Something has likely been deleted in the actual app, and the extension is not aware. Fortunately CoreData provides a way to deal with this case. Check out the stalenessInterval property of NSManagedObjectContext. It defines how long your in memory cache is good for. If you're having problems because your in memory cache goes out of date from disk store change because an external process is changing them, simply set the staleness interval to 0 in your extension, and that will tell CoreData to always fetch new values from the store and ignore the in memory cache.
If you're holding a reference to an object in memory, and that object is deleted in the store, you still may have issues, so always check to make sure the object you are accessing has not been deleted.
There are a few other options if you want to get more detail. You could send notifications from your main app to your extension when things get saved to provide a manual trigger for reloading your data. You could also send specific object ids across that have been deleted or modified and use the refreshObject... method to manually refresh. Or check out mergeChangesFromContextDidSaveNotification:. You might be able to manually serialize the dictionary that expects and then send it across.
You could also have the parent app handle all database accesses and send results back via notifications. This might be unnecessary.
All of this requires a bit of work, but you're going to run into that with any
database system where the database is being accessed across multiple processes, and there is caching.
There are multiple issues with your code which are likely leading to a confused Core Data state. I can't be certain that they're causing your problem, but at the moment things are messed up badly enough that trying to debug this specific error is getting ahead of things. These problems include:
Confusion about how you're sharing data between your app and the extension.
You can do this using iCloud, or you can do it by using an app group to share the persistent store directly. You appear to be attempting both, which is not just unnecessary but also likely to cause problems keeping the extension up to date.
If you use iCloud to share data, you do not need the app group, because both the app and the extension will get their data from iCloud. You don't share a local persistent store in this case, instead the data is transferred via iCloud.
If you use an app group to share the persistent store file, you have no need of iCloud. The app and the extension both access the same file, which is located in the group container. Each can both read and write it.
Conflicting iCloud setups. You're using different values for NSPersistentStoreUbiquitousContentNameKey in different places. Even assuming that iCloud is working properly, this is going to prevent you from sharing data. If the app and extension are going to share via iCloud, they need to access the same cloud store, but you seem to be directing them to use separate cloud data stores.
You're not actually using the merge policy you're aiming for. You're passing NSMergeByPropertyObjectTrumpMergePolicy as one of the options when adding the persistent store file, but this isn't actually a valid option there. I would have expected at least a console message about this, but if there isn't one then it means Core Data is just silently ignoring that key. You set a merge policy by setting the value of the mergePolicy attribute on your managed object context. With no merge policy, you're falling back on the default NSErrorMergePolicy.
Unusual, suspicious code when adding the persistent store. In most cases with Core Data you'd add the persistent store and then later on create one or more managed object contexts. You appear to be creating the context first and only later adding a persistent store. That's not necessarily wrong but it's very unusual. If I were working on this code, it'd be a red flag to look over the life cycle of the Core Data stack very carefully. Especially if, as your code comments indicate, you're not getting any data at all in some cases.
Again I'm not sure if the above is directly causing this specific error, but they're going to be causing problems of various types and it wouldn't be surprising if the resulting confused state leads to this error.
I have an app on the store which points to the appname.sqlite file in the document directory.Here's the old code:
NSURL *storeURL = [[self applicationDocumentsDirectory]URLByAppendingPathComponent:#"MyApp.sqlite"];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:#{NSMigratePersistentStoresAutomaticallyOption : #YES,NSInferMappingModelAutomaticallyOption : #YES}; error:&error]) {
abort();
}
I now want to give an update. The new feature being a prefilled .sqlite database which is there in the bundle.See the following
NSURL *storeURL = [NSURL fileURLWithPath: [[NSBundle mainBundle] pathForResource:#"MyNewPrefilled" ofType:#"sqlite"]];
It would work ok on the fresh installation the app.But what happens to the users who have logged in and have a lot of data saved in the db (old sqlite in the app directory)? I would lose it because I now am pointing to the .sqlite in the app bundle (MyNewPrefilled.sqlite) and not the app directory one. How do I get the old data back from the old sqlite file of doc directory to my new one in the bundle?
FYI: I use code data migration already (involving version numbers in datamodels) which works ok when i change datamodel in updates.
Apple recommends to use two persistent stores (in the same model) to separate data that is user generated and data that is static. Presumably, if your data is static, you should do exactly that and have a read-only store for the static data. See for example Configurations in the Core Data Programming Guide.
Another approach is
to copy the bundle seed database into the documents directory, and then
read the data from the old store and
copy it to the new one.
Finally, you can delete the old store.
I'm trying to create a NSSQLiteStoreType with the readonly option (NSReadOnlyPersistentStoreOption). This fails if the sqlite file doesn't exist (see code below). If it does exist, the store is added without any errors.
The error I get is Cocoa Error 260:
NSFileReadNoSuchFileError = 260, // Read error (no such file)
So it looks like CoreData tries to read a file that doesn't exist, instead of creating a new one...
It seems that when adding NSReadOnlyPersistentStoreOption you can only open a previously existing store, but not create one. This doesn't make sense to me.
Is there any way to create a brand new readonly store in Core Data?
If not, is there some workaround?
// DB URL
NSFileManager *fm = [NSFileManager defaultManager];
NSURL *dbURL = [[fm URLsForDirectory:NSDocumentDirectory
inDomains:NSUserDomainMask] lastObject];
dbURL = [dbURL URLByAppendingPathComponent:#"store.sqlite"];
// Object Model
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:#"Model" withExtension:#"momd"];
NSAssert([fm fileExistsAtPath:[modelURL path]], #"File not found");
NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
// Store Coordinator
NSPersistentStoreCoordinator *coord = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
// Add a readonly SQLite store
NSError *err = nil;
NSPersistentStore *store = [coord addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil URL:dbURL
options:#{NSReadOnlyPersistentStoreOption : #YES}
error:&err];
if (store == nil) {
// I get a Cocoa Error 260.
NSLog(#"Error: %#", err);
}
Creating a new, empty, read-only store makes no sense, and the results you're seeing are exactly what would be expected. By specifying read-only you are specifically indicating that no file should be written, so as a result.... no file is written.
It's hard to tell what you're trying to accomplish. If the file were created, you would not be able to use it, since it would contain no data and since the read-only flag would prevent you from adding any data. An empty file would be exactly as useful.
But no, there is no way to tell Core Data to create a new persistent store file but have that file be read only, mainly because such an operation would be nonsensical and useless.
If you have some reason to want a persistent store file which is both empty and unwritable (and if you do, please share), you would need to
Add the persistent store without the read-only flag
Call removePersistentStore:error: to remove that persistent store
Add the persistent store again, with the read-only flag.
You will now have a persistent store which contains no data, and which you are prevented from adding data to.
A simpler alternative that is just as effective is to not create the file in the first place. An empty read-only persistent store serves literally no purpose at all, so the easy approach is to just not bother creating it.
How can I enable iCloud Core Data in an app which already uses local storage Core Data?
I've tried to use NSPersistentStoreUbiquitousContentNameKey in my persistent store options. Unfortunately, this option enables iCloud but does not transfer any of the local data to iCloud. I can't seem to get migratePersistentStore:toURL:options:withType:error: to work either. I provide the persistent store, its URL, iCloud options, etc. and it still will not migrate the existing local data to iCloud. Here's how I'm using the method:
- (void)migratePersistentStoreWithOptions:(NSDictionary *)options {
NSError *error;
self.storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:[NSString stringWithFormat:#"%#.sqlite", self.SQLiteFileName]];
NSPersistentStore *store = [self.persistentStoreCoordinator migratePersistentStore:self.persistentStoreCoordinator.persistentStores.firstObject toURL:self.storeURL options:options withType:NSSQLiteStoreType error:&error];
if (store) NSLog(#"[CoreData Manager] Store was successfully migrated");
else NSLog(#"[CoreData Manager] Error migrating persistent store: %#", error);
}
The local storage remains separate from the iCloud storage. If possible, I'd like to move the local Core Data to iCloud without manually transferring each entity.
Any ideas? I can find lots of articles, tutorials, and posts about moving back to local storage from iCloud - but I want to move from local storage to iCloud.
Here's what you'll need to do
Create a local NSPersistentStoreCoordinator
Add your existing persistent store to that coordinator and store a reference to this new returned store.
Call that handy migratePersistStore:... providing the store from #2, a URL for the store in the documents directory with a different file name and the all important options including the NSPersistentStoreUbiquitousContentNameKey key.
Here's the code, notes in-line.
NSURL *documentsDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
//This is the path to the new store. Note it has a different file name
NSURL *storeURL = [documentsDirectory URLByAppendingPathComponent:#"TestRemote.sqlite"];
//This is the path to the existing store
NSURL *seedStoreURL = [documentsDirectory URLByAppendingPathComponent:#"Test.sqlite"];
//You should create a new store here instead of using the one you presumably already have access to
NSPersistentStoreCoordinator *coord = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];
NSError *seedStoreError;
NSDictionary *seedStoreOptions = #{ NSReadOnlyPersistentStoreOption: #YES };
NSPersistentStore *seedStore = [coord addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:seedStoreURL
options:seedStoreOptions
error:&seedStoreError];
NSDictionary *iCloudOptions = #{ NSPersistentStoreUbiquitousContentNameKey: #"MyiCloudStore" };
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
//This is using an operation queue because this happens synchronously
[queue addOperationWithBlock:^{
NSError *blockError;
[coord migratePersistentStore:seedStore
toURL:storeURL
options:iCloudOptions
withType:NSSQLiteStoreType
error:&blockError];
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[mainQueue addOperationWithBlock:^{
// This will be called when the migration is done
}];
}];
Note that after you do this migration, you'll need to configure the persistent store you use with your MOC with the new URL and always include the iCloudOptions above with the NSPersistentStoreUbiquitousContentNameKey key.
This was based on Apple's documentation.
After completion, you should see a new folder in your Documents folder in the simulator folder (~/Library/Application Support/iPhone Simulator/...) labeled CoreDataUbiquitySupport. Nested deep in there is your iCloud synced sqlite store.
Tada!
EDIT: Oh and make sure you have created an iCloud entitlement and included it in your bundle. You should be able to do that all within Xcode, but you can update it on the development portal too.
Take a look at this sample app which includes code to migrate a local core data store to iCloud and back again. Best read the associated docs and build the sample apps in your environment to get them working and once they are working then try and refactor your code to use a similar approach.
Feel free to send me an email for further help. Apologies for not giving you an answer here but it can be quite a complicated issue to deal with.
http://ossh.com.au/design-and-technology/software-development/sample-library-style-ios-core-data-app-with-icloud-integration/
Hi I am a begginer with IOS and have created my first core data app. I have created my modal and included it in the project. It does contain the entity 'Client'. When I use the following test code to create the database and attempt to add a Entity I get the following error
..'+entityForName: could not locate an NSManagedObjectModel for entity name 'ItemMake''
when calling [NSEntityDescription insertNewObjectForEntityForName:#"Client" inManagedObjectContext:contxt];
I have checked everything I can think off, the code is shown below. This is some further information...
a) mdl and contxt are not nil.
b) database file is created in the simulator (16kb)
c) accessing the database from another tool shows only two tables which appear to be SQLlite system tables
d) The package does contain a 'EasyOrder.momd' directory and a 'EasyOrder.mom' file. The app is called 'EasyOrder'.
e) The project contains a 'EasyOrder.xcdatamodeld' file which has a Client entity (among others) defined.
f) I have tried other enities in my database with the same results
g) My was a standard app that I added core data support too. I am not using default code in the app delegate, but my examples do not either.
Code as follows
NSManagedObjectContext *contxt;
NSManagedObjectModel *mdl;
NSPersistentStoreCoordinator *ps;
mdl = [NSManagedObjectModel mergedModelFromBundles:nil];
ps = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mdl];
NSArray *documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *docDir = [documentDirectories objectAtIndex:0];
NSString *dbLoc = [docDir stringByAppendingPathComponent:#"test.sqllite"];
NSURL *databaseUrl = [NSURL fileURLWithPath:dbLoc];
NSError *err;
if ([ps addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:databaseUrl
options:nil
error:&err] == false) {
NSLog(#"%#", err);
}
[contxt setUndoManager:nil];
//Add record
//CRASHES HERE !
Client *c = [NSEntityDescription insertNewObjectForEntityForName:#"Client" inManagedObjectContext:contxt];
[c setEntityRef:1234];
....
tia
Rob
I have just solved the issue. I believe there was some sort of versioning problem where the data model being used was old and contained no tables.
I created a test app with a dummy model (just one table) and it worked fine. I then removed that model and used the one from my actual application. It continued to work fine but did not created the tables as per the data model. So I removed the run time from the simulator and it then properly created all tables.
I followed the same process in my actual app, removed from simulator, removed model, added model with a different name and re built. App now works !