使用GCD更新带有Core Data结果的UISearchDisplayController

时间:2012-11-26 23:01:36

标签: multithreading core-data objective-c-blocks grand-central-dispatch uisearchdisplaycontroller

当我实现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];
    });

}];

2 个答案:

答案 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];
});