Adding data to an entity through a view controller - ios

I have a case where I should insert object into an entity via UIViewController. I have designed my database model (Entity and attributes). I'm adding the entity through a UIViewController. What am I supposed to add in the didFinishLaunchingwithOptions method in appDelegate.m?
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch
return YES;
}
And for the TUTViewController (My own view controller - UIViewController) I have used the below code for inserting object.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
NSEntityDescription *entity = [[self.fetchedResultsController fetchRequest] entity];
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
NSString *stripped1 = [response stringByReplacingOccurrencesOfString:#"\r" withString:#""];
NSMutableArray *rows = [NSMutableArray arrayWithArray:[stripped1 componentsSeparatedByString:#"\n"]];
NSMutableArray *contentArray = [NSMutableArray arrayWithCapacity:[rows count]];
NSMutableArray *contentArray1 = [NSMutableArray arrayWithCapacity:[rows count]];
NSArray *components;
NSLog(#"count:%d",[rows count]);
for (int i=0;i<[rows count]; i++) {
if(i == 0 || [[rows objectAtIndex:i] isEqualToString:#""]){
continue;
}
components = [[rows objectAtIndex:i] componentsSeparatedByString:#","];
id x = [components objectAtIndex:0] ;
id y = [components objectAtIndex:1];
id z = [components objectAtIndex:2];
[contentArray addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:x,#"X",y,#"Y", nil]];
[contentArray1 addObject:[NSMutableDictionary dictionaryWithObjectsAndKeys:x,#"X",z,#"Y", nil]];
[newManagedObject setValue:[x] forKey:#"timeStamp"];
[newManagedObject setValue:[y] forKey:#"beat"];
[newManagedObject setValue:[z] forKey:#"rate"];
// Save the context.
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
NSLog(#"Contents of Uterus Contraction: %#",contentArray);
NSLog(#"Contents of Heart Beat: %#",contentArray1);
}
}
}
Is there anything that I'm missing? I'm ending up with the error:
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: '+entityForName: could not
locate an NSManagedObjectModel for entity name 'FetalData''

Did you set up the Core Data Stack or a UIManagedDocument object?
If you didn't set up the Managed Object Model this could be the problem. It means you're probably not loading Managed Object Model that defines FetalData entity. See insertnewobjectforentityforname for further info.
I really suggest to create an empty project and let Xcode to create Core Data stuff for you. In this manner you can see how the code works.
Some Notes
Why do you use a NSFetchResultsController?
Move the save call at the end of your method. In this manner you avoid multiple round trips to the disk.
If you want to start using Core Data, I suggest you core-data-on-ios-5-tutorial-getting-started.
Hope it helps.

Related

IOS Core Data - Find or Create duplicates inserts

I'm missing something in my logic when trying to sync web service data with local store and I need your help. This is what I've got:
I have one NSArray of NSDictionaries describing each event object (downloaded from web), which I sort by event id. Then I fetch local store using IN predicate and also sort it by event id. Then I try to iterate and match the ids and if they match, i update record and if they don't match i create new NSManagedObject. It works fine if the newly downloaded event object has a greater eventID than last eventID in local store, but if the eventID from web service is smaller than the one in local store then it INSERTS ALL OBJECTS, no matter if they exist or not and that exactly is my problem.
So in other words, if a new record is at the beginning of sorted array it will add every object, but if it is at the end of sorted array it will update all except the new one. I need it to create the new one and update old ones.
Here's some code:
The function with the logic where I believe I'm missing something:
- (void)findOrCreateObject:(NSArray*)eventArray
{
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];
//get sorted stored records
NSArray *fetchedRecords = [self.fetchedResultsController fetchedObjects];
//sort dictionaries
NSSortDescriptor *aSortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"id" ascending:YES];
NSArray *downloadedRecords = [self.events sortedArrayUsingDescriptors:[NSArray arrayWithObject:aSortDescriptor]];
NSLog(#"DOWNLOADED EVENTS = %#", downloadedRecords);
NSLog(#"FETCHED EVENTS = %#", fetchedRecords);
//if store is not empty we need to walk through data and add/update records, otherwise/ELSE we need to import initial data
if (fetchedRecords.count != 0) {
//stores has records already
NSLog(#"FIND OR CREATE PROCESS");
if ([downloadedRecords count] > 0) {
NSArray *storedRecords = [self fetchEvents:eventArray withContext:context];
NSUInteger currentIndex = 0;
for (NSDictionary* event in downloadedRecords) {
Event* eventObject = nil;
if ([storedRecords count] > currentIndex) {
eventObject = [storedRecords objectAtIndex:currentIndex];
}
NSLog(#"STRING VALUE OF KEY = %#", [[eventObject valueForKey:#"eventID"]stringValue]);
if ([[event valueForKey:#"id"] isEqualToString:[[eventObject valueForKey:#"eventID"] stringValue]]) {
//Update Record
NSLog(#"Updating Record!!!");
[self updateManagedObject:eventObject withRecord:event inContext:context];
}
else
{
//New Record
NSLog(#"Inserting Record!!!");
eventObject = (Event*)[NSEntityDescription insertNewObjectForEntityForName:#"Event" inManagedObjectContext:context];
eventObject.eventID = [self makeNumberFromString:[event valueForKey:#"id"]];
eventObject.title = [event valueForKey:#"title"];
eventObject.venue = [event valueForKey:#"venue"];
}
currentIndex++;
}
}
}
else
{
//import initial data
NSLog(#"IMPORTING INITIAL DATA");
for (NSDictionary* event in downloadedRecords) {
Event *eventObject = (Event*)[NSEntityDescription insertNewObjectForEntityForName:#"Event" inManagedObjectContext:context];
eventObject.eventID = [self makeNumberFromString:[event valueForKey:#"id"]];
eventObject.title = [event valueForKey:#"title"];
eventObject.venue = [event valueForKey:#"venue"];
}
}
// Save the context.
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
}
}
The FETCHEVENTS method:
-(NSArray*)fetchEvents:(NSArray*)eIDs withContext:(NSManagedObjectContext*)context
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Event" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(eventID IN %#)", eIDs];
[fetchRequest setPredicate:predicate];
[fetchRequest setSortDescriptors:#[ [[NSSortDescriptor alloc] initWithKey: #"eventID" ascending:YES] ]];
NSError *error = nil;
NSArray *fetchedObjects = [context executeFetchRequest:fetchRequest error:&error];
if (fetchedObjects == nil) {
NSLog(#"No rows returned");
}
return fetchedObjects;
}
The Update Object method:
- (void)updateManagedObject:(NSManagedObject*)object withRecord:(NSDictionary*)record inContext:(NSManagedObjectContext*)context
{
[object setValue:[self makeNumberFromString:[record valueForKey:#"id"]] forKey:#"eventID"];
[object setValue:[record valueForKey:#"title"] forKey:#"title"];
[object setValue:[record valueForKey:#"venue"] forKey:#"venue"];
}
I'm calling findOrCreate method once I download the web service data and parse it.
Let me know if you have any other questions.
Try this,
- (void)findOrCreateObject:(NSArray*)eventArray {
//if store is not empty we need to walk through data and add/update records, otherwise/ELSE we need to import initial data
if (fetchedRecords.count != 0) {
//stores has records already
NSLog(#"FIND OR CREATE PROCESS");
if ([downloadedRecords count] > 0) {
NSArray *storedRecords = [self fetchEvents:eventArray withContext:context];
for (NSDictionary* event in downloadedRecords) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"eventID = %#",[event valueForKey:#"id"]];
NSArray *matchedArray = [storedRecords filteredArrayUsing
Predicate:predicate];
Event* eventObject = nil;
if ([matchedArray count] > 0) {
//Update Record
NSLog(#"Updating Record!!!");
eventObject = [matchedArray objectAtIndex:0];
[self updateManagedObject:eventObject withRecord:event inContext:context];
}
else
{
//New Record
NSLog(#"Inserting Record!!!");
eventObject = (Event*)[NSEntityDescription insertNewObjectForEntityForName:#"Event" inManagedObjectContext:context];
eventObject.eventID = [self makeNumberFromString:[event valueForKey:#"id"]];
eventObject.title = [event valueForKey:#"title"];
eventObject.venue = [event valueForKey:#"venue"];
}
}
}
} else {
.....
}
}
I think, every time you insert a new event object, you should update storedObjects such that it should now contain the inserted object.
Or more simply, you should put the initialisation line of storedObjects inside your for loop. (This would make sure that as you enumerate from the beginning of downloadedObjects every eventObject will have the same index on it as on storedObjects. But, with regards to your code this will only be true if all elements of storedObjects will always be found in downloadedObjects which, I assume is the case.)
One thing though, isn't fetchedRecords supposed to be the same as storedObjects, if they are you should just reassign storedObjects as [self.fetchedResultsController fetchedObjects], as it would reflect the changes in your context without executing another fetch request which would solve the inefficiency of the suggestion above.

Populating an Array from Core Data in Xcode

I am trying to populate an NSArray with a collection of data I get from CoreData. But my array doesnt seem to be populating with the data. I have the following code to retrieve the data:
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc]
initWithEntityName:#"WeightLog"];
self.contactarray = [[managedObjectContext executeFetchRequest:fetchRequest
error:nil] mutableCopy];
And I am using the following for loop to populate the NSArray with the data I collect from WeightLog for a particular field.
for (int i =0; i<=self.contactarray.count; i++) {
NSManagedObject *device = [self.contactarray objectAtIndex:i];
[titleNames addObject:device];
}
Just so you know contactarray is a property in my .h file of the following format:
#property (strong) NSMutableArray *contactarray;
Can you tell me where I am going wrong please, I am fairly new to iOS Development, if it doesn't show.
Thank you in advance
Initialise titleNames array before use. Try this,
titleNames = [[NSMutableArray alloc] init];
for (int i =0; i<=self.contactarray.count; i++) {
NSManagedObject *device = [self.contactarray objectAtIndex:i];
[titleNames addObject:device];
}
Just call this user-defined method. for ex -
self.titleNames = [self selectAllRowInEntity:#"WeightLog"];
-(NSArray *) selectAllRowInEntity:(NSString *) entityName
{
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fRequest;
NSEntityDescription *eDesc;
NSArray *arr;
fRequest = [[NSFetchRequest alloc] init];
eDesc = [NSEntityDescription entityForName:entityName inManagedObjectContext:managedObjectContext];
[fRequest setEntity:eDesc];
arr = [managedObjectContext executeFetchRequest:fRequest error:nil];
return arr;
}
This line here:
self.contactarray = [[managedObjectContext executeFetchRequest:fetchRequest error:nil] mutableCopy];
This is a cardinal sin in Core Data - to not use the provided error parameters.
NSError *error;
self.contactarray = [[managedObjectContext executeFetchRequest:fetchRequest
&error] mutableCopy];
if (!self.contactArray) {
// Fetch Requests return a nil on error, in which case you should check the error.
NSLog(#"Error occurred: %#", error);
} else {
// do whatever you want with the array
}
Now run your code and look at the console and you might see the reason for the error.
Edited to add
Following a comment:
You should always check that the return of the method is nil before evaluating the error object. For Cocoa (and Cocoa-Touch) methods this is the only time that the error parameter is guaranteed to be valid.
This is taken from the Error Handling Programming Guide

CoreData insertNewObjectForEntityForName and executeFetchRequest return nil

I'll try to expose my problem, because is a bit complex.
I use Core Data and I have a problem with the data stored.
When I use this code:
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:#"ItemMessage"];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
NSMutableArray *values = [[NSMutableArray alloc] init];
if (error == nil) {
for (int i = 0; i<results.count; i++) {
NSLog(#"results %#",[results objectAtIndex:i]);
ItemMessage *itemMessage = [results objectAtIndex:i];
[values addObject:itemMessage];
}
ecc. the problem is that the value printed by NSLog is correct (the "results" contains something) but the itemMessage contains always 0 key/value pairs (it seems empty).
To understand what is the problem I went back and saw that in insertNewObjectForEntityForName I have also this problem, this is the code that I used when I save the messages data in Core Data:
for (id key in objectMessage) {
ItemMessage *itemmessage = [[ItemMessage alloc] init];
itemmessage.itemMessageId = [key objectForKey:#"itemMessageId"];
itemmessage.message = [key objectForKey:#"message"];
itemmessage.sender = [key objectForKey:#"sender"];
itemmessage.users = [key objectForKey:#"users"];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSManagedObject *newMessage;
newMessage = [NSEntityDescription insertNewObjectForEntityForName:#"ItemMessage" inManagedObjectContext:context];
[newMessage setValue: itemmessage.itemMessageId forKey:#"itemMessageId"];
[newMessage setValue: itemmessage.message forKey:#"message"];
[newMessage setValue: itemmessage.sender forKey:#"sender"];
[newMessage setValue: itemmessage.users forKey:#"users"];
[context save:&error];
if (error != nil) {
NSLog(#"Coredata error");
}
The problem is that newMessage after the insertNewObjectForEntityForName and the setValue contains also 0 key/value pairs.
Can you help me?
You don't seem to insert the new managed objects correctly into the context.
It should be:
for (id key in objectMessage) {
NSManagedObjectContext *context = [appDelegate managedObjectContext];
ItemMessage *itemmessage = (ItemMessage*)[NSEntityDescription insertNewObjectForEntityForName:#"ItemMessage"
inManagedObjectContext:context];
itemmessage.itemMessageId = [key objectForKey:#"itemMessageId"];
itemmessage.message = [key objectForKey:#"message"];
itemmessage.sender = [key objectForKey:#"sender"];
itemmessage.users = [key objectForKey:#"users"];
}
//save your inserts
To create a class file for your managed objects you could:
Go to your model file (xcdatamodeld) ->
select an entity ->
from the menu select:
Editor-> Create NSManagedObjectSubclass -> select the entities your like class files for.
Now you will have managed objects you could access with ease (NSManagedObject subclass) and benefit from CoreData features.
When you insert to manage object contest you have to call save: method, also the saving method should looks something like that:
newMessage = [NSEntityDescription insertNewObjectForEntityForName:#"ItemMessage" inManagedObjectContext:context];
// 2
newMessage.property1 = self.firstNameTextfield.text;
newMessage.property2 = self.lastNameTextfield.text;
if (![context save:&error]) {
NSLog(#"Error: %#", [error localizedDescription]);
}

How does this code use only updates into core data?

This is a SyncEngine from an RW tutorial. I need help understanding how only UPDATED records from the web are fetched and processed into Core Data.
- (void)processJSONDataRecordsIntoCoreData {
NSManagedObjectContext *managedObjectContext = [[SDCoreDataController sharedInstance] backgroundManagedObjectContext];
// Iterate over all registered classes --- CHECK!
for (NSString *className in self.registeredClassesToSync) {
if (![self initialSyncComplete]) {
NSDictionary *JSONDictionary = [self JSONDictionaryForClassWithName:className];
NSArray *records = [JSONDictionary objectForKey:#"results"];
for (NSDictionary *record in records) {
[self newManagedObjectWithClassName:className forRecord:record];
}
} else {
NSArray *downloadedRecords = [self JSONDataRecordsForClass:className sortedByKey:#"objectId"];
if ([downloadedRecords lastObject]) {
NSArray *storedRecords = [self managedObjectsForClass:className sortedByKey:#"objectId" usingArrayOfIds:[downloadedRecords valueForKey:#"objectId"] inArrayOfIds:YES];
int currentIndex = 0;
//if dl count is < current index, there is an updated object dl from the web
for (NSDictionary *record in downloadedRecords) {
NSManagedObject *storedManagedObject = nil;
//Quick check to see if they indeed match, if they do, update the stored object with remote service objects
if ([storedRecords count] > currentIndex) {
storedManagedObject = [storedRecords objectAtIndex:currentIndex];
}
//Othwerwise its a new object and you need to create a new NSManagedObject to represent it in CDdb
if ([[storedManagedObject valueForKey:#"objectId"] isEqualToString:[record valueForKey:#"objectId"]]) {
[self updateManagedObject:[storedRecords objectAtIndex:currentIndex] withRecord:record];
} else {
[self newManagedObjectWithClassName:className forRecord:record];
}
currentIndex++;
}
}
}
// After all NSMO are created in your context, save it!
[managedObjectContext performBlockAndWait:^{
NSError *error = nil;
if (![managedObjectContext save:&error]) {
NSLog(#"Unable to save context for class %#", className);
}
}];
// Cleanup time
[self deleteJSONDataRecordsForClassWithName:className];
[self executeSyncCompletedOperations];
}
[self downloadDataForRegisteredObjects:NO];
}
From what I understand, on the first or initial sync, it fetches JSONDictionaryForClassWithName which reads the downloaded data from disk and creates a newManagedObjectWithClassName.
My confusion is in the update else block. downloadedRecords is populated from JSONDataRecordsForClass which simply calls JSONDictionaryForClassWithName. Then it checks to see if there is at least 1 object in that array. If there is it does this:
NSArray *storedRecords = [self managedObjectsForClass:className sortedByKey:#"objectId" usingArrayOfIds:[downloadedRecords valueForKey:#"objectId"] inArrayOfIds:YES];
This fetches all managedObjectsForClass:sortedByKey which is below:
- (NSArray *)managedObjectsForClass:(NSString *)className sortedByKey:(NSString *)key usingArrayOfIds:(NSArray *)idArray inArrayOfIds:(BOOL)inIds {
__block NSArray *results = nil;
NSManagedObjectContext *managedObjectContext = [[SDCoreDataController sharedInstance] backgroundManagedObjectContext];
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:className];
NSPredicate *predicate;
if (inIds) {
predicate = [NSPredicate predicateWithFormat:#"objectId IN %#", idArray];
} else {
predicate = [NSPredicate predicateWithFormat:#"NOT (objectId IN %#)", idArray];
}
[fetchRequest setPredicate:predicate];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:
[NSSortDescriptor sortDescriptorWithKey:#"objectId" ascending:YES]]];
[managedObjectContext performBlockAndWait:^{
NSError *error = nil;
results = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
}];
return results;
}
The next bit which compares the [storedRecords count] > currentIndex is confusing. Can someone please explain this? I think my confusion lies in what the managedObjectsForClass method does with the usingArraysOfIds & inArrayOfIds.
I would expect that at some point it gets the the updatedAt field from the downloaded records and compares it to the updatedAt field of the CoreData fetched records.
This function is processing the stored JSON. The actual remote fetching and updateAt checking happens in downloadDataForRegisteredObjects and mostRecentUpdatedAtDateForEntityWithName.
[storedRecords count] > currentIndex is a bit crazy. Although in defense of the original programmer, writing any decent syncengine will quickly make you go googoo. Basically he needs to work out which records are existing and which ones are new and update the local data store accordingly, that's all.
I had another look and this code is actually horribly broken. It will only works if either you have the same records both locally and remotely. Or if the new objects have an objectId that sort-wise comes after the last object the local store has. Which is not the case with Parse objectId's.
If you are testing with just one device this works because new objects will be inserted locally before being pushed to the server. Therefor you will always have the same amount of records. If additional records get inserted any other way, this code will do weird things.

Core Data Multiple Item Edit Efficiency and Random Error

I hope this isn't a duplicate question. I can't seem to find anything similar. Most core data questions seem to be about new object creation...
I have a program with a database of around 23,000 items. I'm attempting to create an export/import function to send data to other devices (not linked with iCloud).
The export works just fine, as does the email...
I have the import functioning, but it functions slowly (and, more on this later, doesn't seem to work well with the iPhone 5 or iPad 3)
I have a function that parses the data I'm importing into an NSArray (_importedRows), then I run the following code:
self.managedObjectContext = [(AppDelegate*)[[UIApplication sharedApplication] delegate] managedObjectContext];
NSManagedObjectContext *ctx = self.managedObjectContext;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:#"CHECKLIST"
inManagedObjectContext:ctx];
[fetchRequest setEntity:entity];
ImportedData *importedData;
NSString *verify;
NSError *error = nil;
NSManagedObject *updatedObject;
NSArray *matchingItems;
for (int i = 0; i < [_importedRows count]; i++) {
importedData = [_importedRows objectAtIndex:i];
verify = importedData.uniqueID;
[fetchRequest setPredicate:[NSPredicate predicateWithFormat:#"uniqueID == %#", verify]];
[fetchRequest setFetchLimit:1];
matchingItems = [ctx executeFetchRequest:fetchRequest error:&error];
for (updatedObject in matchingItems) {
HUD.detailsLabelText = [NSString stringWithFormat:#"Updating %#" , [updatedObject valueForKey:#"figureName"]];
[updatedObject setValue:importedData.numberOwned forKey:#"numberOwned"];
[updatedObject setValue:importedData.numberWanted forKey:#"wishList"];
[updatedObject setValue:importedData.availableTrade forKey:#"tradeList"];
}
[ctx save:&error];
if (error != nil) {
NSLog(#"error saving managed object context: %#", error);
}
}
Basically, I'm grabbing a core data entity, and then looping through my array checking for matches. When I find a match (the uniqueID predicate), I'm updating the object with the imported data. This code works fine on my iPhone 4s, but rather slowly. 4,000 items takes around 4-5 minutes. Am I doing anything blatantly wrong? Should I be calling the save function more frequently?
As a bonus, for some reason this code almost never works when I test it on an iPhone 5. 9 times out of 10 (and 50% of the time on my iPad 3) I get a
"Jan 14 08:06:44 : * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFSet addObject:]: attempt to insert nil'"
in the console. Thoughts?
Let me know if more details are needed!
UPDATE:
It seems that handleOpenURL is being called twice... once in applicationdidfinishlaunching
NSURL *url = (NSURL *)[launchOptions valueForKey:UIApplicationLaunchOptionsURLKey];
if (url != nil && [url isFileURL]) {
[self.window.rootViewController performSelector:#selector(showWithLabel:) withObject:url afterDelay:6];
}
and once here:
-(BOOL) application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{ if (url != nil && [url isFileURL]) {
[self.window.rootViewController performSelector:#selector(showWithLabel:) withObject:url];
}
return YES;
}
I have to lave both those in the app delegate, otherwise the function won't always get called (once is for when the application launches, and once if it the application was already in the background, I believe) - I've added a check to prevent it from launching a second time within the showWithLabel thread, but it doesn't seem like that is a very elegant solution...
UPDATE: #mundi advised cleaning up the fetchedresults code as follows:
NSArray *importedIDs = [_importedRows valueForKeyPath:#"uniqueID"];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.entity = [NSEntityDescription entityForName:#"CHECKLIST"
inManagedObjectContext:ctx];
fetchRequest.predicate = [NSPredicate predicateWithFormat:
#"uniqueID in %#", importedIDs];
NSError *error = nil;
NSManagedObject *updatedObject;
NSArray *matchingItems;
matchingItems = [ctx executeFetchRequest:fetchRequest error:&error];
ImportedData *importedData;
for (int i = 0; i < [_importedRows count]; i++) {
importedData = [_importedRows objectAtIndex:i];
for (updatedObject in matchingItems) {
if ([importedData.uniqueID isEqualToString:[updatedObject valueForKey:#"uniqueID"]]) {
HUD.detailsLabelText = [NSString stringWithFormat:#"Updating %#" , [updatedObject valueForKey:#"figureName"]];
[updatedObject setValue:importedData.numberOwned forKey:#"numberOwned"];
[updatedObject setValue:importedData.numberWanted forKey:#"wishList"];
[updatedObject setValue:importedData.availableTrade forKey:#"tradeList"];
}
}
}
[ctx save:&error];
I'm sure it could still be a little cleaner and the actual updating portion (I'm not sure how to do it other than compare each item in the fetchedresults with each item in the initial array to make sure they are updated correctly, but the combined fetchedresults increased the speed tremendously (originally 240 seconds for 4000 items, now between 80-120 seconds)
sorting arrays first, then updating in order speeds it up tremendously yet again:
NSArray *matchingItemsSorted;
matchingItemsSorted = [matchingItems sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
NSString *first = [a valueForKey:#"uniqueID"];
NSString *second = [b valueForKey:#"uniqueID"];
return [first caseInsensitiveCompare:second];
}];
NSArray *importedRowsSorted;
importedRowsSorted = [_importedRows sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
NSString *first = [a valueForKeyPath:#"uniqueID"];
NSString *second = [b valueForKeyPath:#"uniqueID"];
return [first caseInsensitiveCompare:second];
}];
int i = 0;
for (updatedObject in matchingItemsSorted) {
NSLog(#"do we match? %# : %#", [[importedRowsSorted objectAtIndex:i] valueForKeyPath:#"uniqueID"], [updatedObject valueForKey:#"uniqueID"]);
HUD.detailsLabelText = [NSString stringWithFormat:#"Updating %#" , [updatedObject valueForKey:#"figureName"]];
[updatedObject setValue:[[importedRowsSorted objectAtIndex:i] valueForKeyPath:#"numberOwned"] forKey:#"numberOwned"];
[updatedObject setValue:[[importedRowsSorted objectAtIndex:i] valueForKeyPath:#"numberWanted"] forKey:#"wishList"];
[updatedObject setValue:[[importedRowsSorted objectAtIndex:i] valueForKeyPath:#"availableTrade"] forKey:#"tradeList"];
i++;
}
13 seconds or so for 4000 items with the nslog there... the only weird thing now is that when I comment out the nslog, it frequently crashes... is it happening so fast it's breaking core data - when it doesn't crash, it only takes about 4 seconds?
Thanks,
Zack
You have two nested loops. Use this pattern to speed this up:
NSArray *importedIDs = [_importedRows valueForKeyPath:#"uniqueID"];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.entity = [NSEntityDescription entityForName:#"CHECKLIST"
inManagedObjectContext:ctx];
fetchRequest.predicate = [NSPredicate predicateWithFormat:
#"uniqueID in %#", importedIDs];
Like this you can fetch one array with all matching items.

Resources