当我实现GCD时,我无法在我的UISearchDisplayController中显示Core Data的结果。没有它,它可以工作,但显然阻止了用户界面。
在我的SearchTableViewController中,我有以下两种方法:
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
// Tell the table data source to reload when text changes
[self filterContentForSearchText:searchString];
// Return YES to cause the search result table view to be reloaded.
return YES;
}
// Update the filtered array based on the search text
-(void)filterContentForSearchText:(NSString*)searchText
{
// Remove all objects from the filtered search array
[self.filteredLocationsArray removeAllObjects];
NSPredicate *predicate = [CoreDataMaster predicateForLocationUsingSearchText:@"Limerick"];
CoreDataMaster *coreDataMaster = [[CoreDataMaster alloc] init];
// Filter the array using NSPredicate
self.filteredLocationsArray = [NSMutableArray arrayWithArray:
[coreDataMaster fetchResultsFromCoreDataEntity:@"City" UsingPredicate:predicate]];
}
您可能猜到我的问题是从[coreDataMaster fetchResultsFromCoreDataEntity]返回数组。以下是方法:
- (NSArray *)fetchResultsFromCoreDataEntity:(NSString *)entity UsingPredicate:(NSPredicate *)predicate
{
NSMutableArray *fetchedResults = [[NSMutableArray alloc] init];
dispatch_queue_t coreDataQueue = dispatch_queue_create("com.coredata.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(coreDataQueue, ^{
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entityDescription = [NSEntityDescription
entityForName:entity inManagedObjectContext:self.managedObjectContext];
NSSortDescriptor *nameSort = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects:nameSort, nil];
[fetchRequest setEntity:entityDescription];
[fetchRequest setSortDescriptors:sortDescriptors];
// Check if predicate is set
if (predicate)
{
[fetchRequest setPredicate:predicate];
}
NSError *error = nil;
NSArray *fetchedManagedObjects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
for (City *city in fetchedManagedObjects)
{
[fetchedResults addObject:city];
}
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[NSArray arrayWithArray:fetchedResults] forKey:@"results"];
[[NSNotificationCenter defaultCenter]
postNotificationName:@"fetchResultsComplete"
object:nil userInfo:userInfo];
});
return [NSArray arrayWithArray:fetchedResults];
}
因此,在将结果返回self.filteredLocationsArray时,线程尚未完成执行。我已经尝试添加一个NSNotification,它将NSDictionary传递给这个方法:
- (void)updateSearchResults:(NSNotification *)notification
{
NSDictionary *userInfo = notification.userInfo;
NSArray *array = [userInfo objectForKey:@"results"];
self.filteredLocationsArray = [NSMutableArray arrayWithArray:array];
[self.tableView reloadData];
}
我也尝试刷新了像
这样的searchViewController[self.searchDisplayController.searchResultsTableView reloadData];
但无济于事。如果有人能指出我正确的方向并告诉我可能出错的地方,我真的很感激。
由于
修改
我只是想在克里斯托弗的解决方案中澄清一些事情以供将来参考。
当我调用此方法时,我将GCD调用放入竞争块中的主队列。另请注意重新加载tableView的更改。
实施例
[coreDataMaster fetchResultsFromCoreDataEntity:(NSString *)entity usingPredicate:(NSPredicate *)predicate completionHandler:^(NSArray *cities){
dispatch_async(dispatch_get_main_queue(), ^{
self.filteredLocationsArray = cities;
[self.searchDisplayController.searchResultsTableView reloadData];
});
}];
答案 0 :(得分:1)
我想你不能使用搜索显示控制器的常用委托方法来更新UI。异步搜索完成后,您需要自己更新UI。
首先,避免冗余。抛弃整个搜索结果并进行全新搜索是没有意义的。相反,您可以使用谓词过滤内存中已有的结果。
其次,在shouldReloadTableForSearchString
中,您应该返回NO
。相反,通过触发数据阵列filteredLocationsArray
的更新来对搜索字符串中的更改做出反应。 (您可以直接执行此操作,而不必像现在一样创建3个不同的副本。)然后从异步进程内部更新主线程上的表。这是架构:
dispatch_async(coreDataQueue, ^{
// fetch the data
dispatch_async(dispatch_get_main_queue(), ^{
[_tableView reloadData];
});
});
答案 1 :(得分:1)
你有几个问题。
首先,您在异步回调中填充fetchedResults
数组。在获取之前,fetchResultsFromCoreDataEntity:usingPredicate:
会立即返回。处理此问题的常用方法是添加完成处理程序块:
-(void)fetchResultsFromCoreDataEntity:(NSString *)entity usingPredicate:(NSPredicate *)predicate completionHandler:(void (^)(NSArray *))completionHandler {
// create this queue in your init and re-use it
// dispatch_queue_t coreDataQueue = dispatch_queue_create("com.coredata.queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(coreDataQueue, ^{
NSMutableArray *fetchedResults = [NSMutableArray array];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entityDescription = [NSEntityDescription
entityForName:entity inManagedObjectContext:self.managedObjectContext];
NSSortDescriptor *nameSort = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObjects:nameSort, nil];
[fetchRequest setEntity:entityDescription];
[fetchRequest setSortDescriptors:sortDescriptors];
// Check if predicate is set
if (predicate != nil) {
[fetchRequest setPredicate:predicate];
}
NSError *error = nil;
NSArray *fetchedManagedObjects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
for (City *city in fetchedManagedObjects) {
[fetchedResults addObject:city];
}
if(completionHandler != nil) completionHandler(fetchedResults);
});
}
其次,您将核心数据对象从一个线程传递到另一个线程。核心数据不是线程安全的,这样做几乎肯定会导致崩溃似乎是随机的并且很难重现 - 我已经看过了。在后台线程的某个位置,无论是在fetchResultsFromCoreDataEntity:usingPredicate:completionHandler:
内还是在completionHandler
块中,您都应该将所需的字段从核心数据对象映射到vanilla值对象,并将它们传递给主线程。像这样:
NSMutableArray *cities = [NSMutableArray array];
for (City *city in fetchedResults) {
CityFields *cityFields = [[CityFields alloc] init];
cityFields.name = city.name;
cityFields.population = city.population;
[cities addObject:cityFields];
}
dispatch_async(dispatch_get_main_queue(), ^{
self.filteredLocationsArray = cities;
[self.tableView reloadData];
});