Core Data fetch request optimization - ios

I'm developing and application which needs to check if a string has been saved to the database or not. This may seem an easy operation but it needs half a second to return any response which I think is quite a lot. My question is if there is any way to reduce that time. Thanks for your interest.
This is my current code:
- (BOOL) isDeleted:(int)i {
NSString *value = [NSString stringWithFormat:#"deleted.*.%i", i];
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSString *entityName = #"Deleted";
NSEntityDescription *entityDesc = [NSEntityDescription entityForName:entityName inManagedObjectContext:context];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entityDesc];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"(deletedpics like %#)", value];
[request setPredicate:pred];
NSError *error;
NSArray *objects = [context executeFetchRequest:request error:&error];
BOOL returnBool = [objects count] >= 1 ? YES : NO;
return returnBool;
}

One optimization is to replace
NSArray *objects = [context executeFetchRequest:request error:&error];
by
[request setFetchLimit:1];
NSUInteger count = [context countForFetchRequest:request error:&error];
if you only want to check for the existence of objects.
But I assume that the main performance problem is the wildcard search with LIKE, which is slower than searching with ==.
Instead of storing the status and the picture number in one
attribute as deleted.<status>.<picturenumber> (as you wrote in a comment) it would probably be better to use separate attributes status and picturenumber in the entity.
Then you can search for a picture number with the predicate
[NSPredicate predicateWithFormat:#"picturenumber == %d", i];
which should be much faster. If necessary, you can additionally index that property (as #flexaddicted mentioned in a comment).

Related

Get all results of a NSFetchRequest in an NSArray

In my app I do this thing to abtain all specific value from an entity
NSManagedObjectContext *context = [[self sharedAppDelegate] managedObjectContext];
NSError *error = nil;
NSFetchRequest *req = [NSFetchRequest fetchRequestWithEntityName:#"Structure"];
[req setPropertiesToFetch:#[#"id_str"]];
[req setResultType:NSDictionaryResultType];
NSArray *id_str_BD = [context executeFetchRequest:req error:&error];
int this way I obtain a NSArray of NSDictionaryies but I want directly an array of values.
What's a fast way to obtain it without loops?
I agree with Tom. What values?
Anyway, you can accomplish it through KVC. For example,
NSFetchRequest* fetchRequest = [NSFetchRequest fetchRequestWithEntityName:#"Structure"];
[fetchRequest setPropertiesToFetch:#[#"id_str"]];
NSArray* results = [[self managedObjectContext] executeFetchRequest:fetchRequest error:nil];
NSArray* ids = [results valueForKey:#"id_str"];
NSLog(#"%#", ids);
NOTES
Do not pass nil for the error. ALWAYS check for a possible error (for the sake of simplicity I don't use it in this code snippet)
I would rename id_str to idStructure

ios core data - copy entity records to another entity

I have 2 entity, Units and BUnits, the Units entity have a data that will be replaced many time, and the BUnits will be the backup of the Units entity before clearing it's data.
So, I've created a NSManagedObjectContext instance, then I've retrive an instance for each entity by using
NSManagedObjectContext *context = [mainDelegate managedObjectContext];
NSEntityDescription *unitsEntity = [NSEntityDescription entityForName:#"Units" inManagedObjectContext:context];
NSEntityDescription *bUnitsEntity = [NSEntityDescription entityForName:#"BUnits" inManagedObjectContext:context];
but i've didn't managed to copy the Units entity records to the BUnits entity, other than make a loop for each records, but i believe that there is a better solution.
What do you think about this, is there a better solution?
UPDATE:
The solution i've used in case anyone could use it is in my answer, I think there is a better way to do this, i will keep checking for it and i will update the question if i found anything.
Here is what i've used, using looping for each record:
- (void) copyEntities:(NSString *)sourceEntity And:(NSString *)destinationEntity {
NSManagedObjectContext *context = [mainDelegate managedObjectContext];
NSEntityDescription *Units = [NSEntityDescription entityForName:sourceEntity inManagedObjectContext:context];
NSEntityDescription *BUnits = [NSEntityDescription entityForName:destinationEntity inManagedObjectContext:context];
NSFetchRequest *dataRequest = [[NSFetchRequest alloc] init];
[dataRequest setEntity:Units];
NSError *error = nil;
NSArray *dataArray = [context executeFetchRequest:dataRequest error:&error];
for (UnitsClass *unit in dataArray) {
UnitsClass *savedUnits;
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:BUnits];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"(code = %#)", unit.code];
NSPredicate *pred1 = [NSPredicate predicateWithFormat:#"(code2 = %#)", unit.code2];
NSPredicate *compoundPredicate = [NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObjects:pred, pred1, nil]];
[request setPredicate:compoundPredicate];
NSError *error = nil;
NSArray *objects = [context executeFetchRequest:request error:&error];
//this if to indicate if we inserting to the BUnits or updating it.
if ([objects count] > 0) {
//Found the record, update The info
savedUnits = [objects objectAtIndex:0];
} else {
savedUnits = [NSEntityDescription insertNewObjectForEntityForName:destinationEntity inManagedObjectContext:context];
}
savedUnits.code = unit.code;
/*Add your updated info*/
NSError *errors;
if (![context save:&errors]) {
NSLog(#"Whoops, couldn't save: %#", [errors localizedDescription]);
}
}
NSLog(#"BUnits count = %d",[context countForFetchRequest:[NSFetchRequest fetchRequestWithEntityName:destinationEntity] error:&error]);
}
Based on the given information, there is no better way than looping each unit object and creating the backup object.

Core Data - how to check if user exists in database?

So I'm trying to create a simple (if such one exists) login process for my app, and I am getting an error with the below block of code.
NSManagedObject *context = _managedObjectContext;
NSFetchRequest *request= [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Account" inManagedObjectContext:context];
NSPredicate *predicate =[NSPredicate predicateWithFormat:#"username==%#",self.textFieldUsername.text];
[request setEntity:entity];
[request setPredicate:predicate];
NSError *error = nil;
// Below line is giving me error
NSArray *array = [managedObjectContext executeFetchRequest:request error:&error];
if (array != nil) {
NSUInteger count = [array count]; // may be 0 if the object has been deleted.
NSLog(#"Username may exist, %#",count);
}
else {
NSLog(#"Username does not exist.");
}
`
The above code right now is ran when the user clicks the Login button.
Xcode is giving me an error of:
No visible #interface for 'NSManagedObject' declares the selector 'executeFetchRequest:error.'
When I read the above statement it just seems greek to me. The name of the file I am working on is ViewControllerWelcome.m and can be viewed in it's entirety at the following link. If it matters, I am using the stock boilerplate core data code.
Bonus: How do I get objective-c code highlighting when I post on here (SO)?
Change:
NSManagedObject *context = _managedObjectContext;
To
NSManagedObjectContext *context = _managedObjectContext;
and
NSArray *array = [managedObjectContext executeFetchRequest:request error:&error];
should be
NSArray *array = [context executeFetchRequest:request error:&error];
P.S.
If you want the username search to be case insensitive use "username ==[c] %#" for the predicate...

Core Data - update entity in background thread automatically changes NSManagedObject in Main Thread without merging- why?

i´m currently learning Core Data. Core Data is great but i can not explain the behaviour with a second managed object context in a background thread.
I have an entity called TestEntity with 2 attributes (testId and testDescription)
On the main thread i fetch the entity with the testId = 1 and store this managed object into an instance variable.
NSEntityDescription *entityDescription = [NSEntityDescription
entityForName:#"TestEntity" inManagedObjectContext:self.managedObjectContext];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entityDescription];
NSNumber *testId = [NSNumber numberWithInt:1];
NSPredicate *predicate = [NSPredicate predicateWithFormat:
#"testId == %#", testId];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *array = [self.managedObjectContext executeFetchRequest:request error:&error];
t1 = [[array objectAtIndex:0] retain];
TestThread *tt = [TestThread new];
NSOperationQueue *queue = [NSOperationQueue new];
[queue addOperation:tt];
[queue waitUntilAllOperationsAreFinished];
NSLog(#"%#", [t1 valueForKey:#"testDescription"]);
Then is start a NSOperation with a NSOperationQueue called TestThread.
In the main method of this TestThread i create a second managed object context, fetch the same entity (testId = 1) like the main thread, change the testDescription property and save the new context without any errors.
tgAppDelegate *delegate = [[NSApplication sharedApplication] delegate];
self.context = [[[NSManagedObjectContext alloc] init] autorelease];
[context setPersistentStoreCoordinator:delegate.persistentStoreCoordinator];
NSEntityDescription *entityDescription = [NSEntityDescription
entityForName:#"TestEntity" inManagedObjectContext:self.context];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entityDescription];
// Set example predicate and sort orderings...
NSNumber *testId = [NSNumber numberWithInt:1];
NSPredicate *predicate = [NSPredicate predicateWithFormat:
#"testId == %#", testId];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *array = [context executeFetchRequest:request error:&error];
TestEntity *t1 = [array objectAtIndex:0];
t1.testDescription = #"abc";
[context save:&error];
if (error) {
// do something
}
The behaviour i can not explain my self is, that the NSLog output after
[queue waitUntilAllOperationsAreFinished];
has the same string value which updated in my background thread.
abc
My understanding of Core Data and Multithreading is that i have to do an explicit merge between contextes.
In my test app there is no merge.
Can anyone explain why this happenes?
By default, a fetch request will return objects as faults so their content is not actually loaded until you access it (you can learn more about faulting under this link).
To see your expected behavior, try logging the original value of t1.testDescription before you start the second thread.
You can also set
self.context.returnObjectsAsFaults = NO;
but this might have a negative impact on the memory footprint of your app.

Pro Core Data book, example code question

I'm learning Core Data with the help of the book. There is a code:
- (void)loadData {
// Pull the movies. If we have 200, assume our db is set up.
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"Movie"
inManagedObjectContext:context]];
NSArray *results = [context executeFetchRequest:request error:nil];
if ([results count] != 200) {
// Add 200 actors, movies, and studios
for (int i = 1; i <= 200; i++) {
[self insertObjectForName:#"Actor" withName:
[NSString stringWithFormat: #"Actor %d", i]];
[self insertObjectForName:#"Movie" withName:
[NSString stringWithFormat: #"Movie %d", i]];
[self insertObjectForName:#"Studio" withName:
[NSString stringWithFormat: #"Studio %d", i]];
}
// Relate all the actors and all the studios to all the movies
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"Movie"
inManagedObjectContext:context]];
NSArray *results = [context executeFetchRequest:request error:nil];
for (NSManagedObject *movie in results) {
[request setEntity:[NSEntityDescription entityForName:#"Actor"
inManagedObjectContext:context]];
NSArray *actors = [context executeFetchRequest:request error:nil];
NSMutableSet *set = [movie mutableSetValueForKey:#"actors"];
[set addObjectsFromArray:actors];
[request setEntity:[NSEntityDescription entityForName:#"Studio"
inManagedObjectContext:context]];
NSArray *studios = [context executeFetchRequest:request error:nil];
set = [movie mutableSetValueForKey:#"studios"];
[set addObjectsFromArray:studios];
}
}
[request release];
NSError *error = nil;
if (![context save:&error]) {
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
}
The main question is: is it necessary to renew the context pointer, if there were any changes in that context?
What I mean: I get the pointer to the context at the beginning of the method, next in the loop I take this context and insert managed objects there (-insertObjectForName:withName:). Then I see this renewal of the context pointer and have that question: is it a rule of some kind, and I should act the same, or its just not-so-neat code example? Why can't I use the old pointer?
---Edit--- One more question: is that a legal initialization of a request here in the code:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
...
NSFetchRequest *request = [[NSFetchRequest alloc] init];
...
[request release];
Two allocs and only one release?
I also have this book and looked it up.
Clearly seems like a typo to me and doesn't really make much sense.
Just ignore that line and continue - it should work fine without.
In my opinion
its just not-so-neat code example
About your second question: There have to also two releases! Otherwise you have a leak.
That's some seriously ugly code.
This:
NSManagedObjectContext *context = [self managedObjectContext];
... is probably just because they want to be able to write context in a method call instead of self.managedObjectContext.
Using:
NSFetchRequest *request = [[NSFetchRequest alloc] init];
... twice is allowed but it is lazy bad practice. Any variable should be named only once in a scope. In fact, the compiler will generate a warning with this code. It will leak because every init must be balanced by a release.

Resources