【问题标题】:Core Data: use executeFetchRequest in a loop核心数据:在循环中使用 executeFetchRequest
【发布时间】:2012-09-28 20:27:47
【问题描述】:

关于另一个帖子中的answer:在循环中使用executeFetchRequest 是一种不好的做法吗?我在斯坦福 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 获取的传入对象)。如果结果为 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;
}

【问题讨论】:

    标签: objective-c core-data nsfetchrequest cs193p


    【解决方案1】:

    我已经回复了你的问题Core data: executeFetchRequest vs performFetch

    这是我写的:

    在循环中执行请求可能会影响性能 但我不会为此担心。在引擎盖下核心数据维护 一种缓存机制。每次执行请求时,如果数据 不在缓存中,Core Data 在你的 store 上执行一次往返 (例如 sql 文件)并用它拥有的对象填充缓存 取回。如果您执行相同的查询,往返将不会 由于缓存机制再次执行。无论如何,你可以避免 在运行循环中执行请求,只需移动该请求 在循环之外。

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

    另一种方法,可能是将请求移到方法之外

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

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

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

    用下面的代码替换第一个sn-p

    - (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 代码中的一个简单更改可能是在后台执行 Core Data 操作,以防止主线程被阻塞。如果您有大量数据,这种方法可能很有用。如果数据很少,请保持原样。

    【讨论】:

    • 非常感谢您在此处和链接帖子中提供的信息丰富的回答。但是我不确定我是否正确理解了您的替代方法。如何将executeFetchRequest 移到循环之外?在斯坦福的案例中,一个获取请求只对应一个照片对象(因为它的谓词使用照片 ID)。因此,我们一次只能获取一张照片。这就是为什么循环是不可避免的。我错了吗?另外,如果您能改进伪代码,我们将不胜感激。
    • @Philip007 我改进了我的答案。希望能帮助到你。如果你觉得有用,也点赞。干杯。
    • 感谢您改进替代方法。两个问题。首先,删除循环内的// This is the method that will call executeFetchRequest 注释是否正确?您已经一劳永逸地在循环之外executeFetchRequest,并将所有获取的结果放入数组results
    • 第二,您的方法的一个潜在警告。 results 数组不会改变。因此,如果我们有来自网络有效负载的重复照片,则两者都将被插入到上下文中。使用斯坦福方法不会发生同样的事情。但是,确保每张照片都有唯一的 ID 应该是 Web API 的工作。所以应该没有问题。
    • 关于在后台执行核心数据操作的最后一个技巧的另一个问题。由于任何UIManagedDocument 的上下文始终位于主线程中,Apple 对此进行了记录。并且上下文不是线程安全的。我不知道你如何在后台线程中执行executeFetchRequest等。
    猜你喜欢
    • 2012-09-20
    • 1970-01-01
    • 1970-01-01
    • 2010-11-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-08
    相关资源
    最近更新 更多