Core Data: use executeFetchRequest in a loop - objective-c

Regarding one answer in another post: is that use executeFetchRequest in a loop a bad practice? I saw that usage in Stanford CS193p project "Photomania" (click link to download project). The relevant code is below:
The [FlickrFetcher recentGeoreferencedPhotos] is used to fetch photos from Flickr API, which happens in a background thread. But the loop that execute fetch request happens in main thread.
- (void)fetchFlickrDataIntoDocument:(UIManagedDocument *)document
{
dispatch_queue_t fetchQ = dispatch_queue_create("Flickr fetcher", NULL);
dispatch_async(fetchQ, ^{
NSArray *photos = [FlickrFetcher recentGeoreferencedPhotos];
// perform in the NSMOC's safe thread (main thread)
[document.managedObjectContext performBlock:^{
for (NSDictionary *flickrInfo in photos) {
// This is the method that will call executeFetchRequest
[Photo photoWithFlickrInfo:flickrInfo inManagedObjectContext:document.managedObjectContext];
}
[document saveToURL:document.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:NULL];
}];
});
dispatch_release(fetchQ);
}
Here is the factory method that first try to fetch objects from context (according to a pass-in object, which is fetched from flickr API). If result is nil, insert that object into context.
+ (Photo *)photoWithFlickrInfo:(NSDictionary *)flickrInfo
inManagedObjectContext:(NSManagedObjectContext *)context
{
Photo *photo = nil;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Photo"];
request.predicate = [NSPredicate predicateWithFormat:#"unique = %#", [flickrInfo objectForKey:FLICKR_PHOTO_ID]];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"title" ascending:YES];
request.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSError *error = nil;
NSArray *matches = [context executeFetchRequest:request error:&error];
if (!matches || ([matches count] > 1)) {
// handle error
} else if ([matches count] == 0) {
photo = [NSEntityDescription insertNewObjectForEntityForName:#"Photo" inManagedObjectContext:context];
photo.unique = [flickrInfo objectForKey:FLICKR_PHOTO_ID];
photo.title = [flickrInfo objectForKey:FLICKR_PHOTO_TITLE];
photo.subtitle = [flickrInfo valueForKeyPath:FLICKR_PHOTO_DESCRIPTION];
photo.imageURL = [[FlickrFetcher urlForPhoto:flickrInfo format:FlickrPhotoFormatLarge] absoluteString];
photo.whoTook = [Photographer photographerWithName:[flickrInfo objectForKey:FLICKR_PHOTO_OWNER] inManagedObjectContext:context];
} else {
photo = [matches lastObject];
}
return photo;
}

I already replied in your question Core data: executeFetchRequest vs performFetch.
Here what I wrote:
Executing the request within a loop could have impact on performances
but I would not be worried on that. Under the hood Core Data maintains
a sort of cache mechanism. Every time you perform a request, if data
are not in the cache, Core Data executes a round trip on your store
(e.g. sql file) and populate the cache with the objects it has
retrieved. If you perform the same query, the round trip will not
performed again due to the cache mechanism. Anyway, you could avoid to
execute a request within the run loop, simply moving that request
outside the loop.
In this case the request within the for loop is ok since you need to find the possible matches for the current (NSDictionary *)flickrInfo.
An alternative way, it could be to move the request outside the method
+ (Photo *)photoWithFlickrInfo:(NSDictionary *)flickrInfo
inManagedObjectContext:(NSManagedObjectContext *)context;
So for example, modify this method to accomodate a NSArray of results like:
+ (Photo *)photoWithFlickrInfo:(NSDictionary *)flickrInfo photoResults:(NSArray*)results
inManagedObjectContext:(NSManagedObjectContext *)context;
Replace the first snippet of code with the following
- (void)fetchFlickrDataIntoDocument:(UIManagedDocument *)document
{
dispatch_queue_t fetchQ = dispatch_queue_create("Flickr fetcher", NULL);
dispatch_async(fetchQ, ^{
NSArray *photos = [FlickrFetcher recentGeoreferencedPhotos];
// perform in the NSMOC's safe thread (main thread)
[document.managedObjectContext performBlock:^{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Photo"];
NSArray *results = [context executeFetchRequest:request error:&error];
for (NSDictionary *flickrInfo in photos) {
// This is the method that will call executeFetchRequest
[Photo photoWithFlickrInfo:flickrInfo photoResult:results inManagedObjectContext:document.managedObjectContext];
}
[document saveToURL:document.fileURL forSaveOperation:UIDocumentSaveForOverwriting completionHandler:NULL];
}];
});
dispatch_release(fetchQ);
}
In this case through the request you retrieve all the stored photos. The array (of managed objects) is passed to +(Photo*)photoWithFlickrInfo:photoResults:inManagedObjectContext:.
Now within +(Photo *)photoWithFlickrInfo:photoResults:inManagedObjectContext: you need to set a predicate for results that find the possible candidate based on [flickrInfo objectForKey:FLICKR_PHOTO_ID];. The motivation is quite simple: you have move the request outside the loop and now you need to retrieve the specific one. So, for example, you could do like:
+ (Photo *)photoWithFlickrInfo:(NSDictionary *)flickrInfo photoResults:(NSArray*)results
inManagedObjectContext:(NSManagedObjectContext *)context
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"unique == %#", [flickrInfo objectForKey:FLICKR_PHOTO_ID]];
NSArray* filteredPredicate = [results filterUsingPredicate:predicate];
// now filteredPredicate is the same as matches in the second snippet of your code.
// do the other code here..
}
Summarizing
Both approaches are valid. By means of them you can retrieve a photo already created or create a new one.
That's why loop is unavoidable. Am I wrong on this?
No, since you can try to follow my approach but the approach provided in Standford Course has a greater performance than the one I posted. I didn't made any performance test but if you are interested in you can do it yourself and analyze results by Instruments.
Simple tip
A simple change in the Standford code could be to perform Core Data operation in background preventing the main thread to be blocked. This approach could be useful if you have a lot of data. If data is minimal leave it as is.

Related

Returning an object from inside block within category class method implementation

I have run into a certain problem with my implementation which I don't really know how to solve. Could You please advise.
I'm trying to implement an NSManagedObject category class Photo+Flickr.m with one class method +(void)photoWithFlickrData:inManagedObjectContext:
What I would like to do is download data from Flickr API using NSURLSessionDownloadTask and then create Photo object and insert this new created object into database (if it's not already there). This part works fine.
And at the end I would like to return new created (or object that was found in db) Photo object. And this is where I run into problem. Since I'm using category I can't use instance variables. I can't really find any good solution to get this Photo object from inside this completionHandler block.
My code:
#implementation Photo (Flickr)
+ (void)photoWithFlickrData:(NSDictionary *)photoDictionary
inManagedObjectContext:(NSManagedObjectContext *)context
{
NSString *placeId = [photoDictionary valueForKeyPath:FLICKR_PHOTO_PLACE_ID];
NSURL *urlInfoAboutPlace = [FlickrFetcher URLforInformationAboutPlace:placeId];
NSURLRequest *request = [NSURLRequest requestWithURL:urlInfoAboutPlace];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
NSURLSessionDownloadTask *task =
[session downloadTaskWithRequest:request
completionHandler:^(NSURL *localfile, NSURLResponse *response, NSError *error) {
if(!error) {
NSData *json = [NSData dataWithContentsOfURL:localfile];
NSDictionary *flickrPlaceDictionary = [NSJSONSerialization JSONObjectWithData:json
options:0
error:NULL];
dispatch_async(dispatch_get_main_queue(), ^{
Photo *photo = nil;
// flickr photo unique id
NSString *uniqueId = [photoDictionary valueForKeyPath:FLICKR_PHOTO_ID];
NSFetchRequest *dbRequest = [NSFetchRequest fetchRequestWithEntityName:#"Photo"];
dbRequest.predicate = [NSPredicate predicateWithFormat:#"uniqueId = %#", uniqueId];
NSError *error;
NSArray *reqResults = [context executeFetchRequest:dbRequest error:&error];
if (!reqResults || error || [reqResults count] > 1) {
//handle error
} else if ([reqResults count]) {
//object found in db
NSLog(#"object found!");
photo = [reqResults firstObject];
} else {
//no object in db so create a new one
NSLog(#"object not found, creating new one");
photo = [NSEntityDescription insertNewObjectForEntityForName:#"Photo"
inManagedObjectContext:context];
//set its properties
photo.uniqueId = uniqueId;
photo.title = [photoDictionary valueForKey:FLICKR_PHOTO_TITLE];
photo.region = [FlickrFetcher extractRegionNameFromPlaceInformation:flickrPlaceDictionary];
NSLog(#"title: %#", photo.title);
NSLog(#"ID: %#", photo.uniqueId);
NSLog(#"region: %#", photo.region);
}
});
}
}];
[task resume];
//how to get Photo *photo object???
//return photo;
}
I would really appreciate any suggestions on how to implement this.
Since you have async operations happening inside your blocks, you'll need to pass a completion handler (block) to your photoWithFlickrData:inManagedObjectContext: method and call it when you have valid photo data.
You'll need to add a new parameter to your method so you can pass in the completion handler. I'd do something like this:
+ (void)photoWithFlickrData:(NSDictionary *)photoDictionary
inManagedObjectContext:(NSManagedObjectContext *)context
withCompletionHandler:(void(^)(Photo *photo))completionHandler
Then, when you have a valid photo object, call completionHandler like so:
completionHandler(photo);
It looks like you'd want to put that at the very end of the block you're passing to dispatch_async:
/* ... */
dispatch_async(dispatch_get_main_queue(), ^{
Photo *photo = nil;
/* ... */
completionHandler(photo);
});
/* ... */
Then, you can call your method like so:
[Photo photoWithFlickrData:photoDictionary
inManagedObjectContext:context
withCompletionHandler:^(Photo* photo) {
/* use your valid photo object here */
}
];
Outside of your block before you call [session downloadTaskWithRequest:.....] define a variable like this
__block Photo *photoObject = nil;
Then inside the block after you finish setting its properties, set
photoObject = photo;
Now you can do whatever you want with the photoObject variable outside of the block.
Check out this Apple developer documentation on Blocks and Variables.

Parsing asynchronously using NSOperationQueue or GCD

I've got this parsing operation that currently works fine, but I've started to notice that it is freezing up my UI slightly so I'm trying to refactor and get this done asynchronously. I'm having some issues however and was hoping someone could point me in the right direction. Here's my current (synchronous) code:
- (NSArray *)eventsFromJSON:(NSString *)objectNotation
{
NSParameterAssert(objectNotation != nil);
NSData *unicodeNotation = [objectNotation dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
NSDictionary *eventsData = [NSJSONSerialization JSONObjectWithData:unicodeNotation options:0 error:&error];
if (eventsData == nil) {
//invalid JSON
return nil;
}
NSArray *events = [eventsData valueForKeyPath:#"resultsPage.results"];
if (events == nil) {
//parsing error
return nil;
}
NSLog(#"events looks like %#", events);
NSMutableArray *formattedEvents = [NSMutableArray arrayWithCapacity:events.count];
for (id object in [events valueForKeyPath:#"event"]) {
Event *event = [[Event alloc] init];
event.latitude = [object valueForKeyPath:#"location.lat"];
event.longitude = [object valueForKeyPath:#"location.lng"];
event.title = [object valueForKeyPath:#"displayName"];
event.venue = [object valueForKeyPath:#"venue.displayName"];
event.ticketsLink = [NSURL URLWithString:[object valueForKeyPath:#"uri"]];
event.artist = [object valueForKeyPath:#"performance.artist.displayName"];
event.date = [object valueForKeyPath:#"start.datetime"];
[formattedEvents addObject:event];
}
return [NSArray arrayWithArray:formattedEvents];
}
I've been looking into NSOperationQueue's and I'm struggling to find a solution as I'd like to return an array from this method and operation queues are not meant to have return values. I'm also looking at GCD and i've got somethinbg like this:
- (NSArray *)eventsFromJSON:(NSString *)objectNotation
{
dispatch_queue_t backgroundQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
__block NSMutableArray *mutable = [NSMutableArray array];
dispatch_async(backgroundQueue, ^{
NSParameterAssert(objectNotation != nil);
NSData *unicodeNotation = [objectNotation dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
NSDictionary *eventsData = [NSJSONSerialization JSONObjectWithData:unicodeNotation options:0 error:&error];
if (eventsData == nil) {
//invalid JSON
mutable = nil;
}
NSArray *events = [eventsData valueForKeyPath:#"resultsPage.results"];
if (events == nil) {
//parsing error
mutable = nil;
}
NSLog(#"events looks like %#", events);
NSMutableArray *formattedEvents = [NSMutableArray arrayWithCapacity:events.count];
for (id object in [events valueForKeyPath:#"event"]) {
Event *event = [[Event alloc] init];
event.latitude = [object valueForKeyPath:#"location.lat"];
event.longitude = [object valueForKeyPath:#"location.lng"];
event.title = [object valueForKeyPath:#"displayName"];
event.venue = [object valueForKeyPath:#"venue.displayName"];
event.ticketsLink = [NSURL URLWithString:[object valueForKeyPath:#"uri"]];
event.artist = [object valueForKeyPath:#"performance.artist.displayName"];
event.date = [object valueForKeyPath:#"start.datetime"];
[formattedEvents addObject:event];
}
mutable = [NSMutableArray arrayWithArray:formattedEvents];
});
return [mutable copy];
}
For some reason, this seems to be returning the object before the parsing has finished however, as I'm gettting no data out of that mutable object, but I'm noticing that the parsing is indeed occurring (i'm logging out the results). can anyone give me an idea about how to get this asynch stuff going?
Thanks!!
You primary problem is that by their very nature asynchronous operations can't synchronously return a result. Instead of returning an array from -eventsFromJSON:, you should provide a way for the caller to receive a callback when the results are finished. There are two common approaches to this in Cocoa.
You can create a delegate with an associated delegate protocol including a method like -parser:(Parser *)parser didFinishParsingEvents:(NSArray *)events, then have your parser call this method on its delegate when parsing is finished.
Another solution is to allow the caller to provide a completion block to be executed when parsing is complete. So, you might do something like this:
- (void)eventsFromJSON:(NSString *)objectNotation completionHandler:(void (^)(NSArray *events))completionHandler)
{
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(backgroundQueue, ^{
NSMutableArray *mutable = [NSMutableArray array];
NSParameterAssert(objectNotation != nil);
NSData *unicodeNotation = [objectNotation dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
// Snip...
mutable = [NSMutableArray arrayWithArray:formattedEvents];
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler([mutable copy]);
});
});
}
Then you can call this code some thing like this:
- (void)parseJSONAndUpdateUI // Or whatever you're doing
{
NSString *jsonString = ...;
Parser *parser = [[Parser alloc] init];
[parser parseEventsFromJSON:jsonString completionHandler:^(NSArray *events){
// Update UI with parsed events here
}];
}
I like the second, block-based approach better. It makes for less code in most cases. The code also reads closer to the synchronous approach where the method just returns an array, since the code that uses the resultant array simply follows the method call (albeit indented since it's in the completion block's scope).
I would recommend using a completion block that you pass into your parse method. This way you don't have to return a value, but can do what you need to with the information once it is parsed. You just have to make sure you use GCD again to put the completion block on the main thread.
You could also post a notification on the main thread once the operation is complete that contains the array in userInfo.
Returning a value will not work however for asynchronous operations.
You are getting a returned object before the parsing has finished because your return [mutable copy] is outside of the dispatch_async block. Since dispatch_async functions asynchronously, it will return immediately, and then calls your return [mutable copy] (which is empty because it's not done parsing).

Photo instances fetched into memory when app launched?

The code is from Stanford iOS developing course's Photomania app. Basically I want to know when instances of Photo entity are actually fetched into memory (or context). Is that happen when the factory method defined here is called in a table view controller?
#interface Photo (Flickr)
+ (Photo *)photoWithFlickrInfo:(NSDictionary *)flickrInfo
inManagedObjectContext:(NSManagedObjectContext *)context;
#end
#implementation Photo (Flickr)
+ (Photo *)photoWithFlickrInfo:(NSDictionary *)flickrInfo
inManagedObjectContext:(NSManagedObjectContext *)context
{
Photo *photo = nil;
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:#"Photo"];
request.predicate = [NSPredicate predicateWithFormat:#"unique = %#", [flickrInfo objectForKey:FLICKR_PHOTO_ID]];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"title" ascending:YES];
request.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSError *error = nil;
NSArray *matches = [context executeFetchRequest:request error:&error];
if (!matches || ([matches count] > 1)) {
// handle error
} else if ([matches count] == 0) {
photo = [NSEntityDescription insertNewObjectForEntityForName:#"Photo" inManagedObjectContext:context];
photo.unique = [flickrInfo objectForKey:FLICKR_PHOTO_ID];
photo.title = [flickrInfo objectForKey:FLICKR_PHOTO_TITLE];
photo.subtitle = [flickrInfo valueForKeyPath:FLICKR_PHOTO_DESCRIPTION];
photo.imageURL = [[FlickrFetcher urlForPhoto:flickrInfo format:FlickrPhotoFormatLarge] absoluteString];
photo.whoTook = [Photographer photographerWithName:[flickrInfo objectForKey:FLICKR_PHOTO_OWNER] inManagedObjectContext:context];
} else {
photo = [matches lastObject];
}
return photo;
}
#end
The photo is loaded into memory when you call this method. CoreData probably does some caching such that subsequent fetches will not have to go to the storage backend, but this is definitely where the magic happens.
You should read up more on CoreData. It is a huge framework, but a solid understanding of it will take you a long way in designing efficient and sensible storage solutions for Cocoa apps.
This is kind of the "create photo" or "insert photo" method for the "database." So when the FlickrFetcher class goes out and retrieves photos, for every photo it retrieves this method is called.
This method first checks to see if the photos exists in the core data database, and if not adds it, and saves the managed object context.
So, technically, the Photo object is created in memory with [NSEntityDescription insertNewObjectForEntityForName: inManagedObjectContext:] method.

How to use a stored FetchRequest with NSFetchedResultsController?

NSFetchedResultsController provides a lot of nice stuff free when working with tableViews. I also understand that storing fetch requests in the model is good form. Can I use both of these together for super core data goodness? If so, how so?
This example from Mr. Zarra's excellent book shows the template returning an array and I've failed in my attempts to get a fetchedResultsController back.
- (NSArray*)retrieveBigMeals {
NSManagedObjectContext *moc = [self managedObjectContext];
NSManagedObjectModel *mom = [self managedObjectModel];
NSFetchRequest *request = [mom fetchRequestTemplateForName:#"bigMeals"];
NSError *error = nil;
NSArray *result = [moc executeFetchRequest:request error:&error];
if (error) {
[NSApp presentError:error]; return nil;
} return result;
Just use the ios navigation based application template with core data. They already have the code for using NSFetchedResultController there.

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.