Pro Core Data book, example code question - ios

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.

Related

Update NSManagedObjectContext

Please consider the method at the end of this question. It attempts to delete all records from a CoreData entity.
The first -outcommented- part works fine: it deletes everything from my context, and then saves the context.
The second, non-commented part doesn't seem to work:
As a test I do
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:#"myEntity"];
NSError *error = nil;
NSArray *results = [self.managedObjectContext executeFetchRequest:request error:&error];
NSLog(#"Fetch request returns %lu objects", (unsigned long)[results count]);
When I add records, and delete all records with the method below, the amount of records only increase.
As far as I understand, it actually works, but the context is not aware of it (yet).
So, therefore, in order to get a reliable context, I should update the context with
[NSManagedObjectContext mergeChangesFromRemoteContextSave:<#(nonnull NSDictionary *)#> intoContexts:self.managedObjectContext];
However, I don't have a clue what I should enter in the (nonnull NSDictionary *) part.
BTW: I only want to use the NSBatchDeleteRequest because I assume it is faster than iterating through all records.
- (void)deleteAllEntities:(NSString *)nameEntity
{
id appDelegate = (id)[[UIApplication sharedApplication] delegate];
self.managedObjectContext= [appDelegate managedObjectContext];
/*
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:nameEntity];
[fetchRequest setIncludesPropertyValues:NO]; //only fetch the managedObjectID
NSError *error;
NSArray *fetchedObjects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
for (NSManagedObject *object in fetchedObjects)
{
[self.managedObjectContext deleteObject:object];
}
error = nil;
[self.managedObjectContext save:&error];
*/
NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:nameEntity];
NSBatchDeleteRequest *delete = [[NSBatchDeleteRequest alloc] initWithFetchRequest:request];
NSError *deleteError = nil;
}

CoreData saving object is not successful

I am trying to update int data by using CoreData but I think I am missing some parts before updating the object. Here what I am trying to do to save the int value. Here I want to both save and update only first object value.
- (IBAction)btnYakClicked:(UIButton *)sender
{
NSManagedObjectContext *context = [self managedObjectContext];
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:[NSEntityDescription entityForName:#"Kalorimetre" inManagedObjectContext:context]];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
NSManagedObject* kaloriObject = [results objectAtIndex:0];
NSNumber *valWeek = [NSNumber numberWithInteger:300];
NSNumber *valMonth = [NSNumber numberWithInteger:1000];
[kaloriObject setValue:valWeek forKey:#"thisweek"];
[kaloriObject setValue:valWeek forKey:#"thismonth"];
}
I don't see the line where you're saving it. For example:
NSError *saveError;
if (![context save:&saveError]) {
NSLog(#"Error: %# %#", saveError, [saveError localizedDescription]);
}
It might also be a good idea to limit the fetch request since you're only looking for the first object:
[request setFetchLimit:1];
I would hope that this would improve performance but cannot confirm.

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

Core Data fetch request optimization

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).

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.

Resources