Method returning object with executeFetchRequest - objective-c

I searched for the past couple hours reading dozens of posts on the topic of memory management in objective-C and I just don't get it. Sorry. I am doing my best!
What I am looking for at the moment is the answer to how to return an object from a method which calls 'executeFetchRequest'.
Here is some code...
+ (Player *)loadPlayerWithPredicate:(NSString *)name:(NSInteger)index
{
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Player" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
// Set filter predicate
NSString *strFilter = [NSString stringWithFormat:#"%# = %d", name, index];
[request setPredicate:[NSPredicate predicateWithFormat:strFilter]];
// Create the sort descriptors array
NSSortDescriptor *sorter = [NSSortDescriptor sortDescriptorWithKey:name ascending:YES];
[request setSortDescriptors:[NSArray arrayWithObject:sorter]];
NSError *error = nil;
Player *player = nil;
NSArray *array = [managedObjectContext executeFetchRequest:request error:&error];
if ([array count] == 1)
{
player = [array objectAtIndex:0];
//[player retain]; // ???
}
[request release];
return player;
}
But what I really need is for the "player" object to stick around after the method returns.
How can I make 'player' live after being returned?
Should I call 'retain'? (and then I would call 'release' in the receiver)
Should I 'copy' the object?
I read all of the other posts about alloc, copy, new, etc.
I just need a simple example so that I can then derive understanding from it. If you can also show me an example of the receiver calling this method I would appreciate it.
Thanks!

You could use copy, but it would require that the Player class conforms to the NSCopying protocol, which I doubt it does.
The simplest (and probably best) way to do this here is as such:
if ([array count] == 1)
{
player = [[array objectAtIndex:0] retain];
}
[request release];
return [player autorelease];
You are keeping the player from being released for the meantime by retaining it, and then when you return it you use autorelease. This is not strictly necessary, but I think it is good programming practice in this case. This is because your
+ (Player *)loadPlayerWithPredicate:(NSString *)name:(NSInteger)index;
function name would imply (in Obj-C standard practice) that the object returned is autoreleased, thus leaving the memory management of the object up to the caller.
In the class that you call + (Player *)loadPlayerWithPredicate:(NSString *)name:(NSInteger)index, you will need to determine if you want to retain the returned Player (such as setting it to a retain property), or if you want to leave it as is (autoreleased, and thus will likely be released after the method this call is in has finished. Use this if you only need to perform a few actions with it immediately and don't need to hold on to it after).

Related

NSError * returned with bad address - why?

In the code below, the NSError, when returned, shows in XCode with a class type of 'HomeViewController' - one of my view controllers. This would make sense if I'd done something out of the ordinary with the double pointer, but I didn't. Why is this happening? Is there some stupid mistake in my code, a bug in Core Data, or ????
Hopefully I'm not making a fool of myself !
self->ctx = [(AppDelegate *) [[UIApplication sharedApplication] delegate] managedObjectContext];
NSFetchRequest * request = [[NSFetchRequest alloc] init];
NSEntityDescription *entityDescription = [NSEntityDescription
entityForName:#"Street" inManagedObjectContext:self->ctx];
NSSortDescriptor* sortDescriptor = [[NSSortDescriptor alloc]
initWithKey:#"street" ascending:YES];
NSArray* sortDescriptors = [[NSArray alloc] initWithObjects: sortDescriptor, nil];
[request setEntity:entityDescription];
[request setSortDescriptors:sortDescriptors];
NSError * error;
NSArray *array = [self->ctx executeFetchRequest:request error:&error];
if(error!=nil){
NSLog(#"%#", #"Critical model error search");
}
_streets = array;
Clearly the method you're using ([self->ctx executeFetchRequest:request error:&error]) doesn't set its argument to nil if there isn't an error, just to the error if one occurs. Initialize your variable to nil: NSError *error = nil;.
As pointed out in the comments, you have to check the return value of the call, and only if it's nil or NO (depending on the declared return type) is the NSError pointer guaranteed valid (incl. nil), even if it was valid going in. So instead of if(error != nil), use if(!array) or if(array == nil).
This keeps coming up. So, some concrete examples.
There is no reason to initialize the NSError to nil. The one caveat is if the object you are calling with the &err is nil, you'll get a false failure and checking an un-initialized error will blow up. However, that failure mode is very different than an attempt that leads to failure and, really, you should avoid the nil recipient case (imagine if your managed object context is unexpectedly nil-- you probably have a much bigger issues than a false error).
This is a perfectly valid implementation of a method that returns BOOL and takes an NSError**. Valid, but obviously contrived. However, this kind of thing does happen in a many layered API.
- (BOOL)doThis:(NSError**)chaffin
{
if (chaffin) *chaffin = 0x42;
return YES;
}
Calculating the error can be expensive. Pass null for the error parameter if you only need to know success/failure, but not why.

Create NSManagedObject (big size). Memory warning and the application crashes

I'm a new iOS developer. Hope for help.
I want to be able to create many NSManagedObjects. The size of fields of one NSManagedObject is about 5Mb. I couldn't save such a big amount of memory in the iPhone memory. And I want to save it in the database. But when I save the NSManagedObject, it's still in the memory, because when I save about 20 objects, I get the memory warning and the application crashes.
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 try to call [self SaveItem] 20 times , the app crashes with memory warning. When I commented
[object setValue:objNSData forKey:#"big"];
everything was OK.
I tried to add the code to #autoreleasepool , but that didn't help.
I know, that now, when I save data to database, it's still in iPhone memory. How to release it from this memory?
When I get a set of Managed Objects, they are not in the memory (I can easyly get 100 object, each of them has 5Mb fields)
Storing images (or big binary values) in core data is not a good option. Apple recommends against it. There are rules for that. Check this other question/answer.
If you want to get the objects out of memory, you need to remove all references you have for them. If you are executing "SaveItem" in a tight loop, you should wrap every loop execution in an autorelease pool, not the loop itself.
From apple docs, that may be your problem too:
Managed objects that have pending changes (insertions, deletions, or updates) are retained by their context until their context is sent a save:, reset , rollback, or dealloc message, or the appropriate number of undos to undo the change.
The undo manager associated with a context retains any changed managed objects. By default, the context's undo manager keeps an unlimited undo/redo stack. To limit your application's memory footprint, you should make sure that you scrub (using removeAllActions) the context's undo stack as and when appropriate. Unless you retain a context's undo manager, it is deallocated with its context.
If you do not intend to use Core Data's undo functionality, you can reduce your application's resource requirements by setting the context’s undo manager to nil. This may be especially beneficial for background worker threads, as well as for large import or batch operations.

Should I retain, autorelease or do nothing when returning NSArray of executeFetchRequest: result?

Please have a look at the code below:
- (NSArray *)requestEntities:(NSString *)entityName {
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
entityForName:entityName inManagedObjectContext:_context];
[fetchRequest setEntity:entity];
NSError *requestError = nil;
NSArray *result = [_context executeFetchRequest:fetchRequest error:&requestError];
[fetchRequest release], fetchRequest = nil;
return result;
}
and I need to use the result somewhere else, in this method, is result correctly returned(without retain or autorelease)? Also, what should its callers do when getting the result, retain it or use it straight away?
Thanks!
The convention is that you're not responsible for objects returned by methods other than those containing new, alloc, or copy. The array you're given is almost certainly already autoreleased, so it will be released when your code finishes and it goes back to the run loop. If you need to hold onto the array beyond that point (e.g., if you want to keep those results around and refer to them to respond to future UI events) then you should retain the array, preferably by assigning it to a retained property.
I heavily recommend you read the memory management programming guide in full, or at least the first few sections which get right to the meat of what you're asking about.

NSTokenFieldCell Subclass to force use of Core Data To-Many Relationship

I have come across an interesting conundrum (of course, I could just being doing something horribly wrong).
I would like an NSTokenField to "represent" a relationship in a Core Data Application. The premise is such: You click on a Note from a TableView (loaded from the Notes Array Controller). The token field is then bound (through "value") to the Notes Array Controller selection.Tags. Tags is a to-many relationship on the entity Notes.
Obviously, an NSTokenField will not accept the NSSet that the Array Controller Provides it. To get around this, I subclassed NSTokenFieldCell and overrode its objectValue and setObjectValue: methods. I thought that I could simply translate the NSSet that was being provided to the NSArray that the NSTokenFieldCell expected. (Note: I originally tried overriding these methods on a NSTokenField subclass; however, they were not being called.)
So, I came up with said code:
- (void)setObjectValue:(NSSet*)object {
tagsList = [object copy];
NSMutableArray *displayList = [[NSMutableArray alloc] init];
for (id newObject in tagsList) {
[displayList addObject:[newObject valueForKey:#"Name"]];
}
[super setObjectValue:displayList];
}
- (id)objectValue {
NSArray *displayList = [super objectValue];
NSEntityDescription *tagEntity = [NSEntityDescription
entityForName:#"Tag"
inManagedObjectContext:[appDelegate
managedObjectContext]];
NSMutableSet *returnValue = [[NSMutableSet alloc] init];
for (NSString *token in displayList) {
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:tagEntity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:
#"Name == %#", token];
[request setPredicate:predicate];
NSError *error;
NSArray *results = [[appDelegate managedObjectContext] executeFetchRequest:request error:&error];
if (results == nil) {
NSManagedObject *object = [NSEntityDescription insertNewObjectForEntityForName:#"Tag" inManagedObjectContext:[appDelegate managedObjectContext]];
[object setValue:token forKey:#"Name"];
[returnValue addObject:object];
} else {
[returnValue addObject:[results objectAtIndex:0]];
}
}
return returnValue;
}
It crashes. :( And, surprisingly it crashes on the line that calls [super objectValue]. It gives me the error:
-[NSConcreteAttributedString countByEnumeratingWithState:objects:count:]: unrecognized selector sent to instance ...
Sigh. The sad thing is that when I go into the Core Data XML file and give the Note a Tag, it displays correctly, and [super setObjectValue:] is passed an array of strings. However, as soon as I enter something else and mouse away, I get the error.
I am not sure what to do about this. Can anyone spot anything horribly wrong with this? Thanks.
UPDATE:
If it makes a difference, I do not have a delegate configured for the TokenField.
In typical SO fashion, I found the answer to my own question. It was silly to begin with. I simply needed another ArrayController bound to the Notes selection.Tags set. Then, I bound the NSTokenField to the ArrangedObjects of that Controller, implemented some delegate methods. Boom. Simple.
Silly me.

Adding unique objects to Core Data

I'm working on an iPhone app that gets a number of objects from a database. I'd like to store these using Core Data, but I'm having problems with my relationships.
A Detail contains any number of POIs (points of interest). When I fetch a set of POI's from the server, they contain a detail ID. In order to associate the POI with the Detail (by ID), my process is as follows:
Query the ManagedObjectContext for the detailID.
If that detail exists, add the poi to it.
If it doesn't, create the detail (it has other properties that will be populated lazily).
The problem with this is performance. Performing constant queries to Core Data is slow, to the point where adding a list of 150 POI's takes a minute thanks to the multiple relationships involved.
In my old model, before Core Data (various NSDictionary cache objects) this process was super fast (look up a key in a dictionary, then create it if it doesn't exist)
I have more relationships than just this one, but pretty much every one has to do this check (some are many to many, and they have a real problem).
Does anyone have any suggestions for how I can help this? I could perform fewer queries (by searching for a number of different ID's), but I'm not sure how much this will help.
Some code:
POI *poi = [NSEntityDescription
insertNewObjectForEntityForName:#"POI"
inManagedObjectContext:[(AppDelegate*)[UIApplication sharedApplication].delegate managedObjectContext]];
poi.POIid = [attributeDict objectForKey:kAttributeID];
poi.detailId = [attributeDict objectForKey:kAttributeDetailID];
Detail *detail = [self findDetailForID:poi.POIid];
if(detail == nil)
{
detail = [NSEntityDescription
insertNewObjectForEntityForName:#"Detail"
inManagedObjectContext:[(AppDelegate*)[UIApplication sharedApplication].delegate managedObjectContext]];
detail.title = poi.POIid;
detail.subtitle = #"";
detail.detailType = [attributeDict objectForKey:kAttributeType];
}
-(Detail*)findDetailForID:(NSString*)detailID {
NSManagedObjectContext *moc = [[UIApplication sharedApplication].delegate managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription
entityForName:#"Detail" inManagedObjectContext:moc];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entityDescription];
NSPredicate *predicate = [NSPredicate predicateWithFormat:
#"detailid == %#", detailID];
[request setPredicate:predicate];
NSLog(#"%#", [predicate description]);
NSError *error;
NSArray *array = [moc executeFetchRequest:request error:&error];
if (array == nil || [array count] != 1)
{
// Deal with error...
return nil;
}
return [array objectAtIndex:0];
}
Check out the section titled "Batch Faulting" on the page titled "Core Data Performance" in Xcode's Core Data Programming Guide that Norman linked to in his answer.
Only fetching those managedObjects whose ids are IN a collection (NSSet, NSArray, NSDictionary) of ids of the objects returned by the server may be even more efficient.
NSSet *oids = [[NSSet alloc] initWithObjects:#"oid1", #"oid2", ..., nil];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"oid IN %#", oids];
[oids release];
UPDATE: I worked this tip into a solution for the acani usersView. Basically, after downloading a JSON response of users, the iPhone uses the popular open source JSON framework to parse the response into an NSArray of NSDictionary objects, each representing a user. Then, it makes an NSArray of their uids and does a batch fetch on Core Data to see if any of them already exist on the iPhone. If not, it inserts it. If so, it updates the ones that do exist only if their updated attribute is older than that of the one from the server.
I've gotten all this to work really well, thanks to Norman, who put me on the right path. I'll post my helper class here for others.
Basically, my helper class will look up if an NSManagedObject exists for some ID, and can create it for some ID. This executes quickly enough for me, with 1,000 find/create operations taking around 2 seconds on my iPhone (I also did a few other things there, pure find/create is likely faster).
It does this by caching a dictionary of all the NSManagedObjects, and checking that cache rather than executing a new NSFetchRequest.
A couple of modifications that could help things speed up even further:
1. Get only selected properties for the NSManagedObjects
2. Only get the identifier property for the NSManagedObject into a dictionary, instead of the whole object.
In my performance testing, the single query wasn't the slow part (but with only 1,000 items, I'd expect it to be fast). The slow part was the creation of the items.
#import "CoreDataUniquer.h"
#implementation CoreDataUniquer
//the identifying property is the field on the NSManagedObject that will be used to look up our custom identifier
-(id)initWithEntityName:(NSString*)newEntityName andIdentifyingProperty:(NSString*)newIdProp
{
self = [super init];
if (self != nil) {
entityName = [newEntityName retain];
identifyingProperty = [newIdProp retain];
}
return self;
}
-(NSManagedObject*)findObjectForID:(NSString*)identifier
{
if(identifier == nil)
{
return nil;
}
if(!objectList)
{
NSManagedObjectContext *moc = [(AppDelegate*)[UIApplication sharedApplication].delegate managedObjectContext];
NSEntityDescription *entityDescription = [NSEntityDescription
entityForName:entityName inManagedObjectContext:moc];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entityDescription];
NSError *error;
NSArray *array = [moc executeFetchRequest:request error:&error];
objectList = [[NSMutableDictionary dictionary] retain];
for (NSManagedObject* p in array) {
NSString* itemId = [p valueForKey:identifyingProperty];
[objectList setObject:p forKey:itemId];
}
}
NSManagedObject* returnedObject = [objectList objectForKey:identifier];
return returnedObject;
}
-(NSManagedObject*)createObjectForID:(NSString*)identifier
{
NSManagedObject* returnedObject = [NSEntityDescription
insertNewObjectForEntityForName:entityName
inManagedObjectContext:[(AppDelegate*)[UIApplication sharedApplication].delegate managedObjectContext]];
[returnedObject setValue:identifier forKey:identifyingProperty];
[objectList setObject:returnedObject forKey:identifier];
return returnedObject;
}
- (void) dealloc
{
DESTROY(entityName);
DESTROY(identifyingProperty);
[super dealloc];
}
#end
This page provides some help on optimizing performance:
http://developer.apple.com/documentation/Cocoa/Conceptual/CoreData/Articles/cdPerformance.html#//apple_ref/doc/uid/TP40003468-SW1
While not very efficient, why not just build them in-memory with a NSDictionary? Read everything from Core Data into a NSDictionary then merge in your data, replacing everything in Core Data.