0

I'm having trouble displaying the results from Core Data in my UISearchDisplayController when I implement GCD. Without it, it works, but obviously blocks the UI.

In my SearchTableViewController I have the following two methods:

- (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]];

}

You can probably guess that my problem is with returning the array from [coreDataMaster fetchResultsFromCoreDataEntity]. Below is the method:

- (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];
}

So the thread hasn't finished executing by the time it returns the results to self.filteredLocationsArray. I've tried added a NSNotification which passes the NSDictionary to this method:

- (void)updateSearchResults:(NSNotification *)notification
{
    NSDictionary *userInfo = notification.userInfo;
    NSArray *array = [userInfo objectForKey:@"results"];

    self.filteredLocationsArray = [NSMutableArray arrayWithArray:array];

    [self.tableView reloadData];
}

I've also tried refreshing the searchViewController like

[self.searchDisplayController.searchResultsTableView reloadData];

but to no avail. I'd really appreciate it if someone could point me in the right direction and show me where I might be going wrong.

Thanks

Edit

I just want to clarify something in Christopher's solution for future reference.

When I call this method, I put the GCD call to the main queue in the competition block. Also note the change to reload the tableView.

Example

[coreDataMaster fetchResultsFromCoreDataEntity:(NSString *)entity usingPredicate:(NSPredicate *)predicate completionHandler:^(NSArray *cities){

    dispatch_async(dispatch_get_main_queue(), ^{
        self.filteredLocationsArray = cities;
        [self.searchDisplayController.searchResultsTableView reloadData];
    });

}];
4

2 回答 2

1

我想您不能使用搜索显示控制器的常用委托方法来更新 UI。异步搜索完成后,您需要自行更新 UI。

首先,避免冗余。丢弃整个搜索结果并重新搜索是没有意义的。相反,您可以使用谓词过滤内存中已有的结果。

其次,在shouldReloadTableForSearchString你应该 return NO。相反,通过触发数据数组的更新来响应搜索字符串的变化filteredLocationsArray。(您可以直接执行此操作,而无需像现在那样创建 3 个不同的副本。)然后从异步进程内部更新主线程上的表。这是架构:

dispatch_async(coreDataQueue, ^{
   // fetch the data
   dispatch_async(dispatch_get_main_queue(), ^{
       [_tableView reloadData];
   });
});
于 2012-11-27T10:49:12.020 回答
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);
    });
}

其次,您将核心数据对象从一个线程传递到另一个线程。Core Data 不是线程安全的,这样做几乎肯定会导致看似随机且很难重现的崩溃——我已经看到了。在后台线程的某个地方,无论是在块内部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];
});
于 2012-11-27T19:11:18.810 回答