0

GCDを実装すると、UISearchDisplayControllerにCoreDataの結果を表示できません。これがないと機能しますが、明らかにUIがブロックされます。

SearchTableViewControllerには、次の2つのメソッドがあります。

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

}

私の問題は、[coreDataMasterfetchResultsFromCoreDataEntity]から配列を返すことにあると推測できます。以下はその方法です。

- (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に返すまでに実行を終了していません。NSDictionaryをこのメソッドに渡すNSNotificationを追加してみました。

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

}];
4

2 に答える 2

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 に答える
1

検索ディスプレイコントローラーの通常のデリゲートメソッドを使用してUIを更新することはできないと思います。非同期検索が終了したら、自分でUIを更新する必要があります。

まず、冗長性を避けます。検索結果全体を破棄して、新たに検索することは意味がありません。むしろ、述語を使用して、メモリにすでにある結果をフィルタリングできます。

第二に、shouldReloadTableForSearchStringあなたは戻る必要がありますNO。代わりに、データ配列の更新をトリガーして、検索文字列の変更に対応しますfilteredLocationsArray。(現在行っているように3つの異なるコピーを作成せずに、これを直接行うことができます。)次に、非同期プロセス内からメインスレッドのテーブルを更新します。スキーマは次のとおりです。

dispatch_async(coreDataQueue, ^{
   // fetch the data
   dispatch_async(dispatch_get_main_queue(), ^{
       [_tableView reloadData];
   });
});
于 2012-11-27T10:49:12.020 に答える