Interface Builderを使わずにMacアプリケーション作成 - nib検証篇 5
NSApplicationMain()関数を使わないで本当に大丈夫か
大丈夫なはずがない(13/4/24追記)
NSApplicationMain()
関数を使わずにアプリケーションを起動させること自体は可能だ。しかしその行為には全く意味が無い。Interface Builderによって作成するxibファイル、そしてコンパイルして作成されるnibファイル、これらはウインドウやボタンの配置だけを定義しているものではない。
nibはソースコードでもプログラムでもなく、インスタンスそのもののアーカイブである。これをNSApplicationMain()
関数内で解凍し使える状態に戻している。自身で作成したコントローラサブクラスやDelegateクラスのインスタンスも、このタイミングで解凍されるのだ。
ということは、これらのクラスのインスタンスを使用する場合、必然的にnibのロード作業(解凍)を行なうことになる。さんざんNSApplicationMain()
関数を使わない方法を模索しても、このnibロードは回避しようがないのだ。
以下の内容は掲載当時、nibの必要性についての検証が足りなかった時点のものである。NSApp
変数のメモリ参照などを検証したので、検証内容はそのまま載せておく。
NSAppのRetain count
NSApplicationMain()
関数は、NSApplication
のクラスメソッド+ sharedApplication
を呼び出していると説明されている。このとき作成されたオブジェクトは、NSApp
グローバル変数に代入され保持される。アプリケーションの起動直後にこのNSApp
のRetain countを調べてみた。
アプリケーション起動直後の処理ということで、今回はAppDelegate
クラス(Xcodeが最初に作るもの)を使い、- applicationDidFinishLaunching:
内で調べる。delegateの接続はInterface Builder上で行った。
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
NSLog(@"applicationDidFinishLaunching:");
NSLog(@"Retain count: %d", (int)CFGetRetainCount((__bridge CFTypeRef)NSApp));
}
ARC利用時に- retainCount
メソッドは使えないが、NSApp
をCore Foundation
オブジェクトにキャストすることで同様の操作が行える。なお__bridge
修飾子は、使ってもRetain countは変わらず所有権も移行しない。
(__bridge CFTypeRef)NSApp // Core Foundation オブジェクトとしてキャスト
NSApplicationMain()関数を使用
int main(int argc, char *argv[])
{
return NSApplicationMain(argc, (const char **) argv);
}
この状態で実行してみよう。まずはNSApplicationMain()
関数を使ったとき。
2013-04-22 12:17:58.996 withoutIB[4341:303] applicationDidFinishLaunching:
2013-04-22 12:17:58.997 withoutIB[4341:303] Retain count: 3
カウントは3と表示された。
書き換えmain()関数を使用
int main(int argc, char *argv[])
{
@autoreleasepool {
[NSApplication sharedApplication];
[NSBundle loadNibNamed:@"MainMenu" owner:NSApp];
[NSApp run];
}
return EXIT_SUCCESS;
}
続いて書き換えmain()
関数。ここで前回との違いとして、@autoreleasepool
ブロックで囲っている。これは他の資料やAppleリファレンスを読み考えたことなのだが、[NSApp run]
でイベントループ内にAuto release poolが用意されるものの(リファレンス参照)、外には何も無いままだったからだ。
2013-04-22 12:18:26.209 withoutIB[4375:303] applicationDidFinishLaunching:
2013-04-22 12:18:26.210 withoutIB[4375:303] Retain count: 3
結果は同じく3となった。
NSLogを増やしRetain countを追う
せっかく1行ずつ分解できるので、さらに細かく調べてみる。
int main(int argc, char *argv[])
{
@autoreleasepool {
NSLog(@"Retain count: %d", (int) CFGetRetainCount((__bridge CFTypeRef) NSApp));
[NSApplication sharedApplication];
NSLog(@"Retain count: %d", (int) CFGetRetainCount((__bridge CFTypeRef) NSApp));
[NSBundle loadNibNamed:@"MainMenu" owner:NSApp];
NSLog(@"Retain count: %d", (int) CFGetRetainCount((__bridge CFTypeRef) NSApp));
[NSApp run];
}
return EXIT_SUCCESS;
}
1行ずつNSApp
のRetain countを確認するためNSLog
を挿入した。ただし上記のまま実行するとSIGKILLが発生してしまう。[NSApplication sharedApplication]
より前で空のNSApp
変数を参照しているからで、これは期待通りの動作だ。
問題の行は削除するとして、再度実行。
2013-04-22 12:26:57.434 withoutIB[4711:303] Retain count: 1
2013-04-22 12:26:57.505 withoutIB[4711:303] Retain count: 2
2013-04-22 12:26:57.593 withoutIB[4711:303] applicationDidFinishLaunching:
2013-04-22 12:26:57.594 withoutIB[4711:303] Retain count: 3
わかりやすい数字が出た。+ sharedApplication
、これは+ alloc
に相当する処理を行なっているので1増加。nibを読み込み画面上に描画する時点で保持し1増加、最後にAppDelegate
クラスによって保持され1増加、合計3である。
NSApplication
のクラスリファレンスに書かれた概要の通りとなったようだ。
課題
検証でnibは今後必須と発覚した。そんなnibの読み込みについて、裏ではどういう処理をしているのか。
次回、NSApplicationMain()
関数の処理の詳細について検証する。
Discussion