Multithreading 使用GCD使用核心数据结果更新UISearchDisplayController

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 { //

在实现GCD时,在UISearchDisplayController中显示核心数据的结果时遇到问题。如果没有它,它可以工作,但显然会阻塞UI

在我的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重新加载数据]

但是没有用。如果有人能给我指出正确的方向,告诉我哪里可能出错,我会非常感激

谢谢

编辑

我只是想澄清克里斯托弗的解决方案中的一些东西,以供将来参考

当我调用此方法时,我将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];
});