0

我在執行GCD時在我的UISearchDisplayController中顯示來自Core Data的結果時遇到問題。沒有它,它可以工作,但顯然會阻止用戶界面。使用GCD核心數據結果更新UISearchDisplayController

在我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

以供將來參考回答這個問題是相關的http://stackoverflow.com/questions/3903718/uisearchdisplaycontoller-cant-prevent-table-reload-on-typing-in-search-酒吧/ 5342989#5342989。 –

回答

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塊中,都應將您需要的字段映射到核心數據對象,並將其傳遞給主線程。事情是這樣的:

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]; 
}); 
+0

非常感謝您使用Core Data和GCD進行詳細的解釋和整理我的困惑。 –

1

我想你不能使用搜索顯示控制器的通常委託方法來更新用戶界面。在異步搜索完成後,您需要自行更新UI。

首先,避免冗餘。丟棄整個搜索結果並重新搜索是沒有意義的。而是你可以用謂詞過濾你已經擁有的結果。

其次,在shouldReloadTableForSearchString您應該返回NO。相反,通過觸發更新數據陣列filteredLocationsArray來對搜索字符串中的更改作出反應。 (您可以直接執行此操作,而無需像現在這樣創建3個不同的副本。)然後從異步過程內更新主線程上的表。這裏是架構:

dispatch_async(coreDataQueue, ^{ 
    // fetch the data 
    dispatch_async(dispatch_get_main_queue(), ^{ 
     [_tableView reloadData]; 
    }); 
}); 
+0

謝謝你。我會稍後再嘗試。順便說一句,如果我的核心數據塊在不同的類中,我應該讓我的SearchTableViewController成爲CoreDataMaster類的委託,這樣我可以調用[_tableView reloadData]? –

+0

是的,你可以,我想。但我寧願讓Controller控制Model和這個特定View之間的邏輯。 – Mundi