インサイドXamarin(5)
Xamarin.iOSの仕組みとアプリケーションの構成
いよいよXamarin.iOSを取り上げる。その仕組みや、Xamarin.iOSアプリの作成/ビルド/実行とデバッグなどについて解説。
はじめに
さて、今回からようやくモバイル製品の話に入っていきたい。今回と次回はXamarin.iOSを取り上げる。
最初から残念なお知らせを書かなければならないが、筆者はiOS開発の経験はほぼ無いので、今回は経験知より「資料の読み取りと聞き取り」に基づいて書かれている。
Xamarin.iOSは、IDE(Xamarin StudioあるいはVisual Studio)を利用して、C#およびF#によってアプリケーションを作成できるビルド環境と、iOS SDKのネイティブAPIに対応するCLI(.NET)のバインディングAPIを提供するものである。Xamarin.iOSの使い方については、特にVisual Studio(以下、VS)を主な対象として丁寧に説明された記事がBuild Insiderで公開されているので、そちらを参照されたい。Xamarin.iOSの使い方については、日本Xamarinユーザーグループの田淵氏がまとめられた入門ガイドが懇切丁寧にまとめられているので、そちらを参照されたい。
Xamarin.iOSに限らず、Xamarin製品は、ネイティブAPIに対応するバインディングを中心とするAPI構成を採っている。共通UI(ユーザーインターフェース)を提供しているXamarin.Formsも、このネイティブAPIへのバインディングの上に、プラットフォーム共通のサブセットを便宜上提供しているものであり、Xamarinが自らUIフレームワークを(より低レベルのレイヤーである)描画ライブラリなどを用いて設計しているわけではない。
Xamarinは、ネイティブプラットフォームのAPIを(100%ではないが)サポートすることで、ネイティブアプリケーションのユーザーエクスペリエンス(UX)を提供することを目的としている。Xamarin.Formsのように、応用ライブラリのレベルでコードを共通化することはできるが、UIの構成はそもそもプラットフォームによって最適解が異なるものであるから、完成度の高いUIを構築したい場合はプラットフォーム別に作成した方がよい、というのが、Xamarinのスタンスである。
MonoとMac(OS X/macOS)
Xamarinの動作環境としては、OS X(旧OSでは「Mac OS」「Mac OS X」と呼ばれ、最新OSでは「macOS」と改称されている)は最も厚遇された開発環境であると言っていい。Xamarinのエンジニアの大半はMacで作業している。
WindowsからVSを使用してiOSアプリを開発しても、実行/デバッグにはペアリングされたMacが必要になる。VSから使用するために必要になったソフトウェアスタックは相応の規模である(次の図に示すとおり、少なくともMacビルドホストの仕組みと、UIデザイナー画面ホストの仕組みと、リモートデバッグに必要なVSのデバッガー・フロントエンドAPIの応用がある)。このソフトウェアスタックは、MacのXamarin Studioで開発している限り、全く必要ない部分だ。
一方で、Xamarin以前の歴史をたどってみると、MonoのMacサポートは、大して重要ではなかった。2000年代中期まで、Mac開発者は非常に少なかった。Macといえば、かつてはPowerPCであり、当初はPowerPCサポートのJIT実装が主な作業だった。Monoランタイムは割と早い時点でさまざまなCPUアーキテクチャをサポートしていて、ARMもiPhone登場以前にNokia 770のころからサポートしている。大規模な作業ではなかったが、BSDベースのランタイムのInternalCallの実装(第3回を参照)が必要になる場面もあった。
その後のOS Xサポートの努力は、Windowsフォーム(Windows Forms)の実装に向けられた。これは、恐らくアーキテクチャの類似性とAPIの安定性という理由から、Cocoaではなく、Mac OSのさらに低レイヤーのフレームワークであるCarbonに基づいていた。そのころ、Mac OS XのフレームワークであるCocoaのC#バインディングは、いくつかの実装が乱立していて、それぞれに利点と問題点があった。最終的にMonoMacが開発されるまで、この状況は続いた。
また、Mac上のGtk+は、もともと「gtk-osx」というほぼ外郭プロジェクトとして動いていて、Mac環境対応は比較的新しい。そのため、使っていて不満点が出てくることはあり得る。特に最近まではテキストエディターで日本語入力もできなかったので、必要な場合はネイティブアプリケーションからコピー&ペーストする必要があった。Xamarinは、Gtk+開発の達人がそろっているLanedoと提携し、あるいは同社に業務委託して、OS X版(およびWindows版)のGtk+に改善を加えてきた(ちなみに、Gtk+環境が整うまでは、MonoDevelopはMacPortsなどで構築されたX11環境で動作した。X11の方がgtk-osxよりも完成度が高かったので、当時から日本語入力には何の支障も無かった)。
Mono on OS Xを基盤とするXamarin.iOS
iOSはおおむね、OS Xがモバイル環境用に調整されたOSであり、BSDベースのカーネル、CocoaベースのUI(CocoaTouch)、Objective-CベースのSDK(iOS SDK)など、多くの部分が共通している。このため、Xamarin.iOSは、多くの部分でMono on OS Xの対応作業によって、ある程度、対応を容易に実現できたとも言える。
一方で、iOSはARMアーキテクチャの上で動作する。monoランタイムのARMサポートは以前からあったが、そのころからさらに命令が追加されたABI(Application Binary Interface)にも対応している。
iOSのアプリケーションの実行にかかる制約は大きい。iOSでは任意のマシンコードを動的に生成して実行することができない。実行時にCPUネイティブの命令を生成して実行するJIT(ジャストインタイム)コンパイラーは、iOSからは軒並み拒絶されることになる(ただし例外がないわけではなく、例えばSafariブラウザーに搭載されているJavaScriptエンジンではJITが動作している)。第3回でも言及したが、monoは、通常はJITエンジンで動作しているため、そのままではiOSでは使い物にならない。この問題を解決するために、Xamarin.iOSではmonoランタイムの機能の1つであるAOT(事前)コンパイラーを使用している。AOTはJITの完全な置き換えを実現するものではないため、iOS環境では実行できない種類のILコードがいくつかある。AOTについては、特にXamarin.iOSの大きな制限事項の要因なので、次回、改めて詳しく説明する。ここはMono on OS Xとは大きく異なる部分だ。
CocoaTouchのAPIバインディングに関しては、それまでのさまざまなCocoaバインディングの試みから得られた知見を基に、新しく設計が仕切り直された。この仕組みは、その後、「MonoMac」というオープンソースのCocoa(デスクトップ)バインディングにも持ち込まれた。このMonoMacは、後にXamarin.Macというフレームワークとして商用製品となった。Xamarin.MacとXamarin.iOSはそのソフトウェア要素の大部分を共有する形で発展し、その後マイクロソフトによる買収に伴って公開されたソースコードは、GitHub上でxamarin-maciosというリポジトリで公開されている。
iOSは、アップルによる開発者への変化の要求が激しいプラットフォームでもある。本稿を初めて執筆した2013年末から2016年現在に至るまで、さまざまな変更が発生し、Xamarinもその対応を余儀なくされてきた。
最もインパクトのある変更は、32bit環境と64bit環境の両方に対応するためのUnified APIへの移行だろう。2015年2月1日以降、アップルは32bitと64bitの両方に対応していない新規アプリケーションの受け付けを停止している(ことになっている)。
Objective-C言語を前提としたiOS SDKでは、開発者が32bitと64bitの差異を気にしなくて済むように、nint
、nuint
、nfloat
といった型を新たに用意した。Xamarin.iOS開発でも、これに対応した、かつなるべく自然なコードを実現するために、XamarinではSystem.nint
、System.nfloat
といった“型破りな”型を定義している。Xamarinは勝手にC#キーワードを拡大してnint
やnfloat
をキーワードとして追加することはできないので、System
名前空間をインポートしているコード上ならいつでも使える型として、これらを定義したというわけだ。当然ながら、小文字で始まっている型名は、フレームワーク設計ガイドラインには従っていないが、これは状況を踏まえた最適解であるといえよう。
また、アップルはApple WatchやApple TVといった製品にも、iOSから派生したOSを利用しているが、これらはLLVMを前提に開発された「bitcode」という中間コード上で動作するアプリケーションのみをApp Storeで受け付けている。Monoは以前からLLVMバックエンドを利用したコードジェネレーターを実装していたが、全てのネイティブコードをLLVMで生成していたわけではなかったため、“完全な”bitcodeによるアプリケーションのAOTを実現するために、少なからぬ規模の開発作業がXamarinの内部で必要になっていた。
2016年にオープンソース化されたxamarin-maciosは、iOS(iPhoneおよびiPad)、watchOS、tvOS、Mac(OS X/macOS)の全てを含む形で公開されており、ビルドしてインストール済みのXamarin.iOSおよびXamarin.Macを置き換えることが可能になっている。
ただし注意すべきは、Xamarinがオープンソースで公開したのは、あくまでプラットフォームのSDKのみであって、これらに対応するIDE(Xamarin StudioおよびVisual Studio)のアドインはプロプラエタリのままである、ということである。IDEのアドインには、プラットフォーム製品の特定のバージョンの動作を前提として実装されている部分があるので、考えなしにOSS版のビルドをインストールすることは筆者はお勧めしない。
Xamarin.iOSのソフトウェア構成要素
Xamarin.iOSは、いくつかのソフトウェア部品から成っている。もしこれまでにXamarinから個別のソフトウェアパッケージをインストールしたことがあれば、あるいはアップデートダイアログを細かくチェックしていれば(次の図)、ダウンロードが複数のコンポーネントで別々になっていることに気付いたかもしれない。また、特定のバージョンに問題があって古いバージョンを使用したい場合は、XamarinのWebサイトのアカウントページの中で、リンクを発見できるはずだが、そのダウンロード内容は個別のパッケージである。
1 Xamarin.iOS
製品の中心となる、iOSアプリケーションをビルドするために必要な基本セットとなるパッケージで、この中には「mono」(ランタイム、コンパイラー、iOSプロファイルのライブラリ)、iOS APIバインディングである「Xamarin.iOS.dll」、watchOS APIバインディングである「Xamarin.WatchOS.dll」、tvOS APIバインディングである「Xamarin.TVOS.dll」、その他のモバイルライブラリ、ビルドおよびパッケージングのためのツールチェインが含まれている。Xamarin.iOSアプリケーションは、主にXamarin.iOS.dllのAPIを使用して作成することになる。標準ライブラリの使い方は、.NET Frameworkと同様だ。
ちなみに、Xamarin.iOS 10.0より古いバージョンには、「クラシックAPI」というものが存在していた。これはiOSの64ビットサポートおよび要求ポリシーが登場する以前にあったもので、ライブラリも「monotouch.dll」という名前だった。現在ではクラシックAPIサポートは削除されている(Xamarinがいくらサポートを継続しても、App StoreはそのAPIに基づくアプリケーションを受け付けないだろう)。
Xamarin.iOSのアプリケーションは、mtouch
というツールによってビルドされる(通常はXamarin StudioあるいはVSでビルドするであろうから、mtouch
を意識する必要はないが、説明のためには有用なので、度々登場する予定だ)。mtouch
は、iOSシミュレーターとiOSデバイス、またデバッグとリリース、というオプションに基づいて、適切なパッケージングを実行する。iOSのビルドについては、節を改めて論じる。
2 Xamarin Studio iOSアドイン
Xamarin Studioの役割は多岐にわたるが、その基本は、プロジェクトの作成、ビルド、実行とデバッグにある。これが無いところからiOSのアプリケーションを作成するのは、不可能ではないが困難だろう。また、その基本機能に加えて、このアドインにはInterface Builderの統合も含まれており、Xamarin Studioを使用してStoryboardを使用した画面設計を行うこともできる。
iOSアドインは、Xamarin Studioの一部として一緒にアップデートされる。
MonoDevelopは、プロジェクトの種類に応じて、既存のあるいはカスタムのビルドを実行できる。.NETのプロジェクトには、「ProjectTypeGuids」というプロパティがあって(通常はユーザーが触ることはない)、ここには通常、2つのGUIDが含まれている。Xamarin.iOSの.csprojファイルであれば、「C#」「iOSアプリケーション(あるいは「バインディング」などその他のプロジェクトの種類)」が含まれている。Xamarin StudioがこれらのGUIDの含まれたプロジェクトをロードすると、iOSプロジェクトとしてロードされる。プロジェクトのテンプレートは、MonoDevelopの仕組みに完全に基づいて作られている。このプロジェクト種別に基づいて、MonoDevelopはXamarin.iOSアドインによって指定されたビルドおよびデバッグを実行することになる。
ビルドは、通常のC#コンパイラー(あるいはF#コンパイラー)によるアセンブリのビルド、およびその後のmtouch
によって行われる。作成されるプロジェクトファイルの構成、およびアプリケーションの実行とデバッグについては、節を改めて論じる。
また、GUIリソースである.xibファイルをダブルクリックするとInterface Builderが起動され、Interface Builder上でファイルを保存した場合は、関連する自動生成のソースコードが自動的に更新される。ちなみに、Xamarin Studioの画面上にInterface BuilderのデザイナーGUIを埋め込んで表示する機能も実装されていて、その内部的な仕組みは明らかではない。
Xamarin Studioアドインの諸機能については、上記以外のものも含めて、個別のドキュメントページ(英語)で詳しく説明されている。
3 Xamarin.iOS for Visual Studio
Visual Studioアドインも、基本的な役割は、iOSプロジェクトのテンプレートの提供、ビルド、実行とデバッグのサポートであり、Xamarin Studioと変わらない。ただし、実装はXamarin Studioとは全く異なる。
Visual Studioアドインには、Windowsからのリモートデバッグのために追加された機能が含まれている。Visual Studioアドインは、Visual Studio SDKを使用して開発されているが、この中にはVSのデバッガーのUIだけを使用して、他のネイティブ・デバッグエンジンを操作できるAPIが備わっている。Xamarin.iOSでは、これを利用して、Macホスト側で動作する「Xamarin Mac Agent」(次の図)にアドインからデバッグ操作を渡してもらい、実際のデバッガーの制御はMac上で行う(Monoチームは、かつてNovell時代にも、Tools for Visual Studioという、VSからリモートのLinuxデスクトップアプリケーションをデバッグする製品を販売していたことがある)。
また、Visual Studio上にMacホストからInterface Builderの画面を転送したUIデザイナーの機能も実現しており、これはXamarin Studioには無い(必要ない)。後述する「MonoTouch.Dialog」を使用して設計できる範囲のUIであれば、コードからUIを作成することも現実的に可能だし、Xamarin.Formsで実現可能な範囲であれば、Xamarin.iOS上でも動作するUIを、Windows上だけで設計することも不可能ではない。ただし、Xamarin.FormsはあくまでGUIの共通APIを用意するものにすぎないのであって、それぞれのプラットフォームで自分の設計したUIがどのように見えているかは、プラットフォームごとにきちんと確認すべきである。
Xamarin.iOSにおけるVisual Studioサポートは、だいぶ後になって追加されたものだ。iOS開発にはMacが必要となるわけだし、あえてVSで開発できるようにする需要が当初からあったはずはない。iOS開発ツールがMac中心なのは自然な成り行きではある。VSにおけるiOSプロジェクトのサポートは、「Windows上でもプロジェクトのビルドはできる」状態を確保しておきたい、というところから始まっている。
4 サンプルコード
これは配布パッケージには含まれないが、Xamarin.iOSのサンプルはGitHubのリポジトリに多数存在しているので、この中を参照されたい。また、iOS/Android(場合によってはWindows 10 Mobile/Windows Phoneも)に対応した共通のサンプルが含まれるリポジトリもある。
Xamarin.iOSのアプリケーションの作成、ビルド、実行とデバッグ
Xamarin.iOSアプリケーションの動作の基本的な仕組み
Xamarin.iOSは、iOS環境におけるネイティブアプリケーションの開発を目指した製品だ。具体的には、C#あるいはF#、およびCLI(.NET)を使用して、iOS SDKのAPIに基づくアプリケーションを作成できる。Xamarin.iOSアプリケーションは、iOS SDKのアプリケーションのビルドの仕組みにのっとって作成される。すなわち、main
関数からUIApplicationMain
関数を呼び出してアプリケーションを起動する。
Xamarin.iOSアプリケーションでは、このmain
関数の中で、第3回で説明したembedded monoの仕組みに基づいて、monoランタイムを初期化し、アセンブリをロードし、アセンブリのエントリポイント(=Mainメソッド)を呼び出している(Xamarin.iOSアプリケーションのアセンブリは、組み込み環境のアセンブリとしては珍しく、EXE形式である)。
Xamarin.iOSのプロジェクトテンプレート
Xamarin.iOSのプロジェクトの種類は多数あるが、基本的には4種類といえる。
1 アプリケーション・プロジェクト
2 ライブラリ・プロジェクト
3 バインディング・プロジェクト
4 App Extensionsプロジェクト
究極的には、1と2は同一のProjectTypeGuidを使用しているので、出力形式が「Exe」であるか「Library」であるかの違いでしかないが、1にはUIリソースなどのビルドに必要なプロパティなども含まれるので、手作業で2を1に変換するには、それなりに知識が必要になるし、素直にアプリケーション・プロジェクトを新しく作った方が速い。プロジェクトテンプレートとしては、Unit Testのプロジェクトも存在するが、これは1に含まれる。
3のバインディング・プロジェクトは、Objective-CライブラリのAPIをC#から呼び出せるようにするためのバインディング・ライブラリを作成するためのプロジェクトだ(詳細は次回で説明する)。
4の「App Extensions」というのは、アプリケーションの「エクステンション」を作成するためのプロジェクトだ。Xcodeにこれに相当するものがあるので、そのドキュメントを参考にされたい。具体的な用途としては、watchOSアプリケーションが、このApp Extensionsの仕組みに基づいて開発されることになる。
アプリケーションのテンプレートは、Xamarin.iOSの歴史上、さまざまな種類とオプションが用意されていたが、Xamarin.iOS 10現在、プロジェクトテンプレートの選択はだいぶシンプルになっている。
- カテゴリを「iOS」「watchOS」「tvOS」から選ぶ(なお、他にも[Cross Platform]カテゴリにある「Xamarin.Forms」などが存在するが、今回はXamarin.iOSに関するもののみ言及する)
- 「アプリケーション」か「ライブラリ」を選ぶ。アプリケーションは目的(単一ビュー、Master-Detail、WebView、OpenGLなど)によってテンプレートが変わり、ライブラリは一般的なクラスライブラリかバインディング・ライブラリかによってプロジェクトの種類が変わる
かつては、.storyboardファイルの代わりに.xibファイルを使用するテンプレートがあったり、Unified APIに対応しないクラシックAPIを使用したテンプレートがあったりしたが、いずれも現在では削除されている。
Xamarin StudioやVisual Studioで、.storyboardファイルや.plistファイルを開くと、それぞれIDE上で編集画面が開くようになっている。(かつては.xibファイルがサポートされており、それを開くとXcodeのInterface Builderが開くようになっていた。現在はXamarinのIDEアドインが機能を実現している)。
ビルドの種類
iOSでは、デスクトップアプリケーションとは異なり、デバッグビルドとリリースビルドの性質が少なからず異なる。App Storeにリリースするアプリケーションのパッケージは、なるべく小さい構成になっている必要がある。前回も言及したが、この最小構成を実現するために、Xamarin.iOSでは「アセンブリ・リンカー(Linker)」という技術を使用している。これは、アプリケーションで実行される部分のコードを全てILレベルでスキャンし、アセンブリ/型/メソッドにガベージコレクションのようなマーキングを施して、不要なコードを削除する機能だ。
本稿執筆時点でのXamarin.iOSの最新バージョンは10.0で、かつては「Debug」「Release」「Ad-Hoc」「AppStore」などいくつかのビルドの種類が存在していたが、現在は.NETの一般的な構成である「Debug」と「Release」のみとなっている。バンドル署名などのオプションは構成の選択ではなく、それぞれの構成の一部として選択できるようになっている。構成は必要なら自分で複製することも出来るので、以前のように自分で構成を調整することもできる(ただ、複数メンテナンスしなければならなくなるので面倒だろう)。
iOSアプリケーションを開発し、ベータテスター配布などを経験してみると、バンドル署名やプロビジョニング・プロファイルのオプションの意義が理解できるだろう。
実行とデバッグ
Xamarin.iOSのプロジェクトの実行結果は、インストールの対象がiOSシミュレーターである場合と、iOSデバイスである場合とで、大きく異なる。iOSシミュレーターは、エミュレーターではなく、あくまでmacOSが動作しているx86 CPUの上で動作している仮想マシンであり、アプリケーションはJITによって動作する。iOSデバイスはARMであり、iOSデバイス用にビルドされたアプリケーションはAOTによってARMのCPU命令に変換されており、ARM上でしか動作しない。また、iOSシミュレーターでは、iOSデバイス上で厳格に適用された動的コード実行禁止の振る舞いが再現されない。iOSアプリケーションを開発する際には、デバイス上での動作確認は必須だ。
iOSプロジェクトのデバッグ実行は、デスクトップアプリケーションのデバッグ実行とは少し異なる。どちらも別々のmonoランタイム上で実行され、デバッグのための命令やコールバックをクライアント/サーバーのモデルで行っている点は同様だが、「iOSシミュレーターを起動し、必要なファイルを転送した上で、プログラムを起動し、デバッガーを接続する」という仕組みは、単にすでにビルドされた実行ファイルをデバッグするだけのデスクトップアプリケーションには無い仕組みだ。
Xamarin.iOSの実行オプションで特徴的なものを2つ言及しておこう。
一つは、インクリメンタル・ビルドのオプションだ。通常、デバイスへのデプロイメントに際して必要になるAOTビルドでは、全てのアセンブリをネイティブコードに変換することになり、この時間はそれなりに長い。しかし、もしアセンブリ・リンカーを実行しないのであれば、基本クラスライブラリなどの内容は変わらないのだから、一度、AOTで変換しておけば済む話だ。そこで、アセンブリを個別にネイティブコードに変換しておき、デバッグの際に、変更されたアセンブリだけAOTを実行する、このインクリメンタル・ビルドが提供されるようになった。
このオプションは、デフォルトで有効になっている。このビルドによるアプリケーションのデプロイメントは特殊で、Xamarin StudioあるいはVSから実行しないと(デバイス上から直接実行すると)、必要なコードが足りずにクラッシュする。また、インクリメンタル・ビルドを利用するのであれば、上記のような仕組み上、アセンブリのリンクは全く行わないようにする方がよい。
もう一つは、デバッグ・ターゲットのデバイスに特化したビルドだ。通常、iOSアプリケーションは32bitと64bitの両方をサポートする「Unifiedビルド」でなければならないが、通常、デバッグ時には、現在ターゲットとして選択しているデバイス上でのみ動作すればよいのだから、それに特化したビルドのみ実行するようにすれば、ビルド時間が短縮できる。このオプションもデフォルトで有効になっており、リリース用のビルド実行時には無効にしておくことが求められる。
デバイス向けビルドには他にもいくつかのオプションがあり、それらについてはドキュメントページ(英語)が存在する。
■
次回は、Xamarin.iOSで使用するライブラリを説明する。
※以下では、本稿の前後を合わせて5回分(第3回~第7回)のみ表示しています。
連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
3. Xamarinの基盤「Mono」のmonoランタイムとクラスライブラリ
Xamarinにおけるソフトウェアの基盤であるMonoを深く理解すれば、Xamarin製品の理解はもっと深まる。今回はmonoランタイムと、Monoのクラスライブラリについて解説する。
4. Monoのモバイル化の流れ ― Xamarin.iOS/Xamarin.Androidの誕生
デスクトップ環境での動作を主眼に開発された「.NET」のオープンソース実装である「Mono」は、どのようにモバイル開発に向かって流れていくことになったのか。
5. 【現在、表示中】≫ Xamarin.iOSの仕組みとアプリケーションの構成
いよいよXamarin.iOSを取り上げる。その仕組みや、Xamarin.iOSアプリの作成/ビルド/実行とデバッグなどについて解説。
6. Xamarin.iOSで使用するライブラリ
Xamarin.iOS解説の後編。iOSの.NET APIである「monotouch.dll」や、Xamarin.iOS向けの追加ライブラリなどについて説明。