Cocoa

awakeFromNibの落し穴


awakeFromNibはnibファイルがアプリケーションに読み込まれ、nibファイルに登録されたのオブジェクト間のインスタンス変数(IBOutlet)の自動接続が終了すると送信されるメッセージです。
ここでは、この便利なawakeFromNibを使って見事に引っかかった落し穴についてのおはなしです。

ヘッダーファイル : NSNibLoading.h
クラス : NSObject(NSNibAwaking)

-(void)awakeFromNib

ヘッダーファイル : NSNibLoading.h
クラス : NSBundle(NSNibLoading)

+(BOOL)loadNibNamed:(NSString*)nibName owner:(id)owner

この2つのメソッドの組み合わせによって落とし穴が完成します。


loadNibNamed:ownerとawakeFromNibの関係

loadNibNamedでnibファイルをオープンするとnibファイルに含まれるオブジェクトが生成されてメソッド間のインスタンス変数の接続関係を設定してくれます。そして、最後の仕上げとして、nib内のすべてのオブジェクトに対して準備ができたよというメッセージawakeFromNibが送られます。
loadNibNamedの他にもnibをロードするメソッドが準備されていますが、ここでは、代表してこのメソッドで説明します。
アプリケーション本体のnibがオープンされた際のメッセージはこのようになっています。
initメソッドによりオブジェクトが初期化された後で、awakeFromNibメッセージが送られてきます。

init
awakeFromNib
applicationWillFinishLaunching
applicationWillUpdate
applicationDidUpdate
applicationDidFinishLaunching
applicationWillBecomeActive
applicationDidBecomeActive
applicationWillUpdate
applicationDidUpdate
Appdelegateの初期化
Nibファイルが開かれ、使用準備が完了
アプリケーション起動終了予告通知
アプリケーション更新予告通知
アプリケーション更新完了通知
アプリケーション起動終了の完了通知
アプリケーションアクティブ化の予告通知
アプリケーションアクティブ化の完了通知
アプリケーション更新予告通知
アプリケーション更新完了通知
注)上記メッセージの流れは、mainMenuNibにカスタムオブジェクトAppdelegateを実装し、そのオブジェクトをNSApplicationのdelegateに設定した場合のものです。



awakeFromNibの使用上の注意点(落とし穴に落ちないために)

awakeFromNibはnibファイルがオープンされた際に1度だけ呼び出されるメソッドです。
これは、これで正しいのですが、1つのオブジェクトに1度だけ送信されるメッセージではないという事に注意が必要です。
つまり
あるオブジェクトAが、Nibファイルのオープンにより実体化された際にawakeFromNibメッセージを一度受け取ります。
続いて、そのオブジェクトAが別のnibファイル(例えばダイアログとか)をオープンする際に、loadNibNamedメソッドのownerに自分自身を指定すると・・・
先ほど書いたようにnib内のすべてのオブジェクトに対して準備ができたよというメッセージawakeFromNibが送られるので、filesOwnerにもメッセージが送信される事になります。
つまり、このオブジェクトAは、自信が実体化された場合のほかに、自信がオープンしたnibファイルが実態化するごとに複数回awakeFromNibメッセージを受け取る事になります。

したがって、awakeFromNibメソッド内では、データの初期化などはしない方が賢明だという事です。

これをやってしまうと、アプリケーションは正しく動作するものの、ダイアログなどを表示した瞬間にデータがリセットされるトラブルが発生します。


簡単なモデルでの検証

早速、簡単なモデルを作って動作を検証してみょう。
Cocoaプログラムのモデルを使ってMainManuNibのwindow にボタンを1個置きます。
そして、そのボタンのアクションを受け取るためのbundleLoaderオブジェクトを追加します。

・MainMenu.nibのwindowにボタン"load dialogNib"を1個追加します。
・NSObjectのサブクラス"bundleLoader"を追加します。

続いて新しいnibファイルを作ります。
・プロジェクトに新規nibファイル"dialog.nib"を追加します。
・このnibファイルにwindowを追加し、メッセージviewを追加します。

・bundleLoaderオブジェクトにアクションターゲットdoLoadBundleメソッドとawakeFromNibメソッドを実装します。
・MainMenu.nibのウィンドーに追加したボタンのターゲットをdoLoadBundleに指定します。
そしてbundleLoaderoオブジェクトの次のようなメソッドを実装します。

@implementation bundleLoader

 -(IBAction)doLoadBundle:(id)sender
   {
    [NSBundle loadNibNamed:@"dialog" owner:self];
   }

 -(void)awakeFromNib
  {
    NSLog(@"bundleLoader waked");
  }

@end   

上のプログラムを実行し、load dialogNibボタンをクリックすると、filesOwnerにもawakeFromNibメッセージが届いている事がわかります。



(C) 2007 Cocoa探検隊