Multithreading 使用GCD使用核心数据结果更新UISearchDisplayController
在实现GCD时,在UISearchDisplayController中显示核心数据的结果时遇到问题。如果没有它,它可以工作,但显然会阻塞UI 在我的SearchTableViewController中,我有以下两种方法:Multithreading 使用GCD使用核心数据结果更新UISearchDisplayController,multithreading,core-data,objective-c-blocks,grand-central-dispatch,uisearchdisplaycontroller,Multithreading,Core Data,Objective C Blocks,Grand Central Dispatch,Uisearchdisplaycontroller,在实现GCD时,在UISearchDisplayController中显示核心数据的结果时遇到问题。如果没有它,它可以工作,但显然会阻塞UI 在我的SearchTableViewController中,我有以下两种方法: - (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString { //
- (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重新加载数据]
但是没有用。如果有人能给我指出正确的方向,告诉我哪里可能出错,我会非常感激
谢谢
编辑
我只是想澄清克里斯托弗的解决方案中的一些东西,以供将来参考
当我调用此方法时,我将GCD调用放到竞争块中的主队列。还要注意重新加载tableView的更改
范例
我想您不能使用搜索显示控制器的常规委托方法来更新UI。异步搜索完成后,您需要自己更新UI 首先,避免冗余。扔掉整个搜索结果,重新搜索是没有意义的。相反,您可以使用谓词过滤内存中已有的结果 其次,在
shouldReloadTableForSearchString
中,您应该返回NO
。相反,通过触发数据数组的更新来响应搜索字符串中的更改FilteredLocationArray
。(您可以直接执行此操作,而无需像现在这样创建3个不同的副本。)然后从异步进程内部更新主线程上的表。以下是模式:
dispatch_async(coreDataQueue, ^{
// fetch the data
dispatch_async(dispatch_get_main_queue(), ^{
[_tableView reloadData];
});
});
你有几个问题 首先,在异步回调中填充
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
块中,都应该将核心数据对象中需要的字段映射到普通值对象,并将它们传递到主线程。大概是这样的:
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];
});
为了将来的参考,这个问题的答案是相关的。谢谢。我以后再试试。顺便问一下,如果我的核心数据块在不同的类中,我是否应该让我的SearchTableViewController成为CoreDataMaster类的委托,这样我就可以调用[\u tableView reloadData]?是的,我想你可以。但是我更愿意让控制器控制模型和这个特定视图之间的逻辑。非常感谢您使用GCD的核心数据进行详细解释和整理我的困惑。
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];
});