Core Data performance issues - iphone

I have an iPhone app that has the needs to avoid inserting duplicate records, at first I thought I could just test each object in my in-memory array, create a fetch request for each object and test if it exists. But this is proving slow on the device, very slow (about 5 seconds, which is not acceptable).
I have been trying to piece together how to create some smart predicate that I could use in order to get this to work efficiently but without much success.
My objects have a NSNumber field that I have set as the "Identity Property" and also non-optional. This field is called sampleTime (again, this is NOT a date, but a NSNumber)
Here is my idea (borrowed from other threads and even some of my own questions of SO):
Obviously doing a fetch per object (around 380 objects) is not going to work for performance, so I was under the impression that I could do most of it in memory and it would be faster. I need to create some predicate that uses the IN clause, then iterate over that fetch result, testing if any one of the objects is inside that result set, if NOT then insert it, if SO then do nothing.
But my implementation is not working:
NSMutableArray *timeStampArray = [[NSMutableArray alloc] init];
for (id emTmp in myListOfObjects)
[timeStampArray addObject: [NSNumber numberWithInt:[[emTmp sampleTime] timeIntervalSince1970]]];
NSFetchRequest *fetch = [[[NSFetchRequest alloc] init] autorelease];
[fetch setEntity:[NSEntityDescription entityForName:#"ElectricalMeasurementEntity" inManagedObjectContext:context]];
[fetch setPredicate:[NSPredicate predicateWithFormat:#"sampleTime in %#", timeStampArray]];
NSArray *results = [context executeFetchRequest:fetch error:nil];
int resCount = [results count];
But resCount is always zero, and I dont know why...
BTW, myListOfObjects contains business objects which also have a sampleTime property which IS an NSDate type.
EDIT
Ok, update, I got the basics working. The reason why I was not getting any results was because the loop that created the array was using the id type, which when used the way I was using it was not creating NSNumber objects correctly.
Now I do this:
for (id emTmp in myListOfObjects)
{
Measurement *t = (Measurement*)emTmp;
NSTimeInterval d = [t.sampleTime timeIntervalSince1970];
[timeStampArray addObject: [NSNumber numberWithInt:[[t sampleTime] timeIntervalSince1970]]];
}
which creates a nice list which works very well.
However, I then go on to do this:
NSMutableArray *itemsToInsert = [[NSMutableArray alloc] init];
for (id em in myListOfObjects)
{
BOOL found = NO;
Measurement *t = (Measurement*)em;
for (id e in results) //results from Fetch Request which is now populated properly
{
MeasurementEntity *entity = (MeasurementEntity*)e;
if ([entity.sampleTime isEqualToNumber:[NSNumber numberWithInt:[[t sampleTime] timeIntervalSince1970]]])
{
found = YES;
break;
}
}
if (!found)
[itemsToInsert addObject: t];
}
This loop (for around 850 objects, on the iPhone 3Gs) takes around 10 - 12 seconds, which I can see why (when 850*850 = 722500 loops!). Can I be more efficient about this?
Thanks

You need to strip the fetch down such that it will only check the one property. Then do all your comparisons with predicates for speed. Something like this:
NSArray *newData=[NSArray arrayWithObjects:[NSNumber numberWithInt:1],[NSNumber numberWithInt:6],nil];
NSManagedObject *mo;
for (int i=0; i<5; i++) {
mo=[NSEntityDescription insertNewObjectForEntityForName:#"Test" inManagedObjectContext:self.moc];
[mo setValue:[NSNumber numberWithInt:i] forKey:#"numAttrib" ];
}
[self saveContext];
NSFetchRequest *fetch=[[NSFetchRequest alloc] init];
NSEntityDescription *testEntity=[NSEntityDescription entityForName:#"Test" inManagedObjectContext:self.moc];
[fetch setEntity:testEntity];
// fetch only the one property you need to test
NSDictionary *propDict=[testEntity propertiesByName];
[fetch setPropertiesToFetch:[NSArray arrayWithObject:[propDict valueForKey:#"numAttrib"]]];
// Return as dictionaries so you don't have the overhead of live objects
[fetch setResultType:NSDictionaryResultType];
// fetch only those existing property values that match the new data
NSPredicate *p=[NSPredicate predicateWithFormat:#"numAttrib in %#",newData];
[fetch setPredicate:p];
NSArray *fetchReturn=[self performFetch:fetch];//<-- my custom boilerplate
// extract the existing values from the dictionaries into an array
NSArray *values=[fetchReturn valueForKey:#"numAttrib"];
// filter out all new data values that already exist in Core Data
p=[NSPredicate predicateWithFormat:#"NOT (SELF in %#)",values];
NSArray *unmatchedValues=[newData filteredArrayUsingPredicate:p];
NSLog(#"unmatcheValues=%#",unmatchedValues);
... which outputs:
unmatcheValues=(
6
)
Now you only need to create new managed objects for the values returned. All other new values already exist.

This may seem an obvious suggestion, but if [context executeFetchRequest:fetch error:nil] is not returning any results, seems like the first thing to do is check for errors instead of ignoring them (by setting error:nil).
Something like:
NSError *error = nil;
NSArray *results = [context executeFetchRequest:fetch error:&error];
if (results == nil) { // fetch failed - huh?
NSLog(#"Fetch error %#, %#", error, [error userInfo]);
}

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.

Need to update nsmanagedobject from nsarray for loop - iphone

I have a coredata project that I'm trying to programmatically update a number.
I'm retrieving objects from CoreData and then storing it into an array.
Then, I'm looping through that array to see if the current user's IP is present in the database and trying to update the number of times accessed for that specific array.
The problem is, it's updating all the objects, not just the current object in the looped array.
First, I get the info from core data like so:
- (void)fetchRecords {
// Define our table/entity to use
NSEntityDescription *entity = [NSEntityDescription entityForName:#"IPAddr" inManagedObjectContext:managedObjectContext];
// Setup the fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
// Define how we will sort the records
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"ipDate" ascending:NO];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[request setSortDescriptors:sortDescriptors];
// Fetch the records and handle an error
NSError *error;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (!mutableFetchResults) {
// Handle the error.
// This is a serious error and should advise the user to restart the application
}
// Save our fetched data to an array
[self setIpArray: mutableFetchResults];
}
Now, I'm trying to find if the current User IP is present in the fetched results, and if it's present, update the number of times accessed:
// see if the ip is present and update if necessary
-(void)ipPresent {
NSString * theCurrentIP = [self getGlobalIPAddress];
for (IPAddr *allips in ipArray)
{
if ([allips.ipNum isEqualToString:theCurrentIP]) {
NSLog(#"The IP %# was found.", theCurrentIP);
// update the ip
NSError *error = nil;
NSNumber *ipToUpdate = allips.ipAccess;
NSNumber *addIpAccess = [[NSNumber alloc] initWithInt:1];
NSNumber *updateIpAddress = [NSNumber numberWithFloat:([ipToUpdate floatValue] + [addIpAccess floatValue])];
[self.ipArray setValue:updateIpAddress forKey:#"ipAccess"];
if ([self.managedObjectContext save:&error]) { // write to database
NSLog(#"The IP Was Updated from %# to %#", ipToUpdate, updateIpAddress);
} else if (![self.managedObjectContext save:&error]) {
NSLog(#"failed with error: %#", error);
}
break;
} else {
NSLog(#"The IP %# was NOT found.", theCurrentIP);
}
}
}
I'm pretty sure the issue is with this line:
[self.ipArray setValue:updateIpAddress forKey:#"ipAccess"];
Again, it's updating ALL the entities and not just the one in the current loop.
Indeed. You are using the wrong method. self.ipArray is a NSMutableArray.
The method
- (void)setValue:(id)value forKey:(NSString *)key
is used for Key-Value Coding (which is what makes it work for Core Data objects), but when applied to an array, it will invoke setValue:forKey: on each entry in the array.
Now, you can see that you could also call setValue:forKey on the one single array element allips since its property is obviously KVC compliant -- otherwise you would be having a different problem, not see the values being set.
Note, that you could also just assign the property...
allips.ipAccess = updateIpAddress;
EDIT
Sorry, probably should have read slower... You do understand that you don't have to use a mutable array, right? You are not actually changing the array, just the elements in the array. An immutable collection means that the collection contents can not change, but when you have a pointer to an object, as long as that object is not immutable, you can still mutate its properties.
Thus, if you had an immutable array of Foo objects, you could do this...
for (Foo *foo in myImmutableArray) {
Bar *bar = [self getSomeNewBar];
[foo setBar:bar];
// If Foo is KVC compliant, you can do this too...
[foo setValue:bar for Key:#"bar"];
}
If, however, you call setValue:forKey on the array, it will be invoked for each element of the array. Note, that setValue:forKey is actually declared in the immutable NSArray.
EDIT
That comment was hard to read.
The core data object is just another object. It looks like you have subclassed it, and provided it with properties for the attributes. Just replace
[self.ipArray setValue:updateIpAddress forKey:#"ipAccess"];
with
[allips setValue:updateIpAddress forKey:#"ipAccess"];
or
allips.ipAccess = updateIpAddress;
Either of those should modify your core data object, as they would any object that had a read/write property named "ipAccess"
Assuming, of course, that I didn't read it wrong again... and allips is your core data object...

Difference in data when using core data relationships

I'm using core data to fetch values from an sql lite database for my iphone app. I'm pretty new to the syntax so I might be missing a few key infos.
My fetch request looks like this:
NSEntityDescription *difficultyDescription = [NSEntityDescription entityForName:#"Difficulty" inManagedObjectContext:managedObjectContext];
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init] autorelease];
[fetchRequest setEntity:difficultyDescription];
NSArray *diffResults = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
After that, I'm looping through the results by using this snippet:
systems = [[NSMutableArray alloc] init];
values = [[NSMutableArray alloc] init];
for (NSManagedObject *diff in diffResults) {
[systems addObject:diff];
[values addObject:[diff valueForKey:#"difficultyValues"]];
}
What I don't get about this is, that when I log systems by using
NSLog(#"%#", [[systems objectAtIndex:0] valueForKey:#"name"]);
I get the result in plain text. For example "some value".
When logging the results in the "values" array however I get this result:
{(
"some value"
)}
I'm using the same logging call:
NSLog(#"%#", [[values objectAtIndex:0] valueForKey:#"value"]);
The entities difficulty and difficultyValues have a one to many relationship.
How can I display the value in the array "values" like the one in "systems"? I need it later on for a label.
This seems to be a fundamental misunderstanding. difficultyValues is a relationship, while name is an NSString attribute. That's why it appears differently in NSLog(), which works by sending objects a -description message.
The value for difficultyValues will be an NSSet which contains multiple managed objects for the entity difficulty.
Here's what you should do:
NSLog(#"%#", [[systems objectAtIndex:0] valueForKey:#"name"]); // NSString*
// let's loop through all the difficulties of this item
for (NSManagedObject* aDifficulty in [[[systems objectAtIndex:0] valueForKey:#"difficultyValues"] allObjects]) {
NSLog(#"%#", [aDifficulty valueForKey:#"name"]);
}
This, of course, assuming your difficulty entity has a name attribute.

Calculating sum for decimal values via Core Data not working properly?

first time I post to this round, so please bear with me if I don't follow all the rules properly.
I am writing an app for the iPhone (OS 3.1) and am trying to write some code which lets me add decimals. I have a Core Data entity called SimpleItem with a amount attribute. Here is the test case I wrote:
// Create and configure objects
SimpleItem *si1 = [self createSimpleItem:#"si1"];
si1.amount = [NSDecimalNumber decimalNumberWithMantissa:1000 exponent:0 isNegative:NO];
SimpleItem *si2 = [self createSimpleItem:#"s12"];
si2.amount = [NSDecimalNumber decimalNumberWithMantissa:2000 exponent:0 isNegative:NO];
// Need to save prior to fetching results with NSDictionaryResultType (known limitation)
[self save];
// Describe fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entityDescription = [NSEntityDescription entityForName:#"SimpleItem" inManagedObjectContext:self.context];
[request setEntity:entityDescription];
[request setResultType:NSDictionaryResultType];
NSExpression *keyPathExpression = [NSExpression expressionForKeyPath:#"amount"];
// For whatever reason, evaluating this expression here is absolutely not working. Probably decimals aren't handled properly.
NSExpression *sumAmountExpression = [NSExpression
expressionForFunction:#"max:"
arguments:[NSArray arrayWithObject:keyPathExpression]];
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
[expressionDescription setName:#"amount"];
[expressionDescription setExpression:sumAmountExpression];
[expressionDescription setExpressionResultType:NSDecimalAttributeType];
[request setPropertiesToFetch:[NSArray arrayWithObject:expressionDescription]];
// Fetch the amounts
NSError *error = nil;
NSArray *array = [self.context executeFetchRequest:request error:&error];
If I execute this code through otest and debug it, I get an exception when the fetch request is executed: "-[NSDecimalNumber count]: unrecognized selector sent to instance."
Just evaluating the keyPathExpression without the aggregate function works fine, though.
The reference documentation shows exactly the same example so I'm wondering what I am doing wrong. Or could this be just a bug?
All the best,
Harald
Copying your given source to a new iPhone project and running that in the simulator worked fine here. I was compiling against the 3.1.2 SDK on Snow Leopard.
This is what I used for the data model:
model description http://homepage.mac.com/aclark78/.Public/Pictures/test%20model.png
There must be something else going on causing your issue. Can you describe your model or simplify it further?
I think I had this problem before. IIRC, the problem is that you've set your fetch result type to NSDictionaryResultType but you're receiving it in an NSArray. Try switching the fetch result type to NSManagedObjectResultType which does return an array. All the examples in the docs use the default NSManagedObjectResultType.
In any case, the error message clearly results from an NSDecimal object being sent an array's count message. You could confirm this by trapping the result in an id. eg:
id *genericObj = [self.context executeFetchRequest:request error:&error];
NSLog("returned object=%#",genericObj);
NSLog("returned object class=%#",[genericObj class]);
Thanks for letting me know. The thing is that I actually don't want to use managed objects. After all, I am interested in the amounts only and this is what I get with the dictionary.
Also, trying to investigate the issue as you described didn't work as executing the fetch request immediately fails and I don't get anything back.
Any other ideas? Is there any other possibility to get aggregate values? Any "best practices"? At the moment I do the following (continuation of the code above):
NSExpressionDescription *expressionDescription = [[NSExpressionDescription alloc] init];
[expressionDescription setName:#"amount"];
[expressionDescription setExpression:keyPathExpression];
[expressionDescription setExpressionResultType:NSDecimalAttributeType];
[request setPropertiesToFetch:[NSArray arrayWithObject:expressionDescription]];
// Fetch the amounts
NSError *error = nil;
NSArray *array = [self.context executeFetchRequest:request error:&error];
if (array == nil) {
STFail(#"Fetch request could not be completed: %#", [error localizedDescription]);
}
STAssertEquals([array count], (NSUInteger) 2, #"Number of fetched objects != 2");
// Manually calculate the sum
NSDecimalNumber *sum = nil;
for (NSDictionary * dictionary in array) {
NSDecimalNumber *amount = (NSDecimalNumber *) [dictionary valueForKey:#"amount"];
if (!sum) {
sum = amount;
} else {
sum = [sum decimalNumberByAdding:amount];
}
}
STAssertNotNil(sum, #"Sum is nil");
STAssertTrue([sum isEqualToNumber:[NSDecimalNumber decimalNumberWithMantissa:3000 exponent:0 isNegative:NO]],
#"Sum != 3000: %#", sum);
Regards,
Harald

problem with coredata

I have a problem that whenever I'm inserting data using coredata, everything's going fine. But while retrieving, I'm getting the same object all the time retrieved. I'm inserting objects of actors with multiple attribues like id,name,address etc. in add method, I can see everything getting inserted(which actually I'm retrieving from an xml file). my set methods are like:=
[poi setActorCity:[NSString stringWithFormat:#"%#",[poi1 objectAtIndex:j]]];
where, poi is an object of my managedObjectClass POI1 . Are those a problem? & j index is simply for keeping track of xml values from poi1 array. Please help...
(void)addEvent
{
[actorsArray removeAllObjects];
NSEntityDescription *entity1 = [NSEntityDescription entityForName:#"POI1" inManagedObjectContext:self.managedObjectContext];
POI1 *poi = (POI1 *)[NSEntityDescription insertNewObjectForEntityForName:#"POI1" inManagedObjectContext:managedObjectContext];
for(NSInteger i=0;i<[Actors count];i++)
{
NSMutableArray *poi1=[[NSMutableArray alloc]init];
poi1=[Actors objectAtIndex:i];
for(int j=0;j<[poi1 count];j++)
{
if(j==1)
{
[poi setActorName:[NSString stringWithFormat:#"%#",[poi1 objectAtIndex:j]]];
} //Like this it inserts for every attribute
}
[actorsArray insertObject:poi atIndex:i];
[poi release];
}
[self saveAction]; //saving the managedObjectContext
}
This' my fetch method...
-(void)fetchResult
{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity1 = [NSEntityDescription entityForName:#"POI1" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity1];
NSArray *items = [self.managedObjectContext
executeFetchRequest:fetchRequest error:&error];
for(NSInteger k=0;k<[items count];k++)
{
POI1 *_poi=[[POI1 alloc]init];
_poi = [items objectAtIndex:k];
NSString *str=[NSString stringWithFormat:#"%#",[_poi actorName]]; //This' for testing... Shows me same name every time..,
}
[fetchRequest release];
}
It sounds like you have a problem with your fetch. Check your predicate. If it returns the same object the most likely cause is that your predicate is written such that it only finds that one object.
Edit01:
This line is your problem:
POI1 *poi = (POI1 *)[NSEntityDescription insertNewObjectForEntityForName:#"POI1" inManagedObjectContext:managedObjectContext];
Despite the fact that the class called is 'NSEntityDescription' this method returns a managed object instance. Right now you create a single POI1instance and then just keep assigning it different attributes. You're seeing the same values because you've only created, populated and saved one object.
Move the object creation inside the loop:
for(NSInteger i=0;i<[Actors count];i++)
{
POI1 *poi = (POI1 *)[NSEntityDescription insertNewObjectForEntityForName:#"POI1" inManagedObjectContext:managedObjectContext];
NSMutableArray *poi1=[[NSMutableArray alloc]init];
poi1=[Actors objectAtIndex:i];
for(int j=0;j<[poi1 count];j++)
{
if(j==1)
{
[poi setActorName:[NSString stringWithFormat:#"%#",[poi1 objectAtIndex:j]]];
} //Like this it inserts for every attribute
}
[actorsArray insertObject:poi atIndex:i];
[poi release];
}
This will create a new POI1 at each pass so that the each element of the Actors array will have a corresponding POI1 instances containing its data.
A little hard to answer precisely with this information, but my educated guess would be that you do not create new poi instance every time and keep adding the same reference to your array.
Make sure that you're saving your managedObjectContext after doing all of those inserts(probably after each insert), otherwise the information will never leave temporary memory.