Interface Builderを使わずにMacアプリケーション作成 - nib検証篇 3
Interface Builder上の操作とxibへの反映
前回はInterface Builderでxib内の全てのオブジェクトを取り除き、その時xib内(xibはXML形式)には何が書かれているかを確かめた。
では、ここでInterface Builder上の操作にxibがどう反映していくか調べてみようと思う。Interface Builderに戻り、NSWindow
のオブジェクト"Window"をドロップする。
変化がでた。ウインドウの定義に必要な記述が増えたようだ。
<array key="IBDocument.IntegratedClassDependencies">
<string>NSCustomObject</string>
<string>NSView</string>
<string>NSWindowTemplate</string>
</array>
Windowオブジェクトに対応するクラスが追記されている。しかしNSView
はわかるがNSWindowTemplate
が聞きなれない。この記述の実態を探るため、<string>NSWindowTemplate</string>
を<string>FooBar</string>
に書き換えてみた。
当然Xcodeに怒られる。
Unable to resolve Interface Builder plug-in dependency for "MainMenu.xib". Xcode 4 is missing components necessary to load the following class: FooBar. Ensure that Xcode has been properly installed.
つまり、「XcodeはクラスFooBar
をロードするためのコンポーネントなんて備えてないよ」という具合のようだ。Interface Builder plug-inという表記も見られるので、Interface Builder上での取り扱いのためのものだろうか。
実際、アプリケーション起動中のウインドウのクラスを調べると、それはNSWindow
であったり、NSPanel
であったりしてNSWindowTemplate
は登場しない。Interface Builder上でウインドウ関連をまとめて扱うための別のクラスとも考えられ、IntegratedClassDependencies
の"Integrated Class"(統合クラス)の意味もここで推測できる。
仕様が不明なので断定はできないが、Dependenciesとつく項目はInterface Builder画面上での使用・依存している機能やクラスの列挙のようだ。
- IntegratedClassDependencies
- PluginDependencies
xib内の定義への反映
少し脱線したが、Windowオブジェクト追加でxibの内容にどう反映されていくかの話に戻る。
IBDocument.RootObjects
は次のようになっていた。
<array class="NSMutableArray" key="IBDocument.RootObjects" id="1048">
<object class="NSCustomObject" id="1021">
<string key="NSClassName">NSApplication</string>
</object>
<object class="NSCustomObject" id="1014">
<string key="NSClassName">FirstResponder</string>
</object>
<object class="NSCustomObject" id="1050">
<string key="NSClassName">NSApplication</string>
</object>
<object class="NSWindowTemplate" id="858969353">
<int key="NSWindowStyleMask">15</int>
<int key="NSWindowBacking">2</int>
<string key="NSWindowRect">{{196, 240}, {480, 270}}</string>
<int key="NSWTFlags">611845120</int>
<string key="NSWindowTitle">Window</string>
<string key="NSWindowClass">NSWindow</string>
<nil key="NSViewClass"/>
<nil key="NSUserInterfaceItemIdentifier"/>
<object class="NSView" key="NSWindowView" id="234672694">
<reference key="NSNextResponder"/>
<int key="NSvFlags">256</int>
<string key="NSFrameSize">{480, 270}</string>
<reference key="NSSuperview"/>
<reference key="NSWindow"/>
<string key="NSReuseIdentifierKey">_NS:20</string>
</object>
<string key="NSScreenRect">{{0, 0}, {1920, 1178}}</string>
<string key="NSMaxSize">{10000000000000, 10000000000000}</string>
<bool key="NSWindowIsRestorable">YES</bool>
</object>
</array>
前回より記述が増えている。具体的には<object class="NSWindowTemplate" id="858969353">
の辺りが増えた。
引き続き、IBDocument.Objects
について。
<object class="IBObjectContainer" key="IBDocument.Objects">
<array class="NSMutableArray" key="connectionRecords"/>
<object class="IBMutableOrderedSet" key="objectRecords">
<array key="orderedObjects">
<object class="IBObjectRecord">
<int key="objectID">0</int>
<array key="object" id="0"/>
<reference key="children" ref="1048"/>
<nil key="parent"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">-2</int>
<reference key="object" ref="1021"/>
<reference key="parent" ref="0"/>
<string key="objectName">File's Owner</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">-1</int>
<reference key="object" ref="1014"/>
<reference key="parent" ref="0"/>
<string key="objectName">First Responder</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">-3</int>
<reference key="object" ref="1050"/>
<reference key="parent" ref="0"/>
<string key="objectName">Application</string>
</object>
<object class="IBObjectRecord">
<int key="objectID">581</int>
<reference key="object" ref="858969353"/>
<array class="NSMutableArray" key="children">
<reference ref="234672694"/>
</array>
<reference key="parent" ref="0"/>
</object>
<object class="IBObjectRecord">
<int key="objectID">582</int>
<reference key="object" ref="234672694"/>
<reference key="parent" ref="858969353"/>
</object>
</array>
</object>
<dictionary class="NSMutableDictionary" key="flattenedProperties">
<string key="-1.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="-2.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="-3.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<string key="581.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
<boolean value="YES" key="581.NSWindowTemplate.visibleAtLaunch"/>
<string key="582.IBPluginDependency">com.apple.InterfaceBuilder.CocoaPlugin</string>
</dictionary>
<dictionary class="NSMutableDictionary" key="unlocalizedProperties"/>
<nil key="activeLocalization"/>
<dictionary class="NSMutableDictionary" key="localizations"/>
<nil key="sourceID"/>
<int key="maxID">582</int>
</object>
こちらも記述が増えている。そして前回よくわからなかった二つの違いもわかった。上(IBDocument.RootObjects
)ではオブジェクトの各プロパティを具体的に定義している。一方、下はIBObjectContainer
というクラスのオブジェクトを定義。これは上記オブジェクトに対する索引のようなものとも取れる。
xibから記述を削除するとInterface Builderはどういう挙動をとるか
前回と同じ方法として、xibのXMLソースから記述をごっそり削除してみよう。まず、上から<object class="NSWindowTemplate" id="858969353">
の一連を削除する。
当然怒られた。これは前回も試しているが、対応するidが見つからないという警告が出る。
続いて、IBDocument.Objects
から以下の内容を削除してみた。これはまだ前回試していない。
<object class="IBObjectRecord">
<int key="objectID">581</int>
<reference key="object" ref="858969353"/>
<array class="NSMutableArray" key="children">
<reference ref="234672694"/>
</array>
<reference key="parent" ref="0"/>
</object>
すると、Interface Builderを表示しようとしたときXcodeがすぐにクラッシュ。ログを確認すると原因となるメソッドがわかった。Backtraceはさすがに長いので4以降は省略している。
…
Crashed Thread: 0 Dispatch queue: com.apple.main-thread
Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
External Modification Warnings:
Thread creation by external task.
Application Specific Information:
ProductBuildVersion: 4H1003
ASSERTION FAILURE in /SourceCache/IBAutolayoutFoundation/IBAutolayoutFoundation-2065/Framework/Classes/Arbitration/IBAutolayoutArbitrationUnit.m:335
Details: The layout information backing the arbitration unit must contain any objects added to it: <NSView: 0x4040a4b80>
Object: <IBCocoaAutolayoutArbitrationUnit: 0x404398680>
Method: -addObject:
Thread: <NSThread: 0x40010a220>{name = (null), num = 1}
Hints: None
Backtrace:
0 0x000000010db5da2a -[IDEAssertionHandler handleFailureInMethod:object:fileName:lineNumber:messageFormat:arguments:] (in IDEKit)
1 0x000000010ce02554 _DVTAssertionFailureHandler (in DVTFoundation)
2 0x0000000110818019 -[IBAutolayoutArbitrationUnit addObject:] (in IBAutolayoutFoundation)
3 0x0000000110816fea -[IBAutolayoutArbitrationUnit initWithRootObject:layoutInfo:] (in IBAutolayoutFoundation)
4 …
- addObject:
が原因で例外が投げられていることがわかる。IBAutolayoutArbitrationUnit
クラスのメソッドのようだ。"IB Autolayout Arbitration Unit"とは「IBオートレイアウト仲裁ユニット」と訳せる。xibを読み込みInterface Builder画面上に配置するためのクラスだろうか。
ここで注目すべきは3、initWithRootObject:layoutInfo:
というメソッド。xib内のIBDocument.RootObjects
の部分をこのメソッドで扱っているとみられる。そして2のaddObject:
でクラッシュしたということは、こちらはxib内のIBDocument.Objects
の部分について扱っているとみていいだろう。
Interface Builder XIB Compilerの挙動
IBAutolayoutArbitrationUnit
はアプリケーションのコンパイル自体には関わっていないようだ。その証拠に、xibから内容を削除したあとInterface Builderを表示させずにXcodeのBuildを選択するとクラッシュしていない。代わりにコンパイルエラーを出している。
…
ibtoold[7306:b03] [MT] DVTAssertions: ASSERTION FAILURE in /SourceCache/IDEInterfaceBuilder/IDEInterfaceBuilder-3084/Framework/Document/IBObjectContainer.m:499
Details: Can't get the OID of an object not in the document!
Object: <IBObjectContainer: 0x400f2ce40>
Method: -objectIDForObject:
Thread: <NSThread: 0x40010a260>{name = (null), num = 1}
Hints: None
Backtrace:
0 0x00000001102c6723 -[DVTAssertionHandler handleFailureInMethod:object:fileName:lineNumber:messageFormat:arguments:] (in DVTFoundatio
1 0x00000001102c6554 _DVTAssertionFailureHandler (in DVTFoundatio
2 0x000000010f94e311 -[IBObjectContainer objectIDForObject:] (in IDEInterfaceBuilderKi
3 0x000000011209d549 -[NSIBObjectData(DesignTimeInterface) insertObjects:withParent:fromObjectContainer:] (in IDEInterfaceBuilderCocoaIntegratio
4 0x000000011209d652 -[NSIBObjectData(DesignTimeInterface) insertObjects:withParent:fromObjectContainer:] (in IDEInterfaceBuilderCocoaIntegratio
5 0x000000011209d8de -[NSIBObjectData(DesignTimeInterface) initWithObjectContainer:andRootObject:] (in IDEInterfaceBuilderCocoaIntegratio
6 0x0000000112091eea -[IBCocoaDocument compiledKeyedObjectsDataWithOptions:error:] (in IDEInterfaceBuilderCocoaIntegratio
7 0x0000000112092386 -[IBCocoaDocument internalCompiledPackageWithOptions:error:] (in IDEInterfaceBuilderCocoaIntegratio
8 0x000000010f8ddd4a __47-[IBDocument compiledPackageWithOptions:error:]_block_invoke (in IDEInterfaceBuilderKi
9 0x000000010f8f5273 -[IBDocument assertIfArbitrationIsScheduledDuring:] (in IDEInterfaceBuilderKi
10 0x000000010f8ddd15 -[IBDocument compiledPackageWithOptions:error:] (in IDEInterfaceBuilderKit
11 0x000000010f7d9063 (in ibtoold
12 …
/Applications/Xcode.app/Contents/Developer/usr/bin/ibtool failed with exit code 255
クラッシュはしなかったものの、-objectIDForObject:
メソッドが原因のエラーだ。object記述を取り除いたのが原因なことは一目瞭然である。
これらのログから、xibをInterface Builder画面上で表示するときと、nibに変換するときとでは処理が異なることがわかった。
疑問
xib内のIBDocument.RootObjects
では各オブジェクトのプロパティを定義していた。では、これらと同等の情報をObjective-Cコードで定義できるのだろうか?
今回はログが続きややマニアックになってしまった。次回は、NSApplicationMain()
関数を使わずアプリケーションを起動する方法について試していく予定。
続きます。
Discussion