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