🍎

Interface Builderを使わずにMacアプリケーション作成 - nib検証篇 4

2020/09/20に公開

main()関数を書き換える

アプリケーションを起動するとき、最初に実行されるのがmain()関数であることはnib検証篇 1にて解説した。そしてその中身NSApplicationMain()関数がアプリケーション・インスタンスの生成(起動)、nibのロード(ウインドウ等の表示)、メインループ開始(ユーザ操作を受け付け開始)という一連の処理を行なっている。

NSApplicationMain()
void NSApplicationMain(int argc, char *argv[])
{
    [NSApplication sharedApplication];
    [NSBundle loadNibNamed:@"myMain" owner:NSApp];
    [NSApp run];
}

上のコードはNSApplicationクラスリファレンスより引用した概要で、実際この通り実装されているわけではない。

+ loadNibNamed:owner:NSBundleのクラスメソッドだが、これはOS X 10.8 Deprecatedとなっている。10.8からは- loadNibNamed:owner:topLevelObjects:というインスタンスメソッドを使う必要がある。

NSApplicationMain()関数(の概要)の中身をmain()関数にそのまま記述すれば同様の動作ができるのか? というのが今回のテーマ。

+ loadNibNamed:owner:を使う

main()
int main(int argc, char *argv[])
{
    [NSApplication sharedApplication];
    [NSBundle loadNibNamed:@"MainMenu" owner:NSApp];
    [NSApp run];
    return EXIT_SUCCESS;
}

単純ではあるが、main()関数の中身を書き換えた。戻り値の型はintなのでEXIT_SUCCESSを返している。読み込むMainMenu.xibはXcodeのプロジェクトに最初から作成されているものだ。

この状態でビルド。

起動した。ウインドウも表示されメニューバーには一連のメニューが並ぶ。ショートカットキーも受け付け、フォントパネルやカラーパネルも問題なく開いた。NSApplicationMain()関数を使ったときと変わらない印象だ。

- loadNibNamed:owner:topLevelObjects:を使う

続いて10.8に対応できるよう、Deprecatedなメソッドを交換して書いてみた。

main()
int main(int argc, char *argv[])
{
    [NSApplication sharedApplication];
    NSBundle *bundle  = [NSBundle mainBundle];
    NSArray  *objects = [NSArray new];
    [bundle loadNibNamed:@"MainMenu" owner:NSApp topLevelObjects:&objects];
    [NSApp run];
    return EXIT_SUCCESS;
}

- loadNibNamed:owner:topLevelObjects:topLevelObjects引数は(NSArray **)となっている。ここにNSArray型のポインタのアドレスを指定し、MainMenu.nibの読み込みが成功したらトップレベルオブジェクトの配列を返すという仕組み。メソッド自体の戻り値型はBOOLだ。

この内容でビルドする。

ビルドは通った、しかし即座にsignal SIGABRTが発生。

console
withoutIB[10732:303] -[NSBundle loadNibNamed:owner:topLevelObjects:]: unrecognized selector sent to instance 0x100637020

unrecognized selector? 筆者の環境はMac OS X 10.7.5で、考えてみるとOS X 10.8からの新しい実装は動くはずも無い。ということで10.8を準備し、10.7と10.8の両方で検証した。

10.8では結果は成功。クラッシュせず無事に起動し、ちゃんとnibを読み込んだことがわかった。このときのtopLevelObjectsを調べてみると次のようになる。

console
withoutIB[474:303] (
    "<NSApplication: 0x10011c4a0>",
    "<NSWindow: 0x100184a30>",
    "<AppDelegate: 0x100523500>",
    "<NSFontManager: 0x100523f40>",
    "<NSMenu: 0x10051eb90>\n\tTitle: AMainMenu\n\tSupermenu: 0x0 (None), autoenable: YES\n\tItems:     (\n        \"<NSMenuItem: 0x10051dc60 test, submenu: 0x1005160a0 (test)>\",\n        \"<NSMenuItem: 0x10051f4f0 File, submenu: 0x100518ac0 (File)>\",\n        \"<NSMenuItem: 0x10051fb00 Edit, submenu: 0x100519830 (Edit)>\",\n        \"<NSMenuItem: 0x100520140 Format, submenu: 0x1005202c0 (Format)>\",\n        \"<NSMenuItem: 0x1005205d0 View, submenu: 0x100519b60 (View)>\",\n        \"<NSMenuItem: 0x100520710 Window, submenu: 0x100515f60 (Window)>\",\n        \"<NSMenuItem: 0x1005209a0 Help, submenu: 0x10051b3f0 (Help)>\"\n    )"
)

ここでNSButtonなどを追加しても特に変化は無かった。topLevelObjectsの名が示す通り、NSView以下のオブジェクトについては含まれないようである。なお、今回のテーマは"Interface Builderを使用しない"なので、今後nibの読み込みもせず、topLevelObjectsも登場しないということで深くは追求しない。

疑問

main()関数を書き換え、NSApplicationMain()関数と同等の処理を行なうと起動することが確認できた。しかしNSApplicationMain()関数を使わないことで不都合は発生したりしないだろうか? *1

次回main()関数を書き換えた場合とNSApplicationMain()関数を使った場合の差異について検証していく。

続きます。


注釈

*1: 掲載当時NSApplicationMain()関数の必要性についての検証が不十分な状態での仮説です。現在は、NSApplicationMain()関数は必要と結論が出ていますのでご了承ください。(13/4/24追記)

リンク

Discussion