NSTableViewDelegate利用時にArrayControllerへオブジェクトが追加されない事象
注意事項
NSTableViewDelegate で TableView のクリックイベントを取得しようとしたら、ArrayController にオブジェクトが追加されない事象に悩まされたのでメモ。
結論は、awakeFromNibでメンバ変数の初期化をしていたことが原因。
■事象
ArrayController を利用して、AppController が持つ配列を NSTableView に表示していた。
そこに NSTableViewDelegate を使ってテーブルのクリックを取得するため、NSTableView から AppContorller へ delegate を接続したところ、ArrayController に add しても配列にオブジェクトが追加されなかった(1つ目だけは表示される)。
addしても1行目しか表示されないテーブルと、add時のエラーコード
*** -[__NSArrayM insertObject:atIndex:]: index 1 beyond bounds for empty array と怒られているように、表示上1行目だけはデータがあるように見えるが、実際には配列の中にオブジェクトは追加されていない。
配列へのオブジェクト追加は、テーブル下の+ボタンで ArrayController に add を送って追加している。
なお、NSTableView → AppController の delegate 接続を切ると、ちゃんと配列に追加され、表示も反映される。
AppController.h
#import <Foundation/Foundation.h> #import <Cocoa/Cocoa.h> @interface AppController : NSObject <NSTableViewDelegate> @property (strong) NSMutableArray *testArray; - (NSIndexSet *)tableView:(NSTableView *)tableView selectionIndexesForProposedSelection:(NSIndexSet *)proposedSelectionIndexes; @end
AppController.m (問題のあるコード)
#import "AppController.h" #import "Contents.h" @implementation AppController @synthesize testArray = _testArray; - (void) awakeFromNib { _testArray = [[NSMutableArray alloc] initWithCapacity:15]; } - (NSIndexSet *)tableView:(NSTableView *)tableView selectionIndexesForProposedSelection:(NSIndexSet *)proposedSelectionIndexes { return proposedSelectionIndexes; } @end
事象を追っていくと、+ボタンを押したとき、追加されるオブジェクトは生成されていたが、配列の数に変化はなかった。さらに追っていくと、ArrayContorller に add する度、AppContorller の awakeFromNib が呼ばれていることが分かった。
上記のコードの通り、awakeFromNib で 配列である testArray を初期化している。つまり add する度に testArray が初期化されて常に配列が0になっていたため、おかしなことになっていた。
元々 awakeFromNibメソッドで初期化していたのは、Nibからロードされた時の1度しか呼ばれないという思い込みがあって、かつinitメソッドを書くよりかは(若干)書く量が少なくて楽という理由だった。が、awakeFromNib が複数回呼ばれることがあるのであれば、awakeFromNibで初期化するのは止めるべきだろう。
なぜ NSTableViewDelegate を利用すると awakeFromNib が複数回呼ばれるようになるのかは不明。
なので、正解はこれ。
AppController.m (修正版)
#import "AppController.h" #import "Contents.h" @implementation AppController @synthesize testArray = _testArray; - (id)init { self = [super init]; if(self) { _testArray = [[NSMutableArray alloc] initWithCapacity:15]; } return self; } - (NSIndexSet *)tableView:(NSTableView *)tableView selectionIndexesForProposedSelection:(NSIndexSet *)proposedSelectionIndexes { return proposedSelectionIndexes; } @end
■まとめ
awakeFromNib で 変数の初期化はしない
ヒレガス本(第3版)を見返すと、awakeFromNib でテキストフィールドの値の初期化をしていた。テキストフィールドの値の初期化なら問題はないが、これを見て「何でもawakeFromNibで初期化したらいいんや!」と勘違いした可能性がある。