質問

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.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top