Delegateのメソッドはいつ呼ばれるのか?
- controller:didChangeObject:atIndexPath:forChangeType:newIndexPath: は、NSManagedObjectContext に変化があった時に呼ばれる。
次のケースを想定してみる。
UINavigationController を使っていて、一覧画面から詳細画面へ移動してそこで NSManagedObjectContextに操作を加える。
ListViewController <NSFetchedResultsControllerDelegate> ⇒ (参照) UITableView* tableView ↓ DetailViewController ← NSManagedObjectContext操作(変更・削除など)すると ListViewController の -controller:didChangeObject:atIndexPath:forChangeType:newIndexPath: が呼び出される。この時点ではまだ DetailViewController が表示されているものとする。
この Delegateメソッド内では通常 tableViewに対して操作を行っている。
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath {
UITableView *tableView = self.tableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
:上記のケースでメモリ不足が発生して ListViewController の viewが 開放された場合、self.tableView へのアクセスが安全かどうかが気になる。
ListViewController.view が UITableView の場合
通常 ListViewController は UITableViewController のサブクラスとなる。UITableViewの管理は親クラスの tableViewインスタンスで管理される。
このケースは問題ない。
流れとしては次のようになる。
DetailViewController表示 ↓ メモリ不足発生 ↓ ListViewController で viewDidUnload が呼び出され、view(tableView) が開放される ↓ DetailViewController で NSManagedObjectContext を操作 ↓ ListViewController で controllerWillChangeContent: が呼び出される。 この中で self.tableView を参照。 ↓ self.tableViewへのアクセスをトリガーにして、ListViewController で viewDidLoad が呼び出される。これによって self.tableView の準備が完了する ↓ controllerWillChangeContent: の処理を実行 ↓ ListViewController の -controller:didChangeObject:atIndexPath:forChangeType: newIndexPath:呼び出しself.tableView への操作が無事に行われる ↓ ListViewController へ戻ると、変更が反映された表が表示されている。
ListViewController.view が UIView の場合
UIView の上に UITableView が載っているケース。この場合、ListViewController は UIViewController のサブクラスで tableView を定義して自前で管理する。
@interface RootViewController : UIViewController
<NSFetchedResultsControllerDelegate> {
UITableView* tableView_;
}
@property (nonatomic, retain) IBOutlet UITableView* tableView;
@end
@implementation RootViewController
:
- (void)viewDidUnload {
[super viewDidUnload];
self.tableView = nil;
}
:結論からすると、このケースも実質問題がない。
DetailViewController表示 ↓ メモリ不足発生 ↓ ListViewController で viewDidUnload が呼び出され、view が開放される ↓ この時 tableView も開放される(self.tableView=nil) ↓ DetailViewController で NSManagedObjectContext を操作 ↓ ListViewController の -controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:呼び出し ↓ self.tableViewに対してメッセージを送るが nil の為、何も起こらない ↓ ListViewControllerへ戻る ↓ viewが再ロードされ viewDidLoad が呼び出される。 UIViewControllerが参照するビューが芋づる式にロードされる。 ↓ UITableView は新規表示になるのでデータは最新のものを読み直し。 この結果、正しいデータが表示される。
ソースコード
両方のケースを試せる。ただし切り替えは手作業が少々必要(現在は1番目のケースで動作する)。
NSFetchedResultControllerDelegateSample at 2010-07-28x from xcatsan's iOS-Sample-Code - GitHub
関連情報
Cocoaの日々: NSFetchedResultsControllerDelegate を使う



Responses
Leave a Response