核心数据在块中使用setFetchLimit迭代fetchrequest,仅处理一半记录

时间:2013-03-26 18:30:31

标签: core-data batch-processing nsfetchrequest

我正在尝试以特定大小(batchSize)的块处理大量对象。这个循环似乎有效,但它只处理一半的记录。相关的代码是:

{
//Prepare fetching products without images in the database
NSFetchRequest * productFetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Product"];

//Sort by last changed photo first
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"photoModificationDate" ascending:NO];
[productFetchRequest setSortDescriptors:@[sortDescriptor]];

NSPredicate *predicate = [NSPredicate predicateWithFormat: predicateString];
[productFetchRequest setPredicate:predicate];

//First get the total count
NSUInteger numberOfProducts = [self.backgroundMOC countForFetchRequest: productFetchRequest error: &error];
NSLog(@"Getting images for: %d products", numberOfProducts);

//Then set the batchsize to get chunks of data
NSUInteger batchSize = 25;
[productFetchRequest setFetchBatchSize: batchSize];
[productFetchRequest setFetchLimit:batchSize];

//Fetch the products in batches
for (NSUInteger offset = 0; offset < numberOfProducts; offset += batchSize) {
    @autoreleasepool {
        [productFetchRequest setFetchOffset: offset];
        NSArray * products = [self.backgroundMOC executeFetchRequest:productFetchRequest error:&error];
        NSLog(@"Offset: %d, number of products: %d", offset, [products count]);
        if (!products) {
            return NO;
        }

        for (Product * product in products) {
            NSLog(@"Downloading photo for product: %@", product.number);
            [self downLoadAndStoreImageForProduct:product];
        }
        [self saveAndResetBackgroundMOC];
    }
}

return YES;

}

日志显示对于计数的前半部分(numberOfProducts),它按预期工作。因此处理了25种产品。在上半部分之后,循环中的fetchrequest结果为0记录。 如果我再次重试相同的代码,则仅处理一半(剩余)记录,因此总共3/4。 我究竟做错了什么? 请注意,managedObjectContext不仅会保存,还会在保存后重置以节省内存。如果我不是以块的形式执行此操作,则在下载约3000张图片后,程序会一直崩溃。

2 个答案:

答案 0 :(得分:3)

第一点:也许对fetchLimitfetchBatchSize做什么有一些基本的误解。

fetchLimitfetchOffset确定提取的记录数和记录数。

fetchBatchSize表示在一次访问持久性存储期间应检索的记录数。因此,如果(有或没有fetchBatchSize)将要检索的记录数为100,则fetchBatchSize为25将导致4次到商店的旅行。 (换句话说,4个典型的SQLite存储执行的SQL语句。但是,这一切都发生在幕后。)

因此,代码片段

request.fetchLimit      = x; 
request.fetchBatchSize  = x;

是多余的。无论如何,到商店的旅行总数将是一个。

第二点:我不确定你对第二个MOC的设置有多大意义。我想你已经在后台线程了。据我所知,重置MOC非常昂贵。如果禁用MOC的撤消管理器,则没有必要。至于循环,我相信你可以只获取所有记录并让fetchBatchSize处理离散的“分块”。由于Core Data的错误行为,循环中的@autoreleasepool可能只会带来有限的优势。

@autoreleaspool有用的地方是下载图像的时间。也许批处理这部分过程就足够了。

话虽这么说,你可能不想改变那种(有点)工作的东西。

第三点:您根据未知(对我们)谓词字符串计算记录数。它是动态的吗?不确定这可能也不是问题的一部分。毕竟,不知道它是什么,令人惊讶的是记录的数量发生了变化。

最后:检查你是否可以不重置你的MOC。

答案 1 :(得分:0)

问题在于谓词。它会在没有图像的情况下获取所有产品。如果我下载图像,谓词的结果集会在后续提取时发生变化,并且每次都会变小。解决方案是以相反的顺序处理结果集。所以改变:

for (NSUInteger offset = 0; offset < numberOfProducts; offset += batchSize)

分为:

for (NSInteger offset = MAX(numberOfProducts - batchSize, 0); offset > 0; offset -= batchSize)