NSFetchedResultsControllerDelegate - メモリ管理に関するメモ

2010年7月28日水曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

NSFetchedResultsController を使っていて、NSFetchedResultsControllerDelegate を実装している時のメモリ管理に関する私的メモ。


Delegateのメソッドはいつ呼ばれるのか?


- controller:didChangeObject:atIndexPath:forChangeType:newIndexPath: は、NSManagedObjectContext に変化があった時に呼ばれる。

次のケースを想定してみる。

UINavigationController を使っていて、一覧画面から詳細画面へ移動してそこで NSManagedObjectContextに操作を加える。
ListViewController &ltNSFetchedResultsControllerDelegate> ⇒ (参照) 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 へ戻ると、変更が反映された表が表示されている。

UITableViewController を使う場合、self.view と self.tableView は等価となる。このため self.tableViewに対するアクセスによって、self.view に仕掛けられた KVO機構が発動して UITableViewがロードされ self.tableView で使えるようになると思われる(※これは推測)。=> KVOなんて使わなくて単なる getterメソッドの実装でそうなっている、と思われる。


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

人気の投稿(過去 30日間)