🍎

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

2020/09/20に公開

Interface Builder上の操作とxibへの反映

前回はInterface Builderでxib内の全てのオブジェクトを取り除き、その時xib内(xibはXML形式)には何が書かれているかを確かめた。

では、ここでInterface Builder上の操作にxibがどう反映していくか調べてみようと思う。Interface Builderに戻り、NSWindowのオブジェクト"Window"をドロップする。

変化がでた。ウインドウの定義に必要な記述が増えたようだ。

MainMenu.xib
<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は次のようになっていた。

MainMenu.xib
<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について。

MainMenu.xib
<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から以下の内容を削除してみた。これはまだ前回試していない。

MainMenu.xib
<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以降は省略している。

CrashLog
…
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を選択するとクラッシュしていない。代わりにコンパイルエラーを出している。

Interface_Builder_XIB_Compiler_Error
…
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