Core Data Entity Relationship Does Not Save Between Launches - iphone

I am writing an application that has four main entities that are all linked via relationships. Some are one to one, some are one to many. Upon initial load, three of the entities load their data from XML files stored locally to the application and one of the entities downloads an XML from the web and loads its data from it. When the app loads it performs a check to see if the data from each of these files is more recent than what it currently has and, if so, it will replace all current data in that entity with data from the appropriate file.
As part of my debug process during writing I have been forcing a delete of all data. When the delete function is called and all data is loaded at app launch the application runs beautifully and all entities and relationships behave exactly as they should. However, when I remove the call to the delete function and it performs the checks and tries to run from data it has stored, all of the relationships seem to disappear. In debugging this, I have found that all of the entities do contain all of the regular data that they are supposed to, they just don't have the relationships anymore. I can't figure out why in the world the relationships are saved on first load but don't retain when all data is not re-imported.
I would imagine some code would be helpful to anyone debugging, however, I'm not sure how much I should include. So, I will start by including just one of the methods called in the data loading class. If anything else would help, please let me know. Any help is very much appreciated.
UPDATED CODE: 2/25/11 (Based on Suggestions - Problem still exists)
UPDATED CODE: 2/25/11 - Problem Solved
- (NSArray *) loadFeatures {
if ([self checkForUpdate:#"Features"]) {
[self deleteAllObjects:#"Features"];
NSString *filePath = [self dataFilePath:FALSE withResourceName:#"Features"];
NSData *xmlData = [[NSMutableData alloc] initWithContentsOfFile:filePath];
NSError *error;
GDataXMLDocument *doc = [[GDataXMLDocument alloc] initWithData:xmlData options:0 error:&error];
NSArray *featureElements = [doc.rootElement elementsForName:#"FEATURE"];
NSMutableSet *featureSections = [[NSMutableSet alloc] init];
for (GDataXMLElement *featureElement in featureElements) {
NSString *featureName = nil;
NSNumber *featureSecure = nil;
NSNumber *featureID = nil;
NSNumber *featureSortKey = nil;
DisplayTypes *featureDisplayType = nil;
NSArray *names = [featureElement elementsForName:#"NAME"];
if (names.count > 0) {
GDataXMLElement *firstName = (GDataXMLElement *) [names objectAtIndex:0];
featureName = firstName.stringValue;
} else continue;
NSArray *secures = [featureElement elementsForName:#"SECURE"];
if (secures.count > 0) {
GDataXMLElement *firstSecure = (GDataXMLElement *) [secures objectAtIndex:0];
featureSecure = [NSNumber numberWithInt:firstSecure.stringValue.intValue];
} else continue;
NSArray *featureIDs = [featureElement elementsForName:#"FEATUREID"];
if (featureIDs.count > 0) {
GDataXMLElement *firstFeatureID = (GDataXMLElement *) [featureIDs objectAtIndex:0];
featureID = [NSNumber numberWithInt:firstFeatureID.stringValue.intValue];
}
NSArray *featureSortKeys = [featureElement elementsForName:#"SORTKEY"];
if (featureSortKeys.count > 0) {
GDataXMLElement *firstSortKey = (GDataXMLElement *) [featureSortKeys objectAtIndex:0];
featureSortKey = [NSNumber numberWithInt:firstSortKey.stringValue.intValue];
}
NSArray *featureDisplays = [featureElement elementsForName:#"DISPLAYTYPEID"];
if (featureDisplays.count > 0) {
GDataXMLElement *firstFeatureDisplay = (GDataXMLElement *) [featureDisplays objectAtIndex:0];
for (DisplayTypes *thisDisplayType in self.displayTypes) {
if (thisDisplayType.displayTypeID == [NSNumber numberWithInt:firstFeatureDisplay.stringValue.intValue]) {
featureDisplayType = thisDisplayType;
}
}
}
NSArray *sectionElements = [featureElement elementsForName:#"SECTIONS"];
for (GDataXMLElement *sectionElement in sectionElements) {
NSArray *sectionIDs = [sectionElement elementsForName:#"SECTION"];
for (GDataXMLElement *sectionID in sectionIDs) {
NSArray *thisSectionIDs = [sectionID elementsForName:#"SECTIONID"];
if ([thisSectionIDs count]) {
GDataXMLElement *thisSectionID = (GDataXMLElement *) [thisSectionIDs objectAtIndex:0];
for (Sections *thisSection in self.sections) {
if ([thisSection.sectionID isEqualToNumber:[NSNumber numberWithInt:thisSectionID.stringValue.intValue]]) {
[featureSections addObject:thisSection];
}
}
}
}
}
NSManagedObjectContext *context = [self managedObjectContext];
NSManagedObject *featureInfo = [NSEntityDescription insertNewObjectForEntityForName:#"Features" inManagedObjectContext:context];
[featureInfo setValue:featureName forKey:#"name"];
[featureInfo setValue:featureSecure forKey:#"secure"];
[featureInfo setValue:featureID forKey:#"featureID"];
[featureInfo setValue:featureSortKey forKey:#"sortKey"];
[featureInfo setValue:featureDisplayType forKey:#"display"];
[[featureInfo mutableSetValueForKey:#"section"] unionSet:featureSections];
NSError *error;
if (![context save:&error]) {
NSLog(#"Whoops, couldn't save: %#", [error localizedDescription]);
}
[[self.managedObjectContext objectWithID:featureDisplayType.objectID] addFeatureObject:featureInfo];
[self.managedObjectContext save:&error];
[featureSections removeAllObjects];
}
[xmlData release];
[doc release];
[featureSections release];
}
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Features" inManagedObjectContext:[self managedObjectContext]];
[fetchRequest setEntity:entity];
NSError *error;
NSArray *featureArray = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
[fetchRequest release];
return featureArray;
}
UPDATE: 5/25/2011
Per request I am posting a couple of screen shots.
1) This is what I get when the app loads after all data has been deleted and the relationships are in tact
2) This is what I get when the app runs again without first deleting and reloading data. The tabs at the bottom are created by one of the entities, and are titled a bit different. This happens because the relationship with the DisplayType is not present and it doesn't know what type of view controller to load and it doesn't know which icon to use for the tab.

Typically, you wouldn't need to explicitly set both sides of a relationship. When you're dealing with a to-many relationship, it's probably safer to add one entity at a time to the collection, instead of setting the collection all at once. So, instead of:
[featureInfo setValue:[NSSet setWithSet:featureSections] forKey:#"section"];
I would loop through the featureSections Set and add each object one by one to the section relationship of the Feature entity, e.g.:
for (Sections *aSection in featureSections) {
// use the automatically-generated relationship mutator
[featureInfo addSectionsObject:aSection];
}
I hope this helps...
Otherwise, this section in the Apple documentation might be of interest.

Related

Crash without error upon iterating through NSFetchRequest results

I'm using an NSFetchRequest to get all core data entities in a context, and I'm moving them to another context.
This works fine doing it with just 1 entity. I can iterate through results very quickly. The entity has a relationship with another entity however, so I need to run a second NSFetchRequest to get the entities which are joined by this relationship. It's the second NSFetchRequest which is causing the crash. The crash just makes the app quit to the homescreen and no errors appear in the xcode log. I've commented out my code to figure out that this is the problem, and have got it down to this:
NSError *error;
NSFileManager *manager = [NSFileManager defaultManager];
NSManagedObjectContext *oldContext = [self version1ManagedObjectContext];
TICDSSynchronizedManagedObjectContext *newContext = [self version1_1ManagedObjectContext];
NSFetchRequest *oldFetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *oldEntryEntity = [NSEntityDescription entityForName:#"Entry"
inManagedObjectContext:oldContext];
[oldFetchRequest setEntity:oldEntryEntity];
[oldFetchRequest setFetchBatchSize:10];
NSArray *entrys = [oldContext executeFetchRequest:oldFetchRequest error:&error];
int totalEntries = [oldContext countForFetchRequest:oldFetchRequest error:nil];
NSLog(#"total entries: %i", totalEntries);
int i = 0;
while (i < totalEntries) {
#autoreleasepool {
Entry *entry = [entrys objectAtIndex:i];
Entry *newEntry = [NSEntityDescription
insertNewObjectForEntityForName:#"Entry"
inManagedObjectContext:newContext];
//Taking out this fetch request means it functions fine
NSFetchRequest *mediaRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *mediaEntity = [NSEntityDescription
entityForName:#"Media"
inManagedObjectContext:oldContext];
[mediaRequest setEntity:mediaEntity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:
#"(entry == %#)", entry];
[mediaRequest setPredicate:predicate];
NSArray *mediaItems = [oldContext executeFetchRequest:mediaRequest error:&error];
int totalMediaItems = [oldContext countForFetchRequest:mediaRequest error:nil];
NSLog(#"total media items: %i", totalMediaItems);
int i2 = 0;
while (i2 < totalMediaItems) {
#autoreleasepool {
Media *newMedia = [NSEntityDescription
insertNewObjectForEntityForName:#"Media"
inManagedObjectContext:newContext];
i2++;
}
}
[newContext save:&error];
i++;
}
}
Any ideas why this might be crashing my app?
In answer to any questions about what I'm trying to do - I'm trying to migrate data between 2 versions. Standard data migration, using mapping, does not work with large data, such as NSData.
I believe that there are few more code lines that you don't expose here. The ones where you copy the data from the old entities to the new ones...
Maybe the problem is there.
Anyway, I would add NSLog between every 2 lines of code and see which is the last one that is printed...
I think that something is autoreleased in the inner loop and you try to use it in the next iteration.
I suggest that you edit the scheme that you are using to build and enable everything for the diagnostics tab. This will probably spit out whatever might be causing the issue.

Core Data issue. Insert new NSManagedObject

I want to insert 200 5Mb records in my Core Database. But when I save the NSManagedObject, the memory wasn't released (autoreleased pool didn't help), and after inserting 30 records I got the memory warning and the application crashed. Here is my code
- (void)SaveItem
{
NSString *entityName = kEntityName;
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = appDelegate.managedObjectContext;
NSEntityDescription *entityDesctiption = [NSEntityDescription
entityForName: entityName
inManagedObjectContext:context];
// check if town exists
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"id == %d", self.imageID];
NSFetchRequest *requestToCheckExistense = [[NSFetchRequest alloc] init];
[requestToCheckExistense setEntity:entityDesctiption];
[requestToCheckExistense setPredicate:predicate];
NSArray *objects = [context executeFetchRequest:requestToCheckExistense error:nil];
[requestToCheckExistense release];
if (objects == nil)
{
NSLog(#"there was an error");
}
NSManagedObject *object;
if ([objects count] > 0)
{
// edit item
object = [objects objectAtIndex:0];
}
else
{
// if object doesn't exist, find max id to imlement autoincrement
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDesctiption];
request.propertiesToFetch = [NSArray arrayWithObjects: #"id", nil];
NSArray *allobjects = [context executeFetchRequest:request error:nil];
[request release];
NSInteger newID = 1;
if ([allobjects count] > 0)
{
NSNumber *maxID = [allobjects valueForKeyPath:#"#max.id"];
newID = [maxID intValue] + 1;
}
// write item
object = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context];
[object setValue:[NSNumber numberWithInt:newID] forKey:#"id"];
self.imageID = newID;
}
// fill NSManagedObject
// size of objNSData is about 5MB
NSMutableData *objNSData = [[DatabaseManager sharedDatabaseManager] encryptedDataFromImage:bigImage];
[object setValue:objNSData forKey:#"big"];
[context save:nil];
}
When I commented
[object setValue:objNSData forKey:#"big"];
everything was OK.
I tried to add the code into #autoreleasepool , but that didn't help.
I know, that now, when I save data to database, it's still in iPhone RAM. How to release it from this memory? When I get a set of Managed Objects from the database, they are not in the RAM (I can easyly get 100 object, each of them has 5Mb fields)
object =(tblEntity *) [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:context];
try to type cast the object,this may solve the problem
I've solved the issue.
after call of [self SaveItem];
I used
[context save];
[context reset];
[context save];
all the NSManagedObjects from the context will be released.
After that operation I can add as many big objects as I want
Because you don't own an NSManagedObject when you create it, it may be retained by the core data stack even after releasing it (when using an autoreleasepool contained inside the loop).
This may help:
Set the undo manager of your managedobjectContext to nil:
[context setUndoManager:nil];
Be sure that no properties of that object are retained anywhere, because then the managed object will not be released on time inside your loop.
Be sure to add an autorelease pool inside every loop execution, not wrapping all the loop itself, similar to:
for(i;i<n;i++) {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
[obj saveItem];
[pool drain];
}
If that object belongs to a hierarchy of NSManagedObjects, then you need to release the owner of this object too, for this one to be deallocated from memory.
You can check apple's documentation about memory management in CoreData.
Warning: big objects (> 1MB) are not recommended by Apple to be stored inside CoreData (Check this other question/answer.)

Cannot fetch data from Core Data sqlite DB

First i want to apologize for starting more than one thread about this problem but i have taken step-by-step approach and steps to solve this very irritating problem. I have spend four days now to try to solve this.
The situation: I have an application where i use a pre-populated sqlite database (106k), via Core Data. I have moved the sqlite DB to the Resources folder and being able to copy it into documents. When i test i can see that the file exist, see code example and NSLog below.
I have used example code in the delegate, see below.
Everything works perfectly in the simulator but not when i test on the device.
Here is the code from the delegate:
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
if (persistentStoreCoordinator_ != nil) {
return persistentStoreCoordinator_;
}
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"FamQuiz_R0_1.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:#"FamQuiz_R0_1" 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_;
}
Here is the code when i read the database:
- (void)createQuestionsList: (NSMutableArray *)diffArray {
NSLog(#">>createQuestionsList<<");
NSFileManager *filemgr = [NSFileManager defaultManager];
NSString *docsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *filePath = [docsDirectory stringByAppendingPathComponent:#"FamQuiz_R0_1.sqlite"];
if ([filemgr fileExistsAtPath: filePath ] == YES)
NSLog (#"\n\n\nFile exists in createQuestionList\n\n\n");
else
NSLog (#"\n\n\nFile not foundin createQuestionList\n\n\n");
int nrOfQuestions = [[diffArray objectAtIndex:0]intValue];
int nrOfEasyPlayers = [[diffArray objectAtIndex:1]intValue];
int nrOfMediumPlayers = [[diffArray objectAtIndex:2]intValue];
int nrOfHardPlayers = [[diffArray objectAtIndex:3]intValue];
int nrOfPlayers = nrOfEasyPlayers + nrOfMediumPlayers + nrOfHardPlayers;
allHardArrayQ = [[NSMutableArray alloc]init];
allMediumArrayQ = [[NSMutableArray alloc]init];
allEasyArrayQ = [[NSMutableArray alloc]init];
allDummyQ = [[NSMutableArray alloc]init];
allJointQ = [[NSMutableArray alloc]init];
hardQ = [[NSMutableArray alloc]init];
mediumQ = [[NSMutableArray alloc]init];
easyQ = [[NSMutableArray alloc]init];
masterQuestionsArray = [[NSMutableArray alloc] initWithObjects:
[NSNumber numberWithBool:[[diffArray objectAtIndex:4]boolValue]],
[NSNumber numberWithInt:nrOfHardPlayers],
[NSNumber numberWithInt:nrOfMediumPlayers],
[NSNumber numberWithInt:nrOfEasyPlayers],
[NSNumber numberWithInt:nrOfQuestions],
nil];
NSLog(#"masterQuestionsArray %#", masterQuestionsArray);
NSError *error;
//=========PREPARE CORE DATA DB===========//
if (managedObjectContext == nil) { managedObjectContext = [(FamQuiz_R0_1AppDelegate *)
[[UIApplication sharedApplication] delegate] managedObjectContext]; }
// Define qContext
NSManagedObjectContext *qContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"questions" inManagedObjectContext:qContext];
[fetchRequest setEntity:entity];
NSArray *fetchedObjects = [qContext executeFetchRequest:fetchRequest error:&error];
for (NSManagedObject *info in fetchedObjects) {
if ([[info valueForKey:#"qDiff"] intValue] == 1) {
[allEasyArrayQ addObject:[info valueForKey:#"idQ"]];
} else if ([[info valueForKey:#"qDiff"] intValue] == 2) {
[allMediumArrayQ addObject:[info valueForKey:#"idQ"]];
} else if ([[info valueForKey:#"qDiff"] intValue] == 3) {
[allHardArrayQ addObject:[info valueForKey:#"idQ"]];
}
}
NSLog(#"allEasyArrayQ %#", allEasyArrayQ);
NSLog(#"allMediumArrayQ %#", allMediumArrayQ);
NSLog(#"allHardArrayQ %#", allHardArrayQ);
Here is the output:
2011-04-25 19:35:45.008 FamQuiz_R0_1[963:307] >>createQuestionsList<<
2011-04-25 19:35:45.021 FamQuiz_R0_1[963:307]
File exists in createQuestionList
2011-04-25 19:35:45.031 FamQuiz_R0_1[963:307] masterQuestionsArray (
1,
0,
0,
1,
5
)
2011-04-25 19:35:45.238 FamQuiz_R0_1[963:307] allEasyArrayQ (
)
2011-04-25 19:35:45.246 FamQuiz_R0_1[963:307] allMediumArrayQ (
)
2011-04-25 19:35:45.254 FamQuiz_R0_1[963:307] allHardArrayQ (
)
2011-04-25 19:35:45.311 FamQuiz_R0_1[963:307] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[NSMutableArray objectAtIndex:]: index 0 beyond bounds for empty array'
I REALLY appreciate all help i can get to solve this extremely frustrating problem :-)
UPDATE
I did put in a NSLog in the delegate to check the storeURL and got the following result when running on the external iPhone device:
storeURL: file://localhost/var/mobile/Applications/7F5FDB03-0D22-46BC-91BC-4D268EB4BBEB/Documents/FamQuiz_R0_1.sqlite
The following is when i run in the simulator, without the external iPhone device:
storeURL: file://localhost/Users/PeterK/Library/Application%20Support/iPhone%20Simulator/4.2/Applications/E27EC1A4-3A87-4A1B-97DE-D464679005BE/Documents/FamQuiz_R0_1.sqlite
The crash you are getting is not coming from the posted code. The last line in the code is:
NSLog(#"allHardArrayQ %#", allHardArrayQ);
... which prints.
Somewhere down the line from the provided code you make a call like this:
[someArray objectAtIndex:0]
... while someArray is empty and that causes the out of range error. Most likely, someArray is one of the three empty arrays just logged but not necessarily.
Your arrays are showing empty because (1) you are getting no returns from your fetch or (2) the values for qDiff and idQ aren't what or where you think they are. To confirm, add logs:
NSArray *fetchedObjects = [qContext executeFetchRequest:fetchRequest error:&error];
NSLog(#"fetchedObjects =",fetchedObjects);
for (NSManagedObject *info in fetchedObjects) {
NSLog(#"[info valueForKey:#"qDiff"]",[info valueForKey:#"qDiff"]);
NSLog(#"[info valueForKey:#"idQ"]",[info valueForKey:#"idQ"]]);
if ([[info valueForKey:#"qDiff"] intValue] == 1) {
[allEasyArrayQ addObject:[info valueForKey:#"idQ"]];
....
... that well tell you if you are getting the data in the first place. If not then you need to look at the configuration of your Core Data stack.
More generally, I think your code demonstrates that you haven't quite conceptually grasped Core Data yet. Instead, you seem to being trying to treat Core Data as a lightweight object wrapper around SQL. This is a common misperception among those skilled at SQL but new to Core Data and it causes a lot of design issues.
Firstly, there is no reason to check for the physical presence of the store file after you have initialize the persistent store. The NSPersistentStore object in memory cannot exist without the store file so if the object is there, the file is always there.
Secondly, you are piling on a lot of additional data structures in the form of arrays. This is a common need when dealing with raw SQL but is almost always unnecessary when using Core Data. The entire point of Core Data is to manage data manually (persisting it to the disk is just an option and is not required.) All your arrays should most likely be entities in the data model and you should be using fetches and sorts to pull out specific pieces of data ordered for the immediate needs of any particular view.
It's very common to see people with a strong SQL or similar background doing way, way more work than needed to implement Core Data. It's like someone who has drive a manual transmission for year who switches to an automatic. They keep stepping on the non-existent clutch and fiddling with the gear shift.
Seems like you are not getting anything from the context, all three arrays are printed empty. And the exception seems to be thrown from out of what you ve pasted here. Frankly, there are irrelevant, redundant parts like;
//=========PREPARE CORE DATA DB===========//
if (managedObjectContext == nil) { managedObjectContext = [(FamQuiz_R0_1AppDelegate *)
...which is never used, but the main problem is i guess, you are trying to access an empty array assuming something from the context comes in there, but apparently not...
You should try it step by step and be sure that your context operations return anything...
And clean the code a bit so that you can see the problem easier, or anyone who is trying to help you...
PROBLEM SOLVED
After spending yet another evening trying to debug every line in the code, without understanding, i downloaded "iPhone Explorer" and manually deleted the two instances of the DB directly on the device and after that it finally worked.
I noticed that the DB was not copied to the documents folder, there an empty DB resided that was smaller that the populated DB.
Now both DB instances is the correct ones :-) ...meaning that copy works now.
If you want the "iPhone Explorer" you will find it here: http://www.macroplant.com/iphoneexplorer/

Core Data update if exists or create new managed object. Can I make this faster?

I have the following code, and I was wondering if theres any way to make this faster. Basically my app downloads some JSON (about 4000 records) from the net, and updates or creates my managed objects based on the data. At the moment it's quite slow, and I can see why, but I'm new to core data so I was wondering if there's anything I can do to make it faster?
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Company" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
NSMutableArray *coreDataArray = [[managedObjectContext executeFetchRequest:request error:nil] mutableCopy];
[request release];
for (NSDictionary *dict in arr) {
NSArray *filtered = [coreDataArray filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"(code == %#)", [dict objectForKey:#"Code"]]];
//NSLog(#"COREDATA ARRAY: %d FILTERED ARRAY: %d CODE: %# COREDATA FIRST CODE: %#", [coreDataArray count], [filtered count], [dict objectForKey:#"Code"], [[coreDataArray objectAtIndex:0] code]);
if ([filtered count] > 0) {
Company *c = [filtered objectAtIndex:0];
if ([dict objectForKey:#"Defunct"]) {
NSLog(#"DELETED DEFUNCT COMPANY");
[managedObjectContext deleteObject:c];
} else {
[c populateWithJSONDictionary:dict];
}
} else {
Company *c = (Company *)[NSEntityDescription insertNewObjectForEntityForName:#"Company" inManagedObjectContext:managedObjectContext];
[c populateWithJSONDictionary:dict];
}
float percent = (float)[arr indexOfObject:dict]/[arr count];
[self performSelectorInBackground:#selector(updateProgressView:) withObject:[NSString stringWithFormat:#"%f",percent]];
}
[coreDataArray release];
Many thanks for any help you can give.
You should check out the Core Data Programming Guide: Performance section
It has some specific advice for data import performance.
In case Apple moves the documentation again, here is a good search query on Google site:developer.apple.com core data import performance

How to store array of NSManagedObjects in an NSManagedObject

I am loading my app with a property list of data from a web site. This property list file contains an NSArray of NSDictionaries which itself contains an NSArray of NSDictionaries. Basically, I'm trying to load a tableView of restaurant menu categories each of which contains menu items.
My property list file is fine. I am able to load the file and loop through the nodes structure creating NSEntityDescriptions and am able to save to Core Data. Everything works fine and expectedly except that in my menu category managed object, I have an NSArray of menu items for that category. Later on, when I fetch the categories, the pointers to the menu items in a category is lost and I get all the menu items. Am I suppose to be using predicates or does Core Data keep track of my object graph for me?
Can anyone look at how I am loading Core Data and point out the flaw in my logic? I'm pretty good with either SQL and OOP by themselves, but am a little bewildered by ORM. I thought that I should just be able to use aggregation in my managed objects and that the framework would keep track of the pointers for me, but apparently not.
NSError *error;
NSURL *url = [NSURL URLWithString:#"http://foo.com"];
NSArray *categories = [[NSArray alloc] initWithContentsOfURL:url];
NSMutableArray *menuCategories = [[NSMutableArray alloc] init];
for (int i=0; i<[categories count]; i++){
MenuCategory *menuCategory = [NSEntityDescription
insertNewObjectForEntityForName:#"MenuCategory"
inManagedObjectContext:[self managedObjectContext]];
NSDictionary *category = [categories objectAtIndex:i];
menuCategory.name = [category objectForKey:#"name"];
NSArray *items = [category objectForKey:#"items"];
NSMutableArray *menuItems = [[NSMutableArray alloc] init];
for (int j=0; j<[items count]; j++){
MenuItem *menuItem = [NSEntityDescription
insertNewObjectForEntityForName:#"MenuItem"
inManagedObjectContext:[self managedObjectContext]];
NSDictionary *item = [items objectAtIndex:j];
menuItem.name = [item objectForKey:#"name"];
menuItem.price = [item objectForKey:#"price"];
menuItem.image = [item objectForKey:#"image"];
menuItem.details = [item objectForKey:#"details"];
[menuItems addObject:menuItem];
}
[menuCategory setValue:menuItems forKey:#"menuItems"];
[menuCategories addObject:menuCategory];
[menuItems release];
}
if (![[self managedObjectContext] save:&error]) {
NSLog(#"An error occurred: %#", [error localizedDescription]);
}
You set a NSArray as to-many relationship object
NSMutableArray *menuItems = [[NSMutableArray alloc] init];
[menuCategory setValue:menuItems forKey:#"menuItems"];
which might cause the trouble.(should throw an exception?) Relationships in CoreData are always unsorted, therefore NSSets. Add a sortIndex property to your entities for ordering.
I had the same issue. There are 2 major problems with using NSSets and Core Data: if you need non-distinct objects and need them ordered. As an example, say you have 2 entities in Core Data: professor and student. The student takes 10 classes for a degree program and you wish to have a (one-to-many) relationship from the student to the professor in order that the classes were taken. Also, the same professor may teach more than one class. This was how I overcame the issue. Create a Binary Data attribute (we'll call it profData) in student and store dictionaries that make it possible to reconstruct the data as needed. Note: don't store an array of professors, since they inherit from NSManagedObject vs. NSObject. That can cause problems. You can bolt on the required methods using a category. In this example, I created a category on Student called ProfList (Student+ProfList.h/m). This keeps the code out of the NSManagedObject subclasses, so if my attributes in Core Data change, I can regenerate the subclasses automatically without wiping out this code. Here is some sample code:
// Student+ProfList.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "Student.h"
#import "Professor.h"
#interface Student (ProfList)
- (NSArray *)getStudentsFullList;
- (void)storeStudentsFullList:(NSArray *)fullList;
#end
// Student+ProfList.m
#import "Student+ProfList.h"
#implementation Student (ProfList)
- (NSArray *)getStudentsFullList
{
NSData *storedData = self.profData;
if (!storedData) return nil;
NSMutableArray *fullList = [[NSMutableArray alloc] init];
// Retrieve any existing data
NSArray *arrayOfDictionaries = [NSKeyedUnarchiver unarchiveObjectWithData:storedData];
// Get the full professor list to pull from when recreating object array
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"Professor"];
NSSortDescriptor *alphaSort =
[[NSSortDescriptor alloc] initWithKey:#"name"
ascending:YES
selector:#selector(localizedCaseInsensitiveCompare:)];
[fetchRequest setSortDescriptors:#[alphaSort]];
NSSet *allProfessors = [NSSet setWithArray:[context executeFetchRequest:fetchRequest error:nil]];
for (NSDictionary *dict in arrayOfDictionaries) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name LIKE %#", [dict objectForKey:#"name"]];
NSSet *filteredSet = [allProfessors filteredSetUsingPredicate:predicate];
Professor *newProfessor = [filteredSet anyObject];
newProfessor.index = [dict objectForKey:#"index"];
[fullList addObject:newProfessor];
}
return fullList;
}
- (void)storeStudentsFullList:(NSArray *)fullList
{
NSMutableArray *encodedList = [[NSMutableArray alloc] init];
for (Professor *professor in fullList) {
[encodedList addObject:#{#"index" : #([encodedList count]), #"name" : professor.name}];
}
NSArray *encodedArray = [NSArray arrayWithArray:encodedList];
NSData *arrayData = [NSKeyedArchiver archivedDataWithRootObject:encodedArray];
self.profData = arrayData;
}
#pragma mark - Core Data
- (NSManagedObjectContext *)managedObjectContext
{
NSManagedObjectContext *context = nil;
id delegate = [[UIApplication sharedApplication] delegate];
if ([delegate performSelector:#selector(managedObjectContext)]) {
context = [delegate managedObjectContext];
}
return context;
}
#end
You store a local variable in a view controller, then send this message to the student instance to get the list and save it locally for use in a table view or whatever.