私は、CS193P スタンフォード クラスから CoreDataTableViewController を拡張する UITableViewController を備えたかなり標準的なアプリを持っています (これは、NSFetchedResultsControllerDelegate ドキュメントのすべてのボイラープレート コードを使用して NSFetchedResultsControllerDelegate を実装する UITableViewController の単なる拡張です)。
とにかく、私のテーブルにはアイテムのリストが表示されます。アイテムposition
には、整数 (Core Data の NSNumber) であるというプロパティがあり、NSFetchedResultsController は、位置でソートするために NSSortDescriptor で設定されます。
これは通常は機能します。テーブルを開くと、performFetch が実行され、アイテムが正しい順序で表示されます。デバッグするログ メッセージをいくつか追加しました。
fetching Item with pedicate: parentList.guid == "123" and sort: (position, ascending, BLOCK(0x6bf1ca0))
fetched item: aaaaa has array:pos = 0 : 0
fetched item: bbbb has array:pos = 1 : 1
fetched item: cccc has array:pos = 2 : 2
fetched item: dddddd has array:pos = 3 : 3
最初の行が示しているのは、performFetch が、GUID でフィルタリングする述語と、位置でソートするソート記述子で発生していることです。次の行は、フェッチ後に NSFetchedResultsController から fetchedObjects をループするときにログに記録されます。最初に項目名 (aaaa、bbbb など) が表示され、次に fetchedObjects 配列内の配列位置、次に position プロパティの値が表示されます。ずらりと並んでいる様子がわかります。
問題は、新しいアイテムを追加してから親ビューに戻ってから、再びリストに進むときに発生します。新しいアイテムが正しい位置 (最後) に追加されます。しかし、前後に移動すると、いくつかのアイテムが順不同になっています。
最初は、フェッチが再度実行されていないか、sortDescriptor が欠落している可能性があると考えていましたが、ロギングはフェッチが行われていることを示しており、物事が順不同であることがわかります。
fetching Item with pedicate: parentList.guid == "123" and sort: (position, ascending, BLOCK(0x6bf1ca0))
fetched item: bbbb has array:pos = 0 : 1 <= BAD
fetched item: cccc has array:pos = 1 : 2 <= BAD
fetched item: aaaaa has array:pos = 2 : 0 <= BAD
fetched item: dddddd has array:pos = 3 : 3
fetched item: eeee has array:pos = 4 : 4
そこを参照してください: 配列項目 0 の位置が 1 で、配列項目 2 の位置が 0 で、配列項目 1 が 2 であることに注意してください。これは実際にはフェッチの直後にフェッチされたオブジェクトであり、fetchRequestcontroller の sortDescriptor と述語が明らかに正しいため、どうしてこれが可能なのでしょうか?
最初はテーブル ビューの問題かもしれないと思っていましたが、fetchObjects の後にこのデバッグ ログを追加したので、それがフェッチの結果であることがわかりました。
また、NSNumbers を自動的に並べ替えることができないのではないかと考えたので、整数値で並べ替える独自のコンパレータを追加しました。しかし違いはありません。
もう一度行ったり来たりすると、次のフェッチで正しい順序に戻されることに注意してください。後続のすべてのフェッチも同様です。ロード後に発生するのはこれだけです。
何か案は?
[アップデート]
コメントでの有益な議論の後 (興味を持ってくれた @MartinR と @tc に感謝します)、物事を少し単純化し、何が起こっているかを示すコードを追加しました。単純化:
- シンプルなNSStringであるため、アイテム「タイトル」でソートしています。
- 新しい項目を作成するために子 NSManagedObjectContext を使用しなくなりました - それらはリストと同じ MOC で直接作成され、すぐに (そして同期的に) 保存されます
デモ用のコードの追加: 基本的なセットアップは、アイテムのリストのリストです。Todo アプリの標準的なもの。したがって、私の CoreData Model にはリストとアイテムが含まれています。すべてのリストにはアイテムのセット (1 から多数) があり、すべてのアイテムには親リストへの参照があります。
UI は 2 つの TVC: ListOfListsTVC で、リスト名をクリックすると、ListOfItemsTVC に移動します。
アイテムのリストは異なるリストに使用されるため、新しいリストが設定されるたびに完全に新しい FRC が設定されます。それはここで起こります:
- (void)setupFetchedResultsController
{
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"item"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"parentList.guid = %@", self.list.guid];
request.predicate = predicate;
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"title" ascending:YES];
request.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:self.list.managedObjectContext sectionNameKeyPath:nil
cacheName:nil];
self.debug = YES;
}
これself.fetchedResultsController
は、cs193p スタンフォード コースからの逐語的な CoreDataTableViewController スーパークラスを呼び出します。
- (void)setFetchedResultsController:(NSFetchedResultsController *)newfrc
{
self.debug = YES;
NSFetchedResultsController *oldfrc = _fetchedResultsController;
if (newfrc != oldfrc) {
_fetchedResultsController = newfrc;
newfrc.delegate = self;
if ((!self.title || [self.title isEqualToString:oldfrc.fetchRequest.entity.name]) && (!self.navigationController || !self.navigationItem.title)) {
self.title = newfrc.fetchRequest.entity.name;
}
if (newfrc) {
if (self.debug) NSLog(@"[%@ %@] %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), oldfrc ? @"updated" : @"set");
[self performFetch];
} else {
if (self.debug) NSLog(@"[%@ %@] reset to nil", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
[self.tableView reloadData];
}
}
}
主にデバッグ情報ですが、重要なステートメントは次のとおりです。 1. プロパティを設定します。2. delegate
FRC をこの TVC (自己) に設定し、3. 即時フェッチを実行します。
performFetch
は同じ CoreDataTableViewController クラスにあり、上にリストしたすべてのデバッグ情報 (アイテムの名前、fetchedObjects 配列内の位置、および位置の値) をダンプします。(これは実際にはリストをフェッチする一般的なメソッドですが、追加のデバッグ情報を取得するために Item クラスのテストを入れています) ここではリストしませんが、重要なステートメントは次のとおりです。
NSError *error;
[self.fetchedResultsController performFetch:&error];
// Debug:
NSArray *obs = [self.fetchedResultsController fetchedObjects];
// log all the debug info about the items in the fetched array to prove they're not sorted
// ...
[self.tableView reloadData];
したがって、基本的には、テーブルをフェッチしてリロードします。
データにアイテムのリストがある場合、これはアプリを最初に起動したときに機能するようです。失敗しているように見えるのは、新しいアイテムを追加した後です。NewItemTVC という別の静的 TVC でこれを行い、デリゲートの代わりに、ブロック内のコールバックを使用してアイテムを保存します。しかし、効果は同じです。すべて同期です。これがListOfItemsTCVに保存するための私のブロックです
newItemTVC.saveCancelBlock2 = ^ (BOOL save, NSDictionary *descriptor) {
if (save) {
NSManagedObjectContext *moc = self.fetchedResultsController.managedObjectContext;
Item *newItem = [Item itemWithDescriptor:itemDescriptor inManagedObjectContext:moc];
// here's where I set the position but ignore this for now because
// I'm sorting on "title" to debug and it has the same problem
NSInteger newPosition = self.list.lastPosition + 1;
newItem.position = [NSNumber numberWithInteger:newPosition];
// and finally add it to the list
[self.list addItemsObject:newItem];
NSError *error = nil;
BOOL saved = [moc save:&error];
if (!saved) {
NSLog(@"Unresolved error saving after adding item to parent %@, %@", error, [error userInfo]);
}
}
ここで、アイテムを保存した後、NewItemTVC がポップし、ListOfItems がリロードされ、フェッチが実行され、正しい順序になることがありますが、通常はそうではありません。この場合、フェッチは viewWillAppear で実行されます。(以前はそうではありませんでしたが、デバッグ中にこれも追加しました。現在、viewWillDisappear はデリゲートを nil に設定し、NewItemTVC をポップすると、FRC のデリゲートを TVC に戻した後、このコードが新しいフェッチを実行します)これは、リストのリストから進むときにデリゲートを設定したり、フェッチを実行したりしないことに注意してください。これは、list プロパティを設定すると、デリゲートが設定され、フェッチが実行されるためです。したがって、実際には、NewItemTVC をポップして、viewWillAppear でフェッチを実行することから返されるのは、並べ替えが正しく表示されない最初の例です。
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (self.fetchedResultsController != nil && self.fetchedResultsController.delegate != self) {
self.fetchedResultsController.delegate = self;
[self performFetch];
[self.tableView reloadData];
}
}
本当にうまくいかないのは、[戻る] を押して ListOfListsTVC を表示し、もう一度リストを押して同じ ListOfItemsTVC に戻るときです (リストが 1 つでも 12 個でもかまいません)。初めてこれを行うと、アイテムは常に故障しています。時にはそれを 4、5 回繰り返すことができますが、それでも順番が狂いますが、何度も行ったり来たりした後、最終的には順番が整い、そのままになります。
これは、位置の代わりにアイテムの「タイトル」を使用しているため、(サニタイズされた) デバッグ情報です。
[808:fb03] [ListOfItemsTVC performFetch] fetching Item with pedicate: parentList.guid == "DD1E1F25-BFC9-46B9-A637-109C0D6F0D1D" and sort: (title, ascending, compare:)
[808:fb03] fetched item: ccccc in array at index 0
[808:fb03] fetched item: aaaaa in arrat at index 1
[808:fb03] fetched item: bbbbb in array at index 2
数回後戻りすると、aaaa、bbbb、ccccc という正しい順序に落ち着きます。ソートが壊れているか、FRCが壊れているようです。