Interface Builderを使わずにMacアプリケーション作成 - nib検証篇 6
結論 NSApplicationMain()関数は必要
今回の題から"Interface Builderを全く使わない"の__全く使わない__を取り下げた。誠に申し訳ないことに、記事執筆中の調査でNSApplicationMain()
関数およびnibの必要性が確定してしまったからだ(本シリーズは調査と資料作成の同時進行を特徴としている)。ということで、今回から題を"Interface Builderを使わずにMacアプリケーション作成"に改め、テーマもnib検証篇とした。
NSApplicationMain()関数を使わずにアプリケーションを起動
NSApplicationMain()
関数を使わずにアプリケーションを起動させること自体は可能だ。
int main(int argc, char *argv[])
{
[[NSApplication sharedApplication] run];
return EXIT_SUCCESS;
}
究極的にはこのコードだけで「アプリケーションは起動」する。しかしその行為には全く意味が無い。Interface Builderによって作成するxibファイル、そしてコンパイルして作成されるnibファイル、これらはウインドウやボタンの配置だけを定義しているものではない。
nibはソースコードでもプログラムでもなく、インスタンスそのもののアーカイブデータである。これをNSApplicationMain()
関数内で解凍し使える状態に戻している。自身で作成したコントローラサブクラスやDelegateクラスのインスタンスも、このタイミングで解凍されるのだ。
ということは、これらのクラスのインスタンスを使用する場合、必然的にnibのロード作業(解凍)を行なうことになる。さんざんNSApplicationMain()
関数を使わない方法を模索しても、このnibロードは回避しようがないのだ。
Document-Based Applicationで困ることに
シングルウィンドウのアプリケーションの場合、本シリーズが目指していたInterface Builderを__全く使わない__作成は、達成できることがわかっている。困るのはDocument-Based Applicationの作成である。
Document-Based Applicationについては、いずれ今後の章で扱いたいと思っているが、Appleの提供するドキュメントアーキテクチャの主要クラスNSDocumentController
やNSWindowController
ではnibの使用を回避して実装するリスクがとても大きいようだ。
OSの設計とも密接なこのアーキテクチャを安易に模倣して実装してしまうと、その時は動いてもその後のOSのバージョンアップで毎度模倣し直す必要が出てくる。これは、当初の目的だったInterface Builderを使用しないメリットを大幅に上回るデメリットだ。
そのため、Interface Builderを「全く使わない」までは無理としても、使わずに効率的に進める方法についての研究は続けるとして本シリーズを継続していく。
nibのロードでは何が動いているか
今後もnibとうまく付き合っていくとして、その処理周りを調べてみたい。NSApplicationMain()
関数の処理概要は公開されているが、実装コードを覗けない以上、ここを調べることができるのはXcodeに付属の高機能プロファイラ"Instruments"である。
InstrumentsのTime Profilerを使い取得したSample Listが以下のものである。かなり冗長になるので概要として編集した。
dyld _dyld_start
NSApplicationMain()関数開始
+[NSBundle mainBundle]
+[NSApplication initialize]
-[NSUserDefaults(NSUserDefaults) initWithUser:]
+[NSApplication sharedApplication]
-[NSApplication init]
-[NSApplication _registerWithDock]
+[NSBundle(NSNibLoading) loadNibNamed:owner:]
-[NSIBObjectData nibInstantiateWithOwner:topLevelObjects:]
-[NSWindowTemplate nibInstantiate]
+[NSScreen screens]
-[NSWindow initWithContentRect:styleMask:backing:defer:]
-[NSWindow orderWindow:relativeTo:]
-[NSView _drawRect:clip:]
-[NSApplication run]
-[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:]
CFRunLoopRunSpecific
malloc_zone_free
先頭のdyld
とはDynamic Loaderのことで、Foundation
を筆頭とする全てのライブラリのロードに関わってくる根幹部分のことだ。その後、100以上の処理をdyld
が約0.4秒でこなし、この辺りからNSApplicationMain()
関数の表記が見られはじめた。見慣れたメソッド名も並ぶ中、内部ではかなりの処理を行なっている。NSBundle
とNSApplication
が起動の中心を担っているのは明らかで、nibの読み込みにはAppKit
のクラスが動きまわる。
NSWindowTemplate
クラスについてはnib検証篇 3でも触れたが、やはりnibをインスタンス化する際のクラスでありNSWindow
そのものとの継承関係では無さそうだ。スクリーンの取得、ウインドウの描画、NSView
の描画という順番で行なっていることが確認できる。
上記一覧の最後となるCFRunLoopRunSpecific
の辺りで約1秒だ。ここから約10秒後にmalloc_zone_free
に至る。この処理でメモリの一部解放が行われていた。起動後の安定化が行われているようだ。
Main nib file base nameを空欄にした場合の疑問
Main nib file base nameを空欄にする実験はnib検証篇 1で行った。(Project Name)-info.plist
を書き換えるというものだ。
この時エラーを出したが、そのエラーの原因が分からないままだった。問題のログは次のものである。
2013-04-20 14:01:38.213 withoutIB[1399:303] Could not connect the action buttonPressed: to target of class NSApplication
2013-04-20 14:01:38.214 withoutIB[1399:303] Could not connect the action buttonPressed: to target of class NSApplication
2013-04-20 14:01:38.215 withoutIB[1399:303] Could not connect the action buttonPressed: to target of class NSApplication
2013-04-20 14:01:38.215 withoutIB[1399:303] Could not connect the action buttonPressed: to target of class NSApplication
NSApplicationMain()関数の動きを再確認
nib検証篇 1では、まだInstrumentsを用いた検証を行なっていなかった。上で活躍したInstrumentsをここでも使い、1で行った実験を再度試してみる。
Main nib file base nameに該当しないファイル名fooを指定した場合
nib検証篇 1の時は、コンソールにUnable to load nib file: foo, exiting
と表示された。この部分の実態はこうなっている。
dyld _dyld_start
NSApplicationMain()関数開始
+[NSBundle mainBundle]
+[NSApplication initialize]
-[NSUserDefaults(NSUserDefaults) initWithUser:]
+[NSApplication sharedApplication]
-[NSApplication init]
-[NSApplication _registerWithDock]
+[NSBundle(NSNibLoading) loadNibNamed:owner:]
NSLogv
__exit
NSApplicationMain()関数終了
ちょうどloadNibNamed:owner:
の箇所で停止した。NSLogv
はFoundation Functions
にて定義されている、システムログとして出力するための関数のようだ。以下にAppleのリファレンスを引用する。
NSLogv
Logs an error message to the Apple System Log facility.
Main nib file base nameを空欄にした場合
次にMain nib file base name
を空欄にした場合。この時に表示されたログは本記事の冒頭で再掲している。これもInstrumentsのSample Listで該当箇所を探ってみた。
dyld _dyld_start
NSApplicationMain()関数開始
+[NSBundle mainBundle]
+[NSApplication initialize]
-[NSUserDefaults(NSUserDefaults) initWithUser:]
+[NSApplication sharedApplication]
-[NSApplication init]
-[NSApplication _registerWithDock]
+[NSBundle(NSNibLoading) loadNibNamed:owner:]
-[NSNibOutletConnector initWithCoder:]
-[NSIBObjectData nibInstantiateWithOwner:topLevelObjects:]
-[NSWindowTemplate nibInstantiate]
_LSExceptionsLoad
-[NSNibControlConnector establishConnection]
NSLogv
-[NSApplication run]
-[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:]
CFRunLoopRunSpecific
…以下略…
これはexitにならず起動を成功させている。ただし何も表示されない。
nibが見つからない場合にもウインドウの作成を試みている点が意外で、NSNibOutletConnector
など前回は出てきていないクラスも動いている。_LSExceptionsLoad
も前回絡んでいない。
ログのbuttonPressed:
というメソッド自体はProfile内を検索しても見つからず残念ながら詳細は不明だが、Could not connect the action
というログとNSLogv
直前のNSNibControlConnector
がこれに関連していると見られる。
課題
Appleが提供する各種アーキテクチャ、フレームワークは非常に洗練されており、ちょっとやそっとの検証程度じゃまったくぶれないほど、よく作りこまれていることがnib検証編を通じて明らかになった。
これから、MVCやDelegateといったCocoaアプリケーションの作成に欠かせない重要な要素を踏まえていくことになる中、まずはGUIアプリケーションの基本中の基本であるウインドウの表示について調べたい。
次回、テーマを変えウインドウ作成篇としてNSWindow
に迫っていく。
続きます。
Discussion