应用对 NSFetchRequests 无响应
Posted
技术标签:
【中文标题】应用对 NSFetchRequests 无响应【英文标题】:App becomes unresponsive on NSFetchRequests 【发布时间】:2012-04-30 20:05:47 【问题描述】:我的第一个 iPad 应用程序中有上述 CoreData 模型。我正在 TableViewController 中构建过滤系统,如下所示。问题是,每当我更改 UI 时,切换点击按钮的开关,我的 UI 会在一两秒内变得无响应。我运行了一个非常长的函数,它重新创建照片的获取请求,然后运行更多计数获取以确定是否应该启用控件。我只是不知道如何以有意义的方式将其分开以防止挂起。即使我需要添加一秒钟左右的旋转视图,我也很满意。只是想摆脱滞后。
正如我所提到的,这是我第一次尝试 ios 开发,所以我将不胜感激任何建议......
-(void) refilterPhotos
/*
* First section builds the NSCompoundPredicate to use for searching my CoreData Photo objects.
Second section runs queries so 0 result controls can be disabled.
*/
subpredicates = [[NSMutableArray alloc] init];
NSPredicate *isNewPredicate;
if(newSwitch.on)
isNewPredicate = [NSPredicate predicateWithFormat:@"is_new == 1"];
else
isNewPredicate = [NSPredicate predicateWithFormat:@"is_new == 0"];
[subpredicates addObject:isNewPredicate];
//Photo Types
PhotoType *photoType;
NSPredicate *photoTypePredicate;
for (UISwitch *photoSwitch in photoSwitches)
PhotoType * type = (PhotoType *) photoSwitch.property;
if([type.selected boolValue] == YES)
NSLog(@"photo_type.label == %@", type.label);
photoType = type;
photoTypePredicate = [NSPredicate predicateWithFormat:@"photo_type.label == %@", type.label];
break;
//Feed Types
FeedType *feedType;
NSPredicate *feedTypePredicate;
for (UISwitch *feedSwitch in feedSwitches)
FeedType * type = (FeedType *) feedSwitch.property;
if([type.selected boolValue] == YES)
NSLog(@"feed_type.label == %@", type.label);
feedType = type;
feedTypePredicate = [NSPredicate predicateWithFormat:@"feed_type.label == %@", type.label];
break;
//Markets
NSArray *filteredMarkets = [model.availableMarkets filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"selected == 1"]];
for (Market *market in filteredMarkets)
[subpredicates addObject:[NSPredicate predicateWithFormat:@"ANY markets.name == %@", market.name]];
//Tags
NSArray *filteredTags = [model.availableTags filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"selected == 1"]];
for (Tag *tag in filteredTags)
NSLog(@"ANY tags.name == %@",tag.name);
[subpredicates addObject:[NSPredicate predicateWithFormat:@"ANY tags.name == %@", tag.name]];
if(photoTypePredicate)
[subpredicates addObject:photoTypePredicate];
if(feedTypePredicate)
[subpredicates addObject:feedTypePredicate];
NSPredicate *finished = [NSCompoundPredicate andPredicateWithSubpredicates:subpredicates];//Your final predicate
model.availablePhotos = [model fetchPhotoswithPredicate:finished];
[[self parentViewController] setTitle:[NSString stringWithFormat:@"%d items",[model.availablePhotos count]]];
NSLog(@"FILTERED PHOTOS:::: %d", [model.availablePhotos count]);
[gridVC reloadGrid];
/**
* Filtering Section Here, I'm running count requests for each grouping of controls to ensure if they're selected, results will be returned.
* If zero results, I'll disable that control. For the switch-based controls, I need to removed them before running my fetches since there can only be
* one switch value per photo.
*/
//Have to remove the existing type predicate since they're exlcusive values
[subpredicates removeObject:isNewPredicate];
//New Toggle
NSPredicate *newRemainderPredicate = [NSPredicate predicateWithFormat:@"is_new == %d",newSwitch.on?0:1];
[subpredicates addObject:newRemainderPredicate];
if([model countPhotoswithPredicate:[NSCompoundPredicate andPredicateWithSubpredicates:subpredicates]]<1)
[newSwitch setEnabled:NO];
else
[newSwitch setEnabled:YES];
[subpredicates removeObject:newRemainderPredicate];
[subpredicates addObject:isNewPredicate];
[subpredicates removeObject:photoTypePredicate];
//Photo Type Toggles
NSArray *remainderPhotoTypes = [photoSwitches filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"on == NO"]];
for( UISwitch*control in remainderPhotoTypes)
PhotoType *remainderPhotoType = (PhotoType*)control.property;
[subpredicates addObject:[NSPredicate predicateWithFormat:@"photo_type == %@", remainderPhotoType]];
if([model countPhotoswithPredicate:[NSCompoundPredicate andPredicateWithSubpredicates:subpredicates]]<1)
//NSLog(@"PHOTOTYPE OFF %@", remainderPhotoType.label);
control.enabled = NO;
else
//NSLog(@"PHOTOTYPE ON %@ count = %d", remainderPhotoType.label, [[model fetchPhotoswithPredicate:[NSCompoundPredicate andPredicateWithSubpredicates:subpredicates]] count]);
control.enabled = YES;
remainderPhotoType.enabled = [NSNumber numberWithBool:control.enabled];
[subpredicates removeObject:[NSPredicate predicateWithFormat:@"photo_type == %@", remainderPhotoType]];
if(photoTypePredicate)
[subpredicates addObject:photoTypePredicate];
[subpredicates removeObject:feedTypePredicate];
//Feed Type Toggles
NSArray *remainderFeedTypes = [feedSwitches filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"on == NO"]];
for( UISwitch*control in remainderFeedTypes)
PhotoType *remainderFeedType = (PhotoType*)control.property;
[subpredicates addObject:[NSPredicate predicateWithFormat:@"feed_type == %@", remainderFeedType]];
if([model countPhotoswithPredicate:[NSCompoundPredicate andPredicateWithSubpredicates:subpredicates]]<1)
control.enabled = NO;
else
control.enabled = YES;
remainderFeedType.enabled = [NSNumber numberWithBool:control.enabled];
[subpredicates removeObject:[NSPredicate predicateWithFormat:@"feed_type == %@", remainderFeedType]];
if(feedTypePredicate)
[subpredicates addObject:feedTypePredicate];
NSArray *remainderMarkets = [[model availableMarkets] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"selected == 0"]];
//Markets..many-to-many so I don't remove the existing predicate
for( Market *remainderMarket in remainderMarkets)
[subpredicates addObject:[NSPredicate predicateWithFormat:@"ANY markets == %@", remainderMarket]];
NSInteger countForTag = [model countPhotoswithPredicate:[NSCompoundPredicate andPredicateWithSubpredicates:subpredicates]];
if(countForTag<1)
remainderMarket.enabled = [NSNumber numberWithInt:0];
else
remainderMarket.enabled = [NSNumber numberWithInt:1];
[subpredicates removeObject:[NSPredicate predicateWithFormat:@"ANY markets == %@", remainderMarket]];
NSArray *remainderTags = [[model availableTags] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"selected == 0"]];
//TAGS..many-to-many so I don't remove the existing predicate
int tagCounter = 0;
for( Tag *remainderTag in remainderTags)
[subpredicates addObject:[NSPredicate predicateWithFormat:@"ANY tags == %@", remainderTag]];
NSInteger countForTag = [model countPhotoswithPredicate:[NSCompoundPredicate andPredicateWithSubpredicates:subpredicates]];
if(countForTag<1)
NSLog(@"TAG OFF %@", remainderTag.name);
remainderTag.enabled = 0;
else
NSLog(@"TAG ON %@ count = %d", remainderTag.name, countForTag);
remainderTag.enabled = [NSNumber numberWithInt:1];
[subpredicates removeObject:[NSPredicate predicateWithFormat:@"ANY tags.name == %@", remainderTag.name]];
tagCounter ++;
//Update the controls with this new data
[self.tableView reloadData];
【问题讨论】:
在代码中您似乎没有对subpredicates
做任何事情。在您的 UI 中,您有时应该使用 UISegmentedControl
而不是 UISwitch
。
嗨,谢谢...我正在将子谓词发送到我的模型,该模型执行 NSManagedObjectContext 获取...我创建了一个 NSCompoundPredicate ([NSCompoundPredicate andPredicateWithSubpredicates:subpredicates];) 将所有谓词连接在一起。返回一个 NSArray 照片或 NSInteger 计数。
【参考方案1】:
好的,这里有几件事要考虑。
首先,我会考虑为主要搜索字段创建索引。没有索引,每次搜索都是线性的,因为它必须检查每条记录的值。索引将导致更快的搜索时间。
其次,在复合谓词中排序时我会非常小心。它将根据顺序过滤它们。因此,您希望首先使您最快、最多过滤谓词。尽快修剪可能的解空间。
通过对前 1-3 个谓词中使用的属性进行索引,您可以获得很多好处。我在底部注意到,当您查询计数时,您仍在使用相同的复合谓词。你真的想要那个吗?另外,在这段代码中
//Have to remove the existing type predicate since they're exlcusive values
[subpredicates removeObject:isNewPredicate];
//New Toggle
NSPredicate *newRemainderPredicate = [NSPredicate predicateWithFormat:@"is_new == %d",newSwitch.on?0:1];
[subpredicates addObject:newRemainderPredicate];
您正在从前面删除 is_new 检查,并将其放在后面。如果您只是检查这一个谓词来切换该开关,而您只关心是否有 0 个或更多,为什么还要使用整个复合谓词?相对于所有其他字段,“切换”是否会打开/关闭?
如果您继续这样做,请记住,它将首先执行所有其他谓词(有些是引用)。尽量让它们保持良好的顺序,以便尽可能快地过滤。
第三,使用引用很方便,但代价高昂。您可以通过单独查询它们,然后使用复合谓词过滤内存中的对象来获得更好的性能。
第四,您应该在单独的线程中执行所有这些查询。这很容易做到,但具体方法取决于您当前的 ManagedObjecttContext 安排。你有一个单一的 MOC,一个父/子关系,一个 UIManagedDocument 吗?基本上,您可以创建一个单独的 MOC,并调用 performBlock 来执行提取。事实上,您可以使用多个 MOC 同时异步触发所有这些获取。
然后,你可以在它们完成后调用主线程。
最后,您可能需要考虑对数据库进行非规范化。它会导致您使用更多空间,但获取速度会快得多。具体来说,关系字段......您可以将照片/提要标签与照片本身放在一起。这样,在搜索时,您不必进行额外的连接来获取这些记录。
所以,这不是一个简单的答案,而是实现其中的每一个,看看你的性能是否没有显着提高(更不用说你的 UI 响应能力了)。
【讨论】:
非常感谢您的周到。这是极好的信息,正是我扩展知识所需要的。要回答您关于 is_new 检查的问题,是的,顶部的“新”开关需要相对于其他字段启用/禁用。我有一个模型类来保存对我的上下文的引用,所以当我需要一个照片的 NSArray 或只是计数时,我调用了这两个方法:fetchPhotosWithPredicate 和 countPhotoswithPredicate。您认为使用多个 MOC 进行过滤与仅使用一个用于线程的 MOC 相比,性能会有所提高吗? 在后台线程上执行工作肯定会帮助您的 UI 变得无响应。对于其余部分,我个人会运行工具来确定导致性能问题的主要因素,然后使用这些信息来解决问题,按照它们对性能的影响进行排序。以上是关于应用对 NSFetchRequests 无响应的主要内容,如果未能解决你的问题,请参考以下文章