Xamarin.iOS のネイティブバインディングを活用して既存の Objective-C 資産を流用する

Xamarin.iOS では、ネイティブバインディングを使用して Objective-C で書かれたライブラリを利用することができます。

つまり、既存のObjective-Cで書かれたアプリをXamarinに移行する際、特定の機能はネイティブライブラリにすることでObjective-Cのまま利用することができます。

ここでは、Objective-Cで書かれたアプリをUIViewControllerごとネイティブライブラリにし、Xamarin.iOSにネイティブバインディングで持ってくる方法をご紹介します。

プロジェクトの作成

作成したネイティブライブラリを実際に動作させて確認する確認用アプリを作り、その中に今回の目標物を生成するスタティックライブラリプロジェクトを追加し、Objective-Cで確認してからXamarinに持って行くと効率的です。

Xcode プロジェクト作成

まず、Xcode で Empty Application を作成します。

名称未設定.png

次に、作成したプロジェクトを右クリックし、New Project... を選択します。

名称未設定.png

今度は Cocoa Touch Static Library を指定します。これがXamarinに持って行くスタティックライブラリになります。

名称未設定.png

次に、スタティックライブラリプロジェクトの Build Settings にある、Build Active Architecture Only の指定を削除し、No にします。

Xamarinからネイティブコードを活用する.pptx 2014-06-09 21-00-36 2014-06-09 21-00-39.png

リソースバンドル作成ターゲットの追加

次に、このライブラリが使用する画像・テキストなどのリソースが含まれるバンドルを作成します。

追加したネイティブライブラリのプロジェクトを選択し、ターゲットのリストの下にある追加ボタンをクリックします。

名称未設定 2014-06-09 20-50-25 2014-06-09 20-50-41.png

iOS のほうには Bundle がないため、OS X のほうで追加してから iOS に変更するという手順を踏みます。OS X の Frameworks & Library から Bundle を選択します。このとき、名前は ネイティブライブラリの名前 + Resources という形にしてください。(この後で追加するスクリプトがそれを前提に作られています。)

名称未設定.png

Build Settings で Base SDK に設定されている OS X の指定を delete キーで消します。こうすることでプロジェクト側に指定されている iOS の指定が使用されるようになります。

Xamarinからネイティブコードを活用する.pptx 2014-06-09 20-54-10 2014-06-09 20-54-12.png

次に、スタティックライブラリのターゲットを選び、Build Phases のページにある Target Dependencies に、先ほどのバンドルを指定します。これで、スタティックライブラリのビルド時にリソースのバンドルが同時に作成されるようになります。

Xamarinからネイティブコードを活用する.pptx 2014-06-09 20-55-11 2014-06-09 20-55-13.png

ユニバーサルバイナリ作成のターゲット追加

通常、iOS アプリやライブラリは、実機実行するときは実機用、シミュレータ実行するときはシミュレータ用のライブラリのみが出力されます。これではシミュレータ実行時と実機実行時で別々のDLLを参照しないといけなくなって不便なため、スクリプトを追加して実機用とシミュレータ用のスタティックライブラリをあわせて1つのスタティックライブラリにする処理をプロジェクトに追加します。

先ほどBundleを追加したときと同じ方法で、iOS の Other にある、Aggregate を選択します。自分はライブラリ名 + Universal と名前をつけることが多いです。

名称未設定.png

Aggregate の Build Phase に Run Script Build Phase を追加します。

名称未設定.png

追加されたRun Script Build Phaseに以下のスクリプトを入力します。

# define output folder environment variable
LIBRARY_TARGET_NAME=${PROJECT_NAME}
UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal

# Step 1. Build Device and Simulator versions
xcodebuild -target "${LIBRARY_TARGET_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos  BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}"
xcodebuild -target "${LIBRARY_TARGET_NAME}" -configuration ${CONFIGURATION} -sdk iphonesimulator -arch i386 BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}"

# make sure the output directory exists
mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"

# Step 2. Create universal binary file using lipo
lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/lib${PROJECT_NAME}.a" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/lib${PROJECT_NAME}.a" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/lib${PROJECT_NAME}.a"

# Last touch. copy the header files. Just for convenience
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/include" "${UNIVERSAL_OUTPUTFOLDER}/"
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${LIBRARY_TARGET_NAME}Resources.bundle" "${UNIVERSAL_OUTPUTFOLDER}/"

open "${UNIVERSAL_OUTPUTFOLDER}/"

※このスクリプトCreating a Static Library in iOS Tutorial | Ray Wenderlichに掲載されていたものに少し手を入れたものです。

確認用アプリの設定

確認用アプリから作成したスタティックライブラリを利用できるように設定します。

確認用アプリの Build Phases で、以下のような設定を行います。

  • Target Dependencies
    • スタティックライブラリ、バンドルを追加します。
  • Link Binary With Libraries
    • スタティックライブラリを指定します。
  • Copy Bundle Resources
    • バンドルを追加します。
    • ここでは追加ボタンから参照できないことがあります。そのときは Project Navigator の Products にあるバンドルの項目をドロップして追加してください。

Xamarinからネイティブコードを活用する.pptx 2014-06-10 00-10-12 2014-06-10 00-10-15.png

既存アプリのソースのプロジェクトへの追加

あとは既存のソースをスタティックライブラリのプロジェクトに追加していきます。

ソースコードはスタティックライブラリ、リソースはバンドルをターゲットに指定します。

Xamarinからネイティブコードを活用する.pptx 2014-06-09 21-11-03 2014-06-09 21-11-07.png

また、既存のソースで使用しているフレームワークなどは別途スタティックライブラリに追加しておきます。

Xamarinからネイティブコードを活用する.pptx 2014-06-10 00-01-06 2014-06-10 00-01-14.png

追加したソースの調整

追加したソースのうち、スタティックライブラリにしたことによる調整と、Xamarin側から呼び出すときのインターフェイスを整備します。

リソースの読み込みの修正

リソースの読み込み部分について、下記のように修正します。

  • NSBundle を指定した読み込みの場合、[NSBundle mainBundle] や nil を指定している部分にライブラリのバンドルを明示します。
  • [UIImage imageNamed:] を使用しているところは、ファイル名の先頭にバンドル名を指定します。 ** [UIImage imageNamed:@"hoge.png"] → [UIImage imageNamed:@"EditorResources.bundle/hoge.png"]

バンドルを使用するときは、下記のようなクラスメソッドを用意すると便利です。

static NSBundle *editor_SharedBundle = nil;

+ (NSBundle *)bundle
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        editor_SharedBundle = [NSBundle bundleWithURL:            [[NSBundle mainBundle] URLForResource:@"EditorResources”
                                    withExtension:@"bundle"]];
    });
    return editor_SharedBundle;
}

Xamarin 側から呼び出すインターフェイスを用意する

自分で作るライブラリは外部に公開するインターフェイスを自由に作ることができるので、Objective Sharpieに処理させる部分を最小にすることができれば、ネイティブバインディングを作成するときに手間が少ないです。

そこで、画面ベースで持ってくるのであれば当該のUIViewControllerを作成するクラスだけを公開するとよいでしょう。

#import "Editor.h"
#import <UIKit/UIKit.h>

@implementation Editor

+ (UIViewController *) editorViewController;

@end

プロジェクト内での作業

プロジェクト内で作業を行うときは、ターゲットの指定を以下のように切り替えます。

  • 動作確認をするときはサンプルアプリ
  • スタティックライブラリを作成するときは Universal の Aggregate
    • このとき、ターゲットデバイスは iOS Device に合わせるようにしてください。

Xamarinからネイティブコードを活用する.pptx 2014-06-10 01-18-16 2014-06-10 01-18-20.png

ネイティブバインディングの作成

ユニバーサルライブラリを生成すると、生成されたファイルが格納されているフォルダが表示されます。

この中の .a ファイルを iOS Binding Project へ追加し、.h ファイルを Objective Sharpie に読み込ませて定義生成します。

Xamarinからネイティブコードを活用する.pptx 2014-06-10 01-22-04 2014-06-10 01-22-10.png

ネイティブバインディングライブラリができたら、アプリのプロジェクトに参照設定で追加し、別途出力されているbundleもプロジェクトにリソースとして追加します。

おわりに

既存のプロジェクトをXamarinへ移行する際は、一気にすべてC#でリプレイスする以外にこのようなネイティブライブラリを使用して既存のコードを活用しながら少しずつすすめる方法もあります。プロジェクトの移行の際は検討してはいかがでしょうか。