Koichi Adachi

smart phone app development tips

自作ライブラリをシームレスにObjCからSwiftへ移行する話(iOS Advent Calendar 2016/12/15)

iOS Advent Calendar 2016 - Qiitaの15日目の記事です。

これまでObjCで書いていたライブラリを'少しずつ'Swiftに移行する話です。

自己紹介

フリーランスiOSAndroid、Web上で動くライブラリの開発をしています。 最近はEC2のインスタンス上にSwift Linuxで作った簡単なプロジェクトをDockerに入れて起動してみたり・・・といったことがマイブームです。

動機

仕事に適用する前に、まずは個人プロジェクトで作り続けてきたライブラリプロジェクトをSwift化しようと思ったのがきっかけです。 WWDC16のラボで聞いたところ、「iOS8以上だったらSwiftで.frameworkを書けるよ!」とAppleのエンジニアの方にお聞きしたため これはチャンス!と思いやってみました。

このトピックで実現すること

  • 既存ライブラリ(ObjC)のプロジェクトはそのまま残す。
  • Swiftで書きたい(移行したい)ロジックから新規ライブラリプロジェクトに記述(移動)する
  • AppからはSwiftライブラリとObjCライブラリの両方のクラスを呼び出せるようにする

制約

  • この構成ではObjCライブラリ<->Swiftライブラリの相互参照はできないため、  Swiftライブラリ->ObjCライブラリという参照関係になる
  • iOSのDeployment Targetは8.0以上

省略しているところ(このトピックでの本質的な話ではないため)

  • bitcodeをオンにする(Build Settingsの3項目)
  • Swift<->ObjCのbridging headerとか
  • アプリがObjCプロジェクトだったらAlways Embed Swift Standard LibrariesをYESにする

初期状態

f:id:sgspecial:20161214223929p:plain

  • workspaceで構成されていること
  • ObjCで作られたframework(以下ObjCFramework)を参照している

ステップ1 新規作成したSwiftコード用frameworkをworkspaceに追加

f:id:sgspecial:20161214223958p:plain

SwiftFrameworkのBuildPhaseに、ObjCFrameworkへの参照を追加する

f:id:sgspecial:20161214224034p:plain

アプリのターゲットのBuild Phase内、「Link Binary With Libraries」と「Embed Framework」にSwiftFrameworkへの参照を追加する

f:id:sgspecial:20161214224100p:plain

アプリのコードからそれぞれのロジックを呼んでみる

f:id:sgspecial:20161214224139p:plain

動いた! f:id:sgspecial:20161214224150p:plain

これでアプリからもSwiftで作ったFrameworkからもObjCで作ったライブラリを参照できた。

f:id:sgspecial:20161214224201p:plain イメージ

ただ、これだとframeworkを2つ参照しなければならない。それは面倒。

SwiftFrameworkにframeworkを同梱できたらいいのに。

frameworkを消して「Runpath Search Paths」と「Framework Search Path」に記述を追加

f:id:sgspecial:20161214224221p:plain Runpath Search Pathには
@executable_path/Frameworks/UmbrellaSwiftFramework.framework/Frameworks

f:id:sgspecial:20161214224235p:plain Framework Search Pathには
$(PROJECT_DIR)/UmbrellaSwiftFramework.framework/Frameworks

動いた!

けどiTunesConnectにプッシュしてみるとエラーが出る。 hogehoge.frameworkにFrameworkフォルダやbundleが入っているとiTunesConnectにアップできないようです。

f:id:sgspecial:20161214224305p:plain

Releaseビルドではフォルダを変更する。

iTunesConnectへのアップ時にパスするフォルダ構成にすればいいだけなので、 下記のスクリプトをアプリの「Run Script」に追加する。

if [ "${CONFIGURATION}" != "Release" ]; then
exit;
fi
mkdir ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ObjCFramework.framework
mv -f ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/UmbrellaSwiftFramework.framework/Frameworks/ObjCFramework.framework ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}
rm -rf ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/UmbrellaSwiftFramework.framework/Frameworks

f:id:sgspecial:20161214224320p:plain

成功!

まとめ

アプリを作る時にはよくライブラリを作ると思いますが、
一気にSwift化するのがつらい時にこの手の方法が使えるかと思います。
個人開発者でも仕事で作っているアプリでも、優先順位やビジネスへのインパクトなど様々な事情があります。
全ての開発者が必ずしも 全てのコードをSwift化する 工数を確保できるとは限りません。 (自分) そのような時、部分的にでもSwift化できればいいかなと思い調べてみました。

特にAndroid(Java, Kotlin)やWeb(JS)などで同時開発をしたい時に、ObjCよりはソースコードの比較がしやすくなるため、
このような形でSwiftへ移行するのはありなのではないでしょうか。
どなたかのお役に立てれば幸いです。