Renamed Core Data Transformable - objective-c

I'm using CoreData to manage my offline storage in my app. The data of the offline storage is saved in an custom NSObject as a transformable in the xcdatamodel.
My current version in the app (v1.0) is storing the Navigation class.
I needed to rename the Navigation class to fight some name space problems in another target of my code base.
In Version 2.0 the app is crashing because when I'm searching the CoreData store the NSKeyedUnarchiver is failing because of the missing Navigation class.
What's the best approach to migrate my current CoreData store to fight this issue?
I tried something like this:
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"OfflineStorage"];
request.fetchLimit = 1;
request.predicate = [NSPredicate predicateWithFormat:#"storageID = 1"];
NSError *error;
NSArray *result;
#try {
result = [context executeFetchRequest:request error:&error];
}
#catch (NSException *exception) {
if([exception.name isEqualToString:NSInvalidUnarchiveOperationException]) {
NSFetchRequest *deleteRequest = [request copy];
deleteRequest.resultType = NSManagedObjectIDResultType;
deleteRequest.includesPropertyValues = NO;
deleteRequest.propertiesToFetch = #[];
NSArray *deleteResult = [context executeFetchRequest:request error:&error];
if(deleteResult.count) {
[context performBlockAndWait:^{
[context deleteObject:deleteResult.firstObject];
[context save:nil];
}];
}
}
}
I though I would be smart to catch the exception and try only to fetch the ObjectID to delete the corrupt data in my store. But it's not working…

Did you add a new version to your model where you renamed that transformable class as well?
Did you try to do your own migration and rename the transformer for the transformable?
Here's some documentation: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CoreDataVersioning/Articles/Introduction.html#//apple_ref/doc/uid/TP40004399-CH1-SW1
I didn't do that myself, yet. As auto migration was enough for me by now.
Update
I'm sorry, I misunderstood your question.
The transformable is transformed by your own value transformer? If so, you could use this in the NSKeyedUnarchiver to replace the class with the old name by the new class: https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSKeyedUnarchiver_Class/index.html#//apple_ref/occ/clm/NSKeyedUnarchiver/setClass:forClassName:

Related

Core Data Reading Data

I am a VERY new beginner to Core Data and I have recently been trying to read and write data. I created an entity named "Person" with the entities "name" and "age". I also have a textfield name "personName" and a textfield named "personAge".
- (IBAction)readData:(id)sender
{
NSNumber *ageNum = [NSNumber numberWithInteger:personAge.text.integerValue];
Person *newPerson = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:managedObjectContext];
newPerson.name = personName.text;
newPerson.age = ageNum;
NSLog(#"Person %# name is %#", personName.text, ageNum);
}
When I load the app, all i get is SIGABRT. Even when all I put in the method is
Person *newPerson = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:managedObjectContext];
All help is appreciated.
For Adding values to the core data you can do so:-
- (IBAction)save:(id)sender {
NSManagedObjectContext *context = [self managedObjectContext];
// Create a new managed object
NSManagedObject *person = [NSEntityDescription insertNewObjectForEntityForName:#"Person" inManagedObjectContext:context];
[person setValue:self.personName.text forKey:#"name"];
[person setValue:self.personAge.text forKey:#"age"];
NSError *error = nil;
// Save the object to persistent store
if (![context save:&error]) {
NSLog(#"Can't Save! %# %#", error, [error localizedDescription]);
}
}
For fetching the values from core data:-
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Person"];
self.personValues = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];
here personValues is a NSMutableArray.
For better understanding about these please go through this link.
http://www.appcoda.com/introduction-to-core-data/
Hope this will help you out.
If you are very new to Core Data, try using the MagicalRecord library which provides a series of helper categories for dealing with a lot of the boiler plate setup of Core Data.
Which brings me to the question you are asking: are you sure your Core Data stack is being setup correctly? Check to make sure your Managed Object Context is valid along with a proper Persistent Store Coordinator.
Best thing to do: put a breakpoint during the Core Data stack setup and step through it making sure everything is setup properly. Or install MagicalRecord and do one method call to [MagicalRecord setupAutomigratingCoreDataStack]...

How does UIManagedDocument find the default data model file?

I've been following the CS193p's lectures on Core data and I've run into a problem when I'm inserting a new managed object.
The error is:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '+entityForName: could not
locate an entity named 'Card' in this model.'
I've created the "Card" entity in my data model file. However I have a feeling it isn't finding the data model file correctly, since removing that file all together give the same error. I'm wondering how I can find out whether it is due the UIManagedDocument object not finding the data model that is causing this error.
Here is what I'm doing in my controller class:
NSURL *docURL = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *databaseURL = [docURL URLByAppendingPathComponent:#"Cards Database"];
self.cardsDatabase = [[UIManagedDocument alloc] initWithFileURL:databaseURL];
if (![[NSFileManager defaultManager] fileExistsAtPath:[self.cardsDatabase.fileURL path]]) {
[self.cardsDatabase saveToURL:self.cardsDatabase.fileURL forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {
NSLog(#"Done");
}];
} else if (self.cardsDatabase.documentState == UIDocumentStateClosed) {
NSLog(#"Closed");
[self.cardsDatabase openWithCompletionHandler:^(BOOL success) {
if (success) {
NSLog(#"Opened");
[self addSampleData];
}
}];
} else if (self.cardsDatabase.documentState == UIDocumentStateNormal) {
NSLog(#"Normal");
}
- (void)addSampleData
{
NSManagedObjectContext *context = self.cardsDatabase.managedObjectContext;
[context performBlockAndWait:^{
Card *card = [NSEntityDescription insertNewObjectForEntityForName:#"Card" inManagedObjectContext:context];
card.title = #"Test Title";
}];
[self.cardsDatabase saveToURL:self.cardsDatabase.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:^(BOOL success) {
NSLog(#"Saved");
}];
}
The error occurs on this line:
Card *card = [NSEntityDescription insertNewObjectForEntityForName:#"Card" inManagedObjectContext:context];
Try resetting the simulator(In device, remove the app), clean the build and run again. That should resolve most of the issues related to 'could not locate entities'.
This kind of issue may happen, when you updated your Core Data model, by changing names of attributes of entities, and then Run it without clean(delete) the app with the older Core Data model from the device. Unless you are planning to use light migration or model mapping, please be aware it may happen whenever you Run the app

NSPredicate (== comparison to numeric string) not working

So here's the deal:
// A. Inserting
Item *item = (Item *)[NSEntityDescription insertNewObjectForEntityForName:#"Item" inManagedObjectContext:managedObjectContext];
NSError *error = nil;
[managedObjectContext save:&error];
..
[item setItemID:#"15"];
[managedObjectContext save:&error];
NSLog(#"Error: %#", error); // outputs (null)
// B. Fetching all records
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:#"Item"];
request.returnsObjectsAsFaults = NO;
NSArray *allItems = [managedObjectContext executeFetchRequest:request error:nil];
NSLog(#"All Items: %#", allItems);
Now, this outputs a huge list, containing the previously inserted item:
"<Item: 0x7eb7bc0> (entity: Item; id: 0x7eb71c0 <x-coredata://BC6EB71C-47C0-4445-905D-7D42E6FC611B/Item/p2> ; data: {\n itemID = 15;\n})"
So far so good, but I want to check whether this particular item does exist (I know it may sound strange in this context, but it really makes sense here). However, the predicate I'm using fails (and I don't see why):
// C. Fetching a single record
NSFetchRequest *singleRequest = [[NSFetchRequest alloc] initWithEntityName:#"Item"];
singleRequest.predicate = [NSPredicate predicateWithFormat:#"itemID == %#", #"15"];
NSError *error = nil;
NSArray *results = [managedObjectContext executeFetchRequest:singleRequest error:&error];
NSLog(#"Error: %#", error); // outputs (null) again
NSLog(#"Results: %#", results); // outputs () ...
I don't really understand how to "fix" this.
Here are some other facts:
Using persistent SQLite store with CoreData (pretty much default configuration, not even relationships, just plain key-value in 3 tables).
The itemIDs always are strings
When reopening the app, the second code block, does return an item (= the item inserted in the previous run). Could it be that save: writes to disk asynchronously, and that the NSPredicate only filters items wrote to disk?
Part A happens in a different method, but on the same thread as B and C. C is directly below B and both are placed in the same method.
If you're comparing strings, try this :
#"itemID LIKE %#"
Have a read of this, the section titled 'String Comparisons"
Okay got it. I used #synthesize instead of #dynamic in the particular model's .m-file. Didn't know it would be such a big problem .. :)
For some reason, updating the SQLite-database goes wrong when using #synthesize ..

How to remove a core data persistent store

I need to delete my persistent store (doing it object by object is not practical because I have over 100,000 objects). I've tried this:
- (IBAction)resetDatabase:(id)sender {
NSPersistentStore* store = [[__persistentStoreCoordinator persistentStores] lastObject];
NSError *error = nil;
NSURL *storeURL = store.URL;
// release context and model
[__managedObjectContext release];
[__managedObjectModel release];
__managedObjectModel = nil;
__managedObjectContext = nil;
[__persistentStoreCoordinator removePersistentStore:store error:nil];
[__persistentStoreCoordinator release];
__persistentStoreCoordinator = nil;
[[NSFileManager defaultManager] removeItemAtPath:storeURL.path error:&error];
if (error) {
NSLog(#"filemanager error %#", error);
}
// recreate the stack
__managedObjectContext = [self managedObjectContext];
}
But I get this error when I try to insert entities into the store afterwards:
This NSPersistentStoreCoordinator has no persistent stores. It cannot perform a save operation.
Update:
I tried releasing the MOC and MOM before removing the persistent store but I still get the same error.
Here is how I do a "reset data" function in several apps:
- (void)reset {
// Release CoreData chain
[_managedObjectContext release];
_managedObjectContext = nil;
[_managedObjectModel release];
_managedObjectModel = nil;
[_persistentStoreCoordinator release];
_persistentStoreCoordinator = nil;
// Delete the sqlite file
NSError *error = nil;
if ([fileManager fileExistsAtPath:_storeURL.path])
[fileManager removeItemAtURL:_storeURL error:&error];
// handle error...
}
Basically I just release the CoreData chain, then delete the persistentStore file. That's what you are trying to do, without using removePersistentStore, which I do not care since I will just rebuild the persistentStore coordinator later. Then at next core data call the chain is rebuilt transparently using singleton-lazy-style constructors like :
- (NSManagedObjectModel *) managedObjectModel {
if (!_managedObjectModel)
_managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];
return _managedObjectModel;
}
You can do it externally given that you only need to do this while developing your application. I have a terminal open in which I remove the store manually before re-running my app. All you need to know is where it is located. I log it to console everytime my app runs with the following code:
[[CoreDataSingleton sharedManager] managedObjectContext]; //be sure to create the store first!
//Find targeted mom file in the Resources directory
NSString *momPath = [[NSBundle mainBundle] pathForResource:#"Parking" ofType:#"mom"];
NSLog(#"momd path: %#",momPath);
Hope that helps!
You need to make sure that any managed object context attached to the persistent store have been released before you try to delete the store. Otherwise, the context will evoke that error.

How to use a stored FetchRequest with NSFetchedResultsController?

NSFetchedResultsController provides a lot of nice stuff free when working with tableViews. I also understand that storing fetch requests in the model is good form. Can I use both of these together for super core data goodness? If so, how so?
This example from Mr. Zarra's excellent book shows the template returning an array and I've failed in my attempts to get a fetchedResultsController back.
- (NSArray*)retrieveBigMeals {
NSManagedObjectContext *moc = [self managedObjectContext];
NSManagedObjectModel *mom = [self managedObjectModel];
NSFetchRequest *request = [mom fetchRequestTemplateForName:#"bigMeals"];
NSError *error = nil;
NSArray *result = [moc executeFetchRequest:request error:&error];
if (error) {
[NSApp presentError:error]; return nil;
} return result;
Just use the ios navigation based application template with core data. They already have the code for using NSFetchedResultController there.