核心数据:在循环中使用executeFetchRequest

时间:2012-09-28 20:27:48

标签: objective-c core-data nsfetchrequest cs193p

关于另一篇文章中的一个answer:在循环中使用executeFetchRequest是一种不好的做法吗?我在Stanford CS193p项目中看到了这种用法“Photomania”(点击链接下载项目)。相关代码如下:

[FlickrFetcher recentGeoreferencedPhotos]用于从Flickr API中获取照片,这些照片发生在后台线程中。但是执行获取请求的循环发生在主线程中。

- (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);
}

这是首次尝试从上下文中获取对象的工厂方法(根据从flickr API获取的传入对象)。如果result为nil,则将该对象插入上下文。

+ (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;
}

1 个答案:

答案 0 :(得分:2)

我已在您的问题Core data: executeFetchRequest vs performFetch中回复。

这是我写的:

  

在循环中执行请求可能会影响性能   但我不担心。核心数据保持领先   一种缓存机制。每次执行请求时,如果是数据   不在缓存中,Core Data会在您的商店中执行往返   (例如sql文件)并使用它拥有的对象填充缓存   检索。如果您执行相同的查询,则往返不会   由于缓存机制再次执行。无论如何,你可以避免   在运行循环中执行请求,只需移动该请求   在循环之外。

在这种情况下,for循环中的请求是正确的,因为您需要找到当前(NSDictionary *)flickrInfo的可能匹配项。

另一种方法是,可以将方法移出方法

+ (Photo *)photoWithFlickrInfo:(NSDictionary *)flickrInfo
        inManagedObjectContext:(NSManagedObjectContext *)context;

例如,修改此方法以容纳NSArray个结果,如:

+ (Photo *)photoWithFlickrInfo:(NSDictionary *)flickrInfo photoResults:(NSArray*)results
        inManagedObjectContext:(NSManagedObjectContext *)context;

使用以下

替换第一段代码
- (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);
}

在这种情况下,通过请求您可以检索所有存储的照片。数组(托管对象)传递给+(Photo*)photoWithFlickrInfo:photoResults:inManagedObjectContext:

现在在+(Photo *)photoWithFlickrInfo:photoResults:inManagedObjectContext:范围内,您需要为results设置一个谓词,根据[flickrInfo objectForKey:FLICKR_PHOTO_ID];找到可能的候选者。动机非常简单:您已将请求移出循环,现在您需要检索特定的请求。所以,例如,你可以这样做:

+ (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..    
}

综述

两种方法都有效。通过它们,您可以检索已创建的照片或创建一个新照片。

  

这就是为什么循环是不可避免的。我错了吗?

不,因为你可以尝试遵循我的方法但是Standford课程中提供的方法比我发布的方法有更好的表现。我没有进行任何性能测试,但如果你有兴趣你可以自己做,并通过仪器分析结果。

简单提示

Standford代码的一个简单变化可能是在后台执行Core Data操作,防止主线程被阻塞。如果您有大量数据,这种方法可能很有用。如果数据最小,请保持原样。