iOSで自作したライブラリやSDKを公開する際に気をつけたいこと
島根ライフをエンジョイして1年になるasakaharaです。
今回は以前勉強会で発表した内容を整理して公開しています。
以前iOSでSDKの開発・公開をしたことがあるのですが、あまり手順がまとまったサイトが多くない点やiOS7など以前のOSもサポートする際に注意点がいくつかあるので整理してみました。
目次
選択できるライブラリの種類
まずiOSで選択できるライブラリの種類は下記の通りです。
- Xcode 5まではiOSではCocoa Touch Static Libraryのみ
- Xcode 6からCocoa Touch Frameworkを選択することでFrameworkを簡単に作成できる
Static Libraryは以下の特徴を持っています。
- アプリケーションのコンパイル時に組み込まれる形で(静的に)リンクされるライブラリ
- コンパイル時に組み込まれるので、その分アプリケーションのサイズが大きくなる
- 画像やNibなどのリソースを含むことができない
次にFrameworkについてですが、ドキュメントからの直訳をするとこんな説明になります。
動的共有ライブラリ、nib、画像、ローカライズファイル 、ヘッダーファイル、ドキュメント等のリソースファイルを1つのパッケージにまとめたディレクトリ構造のことを指します。
実際のFrameworkのディクレトリ構造はこんな感じです。
説明より実際の構造を見る方がずっと分かりやすいかと思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
[sourcecode lang="java"] MyFramework.framework/ Headers ->; Versions/Current/Headers MyFramework ->; Versions/Current/MyFramework Resources ->; Versions/Current/Resources Versions/ A/ Headers/ MyHeader.h MyFramework Resources/ English.lproj/ Documentation InfoPlist.strings Info.plist B/ Headers/ MyHeader.h MyFramework Resources/ English.lproj/ Documentation InfoPlist.strings Info.plist Current ->; B [/sourcecode] |
上記の例を見ると分かると思いますが、複数のバージョンをFramework内に含むことも可能です。
この辺りの情報は公式ドキュメントに詳しい説明があるので、もっと詳細を知りたい方はこちらを参考にしてください。
Embedded Frameworkについて
Xcode6からは新たにコードをシェアする仕組みとしてEmbedded Frameworkが登場しました。
最初に紹介したCocoa Touch Frameworkを選択することで作成することができます。
特徴としてはこんな感じです。
- iOS8、Xcode6から使用可能
- Frameworkを作るのがものすごく簡単になった(ビルド用のスクリプトなど必要なくなった)
- App Extensionsを使用するケースなどを想定し、簡単に複数ターゲットから参照・利用することができるようになった
とても便利なEmbedded Frameworkですがいくつか注意点もあります。
まずDeployment Targetを8.0以降にしたアプリにしか組み込めません。
実際にはSwiftで実装する場合、frameworkのDeployment Targetを7.0にしておけば、7.0以降のアプリにも組み込みは問題なくできるんですが、App Storeへのアップロード時にエラーになってしまいます。
そのためiOS8より以前をサポートしたい場合は、Embedded FrameworkではなくStatic LibraryをObjective-Cで作成する必要があります。
ちなみにSwiftはStatic Libraryをサポートしていませんので、こちらもご注意ください。
Universal Framework
作成したFrameworkを配布する際に実機だけでなくシミュレータでも動作するようにする必要があります。
そのために実機のアーキテクチャだけでなくシミュレータのアーキテクチャを含ませることでこれを実現できます。
ここでは簡単に作成方法を紹介しておきます。
- New -> Target -> Other -> Aggregate でUniversal Framework作成用のターゲットを作る
- 作成したターゲット -> Build Phases -> +ボタン -> New Run Script PhaseでRun Scriptを追加
こちらが追加するスクリプトです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
[sourcecode lang="java"] !/bin/sh UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal mkdir -p "${UNIVERSAL_OUTPUTFOLDER}" # ビルドする xcodebuild -target "${PROJECT_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build xcodebuild -target "${PROJECT_NAME}" -configuration ${CONFIGURATION} -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build # iphoneos配下のframeworkをコピーする cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework" "${UNIVERSAL_OUTPUTFOLDER}/" # iphoneos配下のswiftmoduleをコピーする # cp -R "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule/." "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule" # lipoコマンドでUniversal binaryを作成 lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework/${PROJECT_NAME}" # プロジェクトフォルダ配下にframeworkをコピー cp -R "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework" "${PROJECT_DIR}" [/sourcecode] |
あとはXcodeから作成したターゲットを選択してビルドすると両方のアーキテクチャに対応したFrameworkが作成されます。
Bitcode
Frameworkをビルドする際の注意点としてBitcodeについても軽くふれておきます。
iOS9からアプリ最適化の仕組みとしてApp Thinningが登場したのですが
そのためXcode7からはアプリへのBitcodeの埋め込みがデフォルトで有効になっています。
App Thinningについてはこちらに詳しく記載されています。
Frameworkを作成する際もBitcodeの埋め込みが必要になりますが、実はXcodeでArchiveを行う以外はデフォルトではBitcodeが埋め込まれないという問題があります。
例えば上記のスクリプトのようにxcodebuildコマンドでビルドする場合も同様です。
その対応方法としてBuild SettingsのUser-DefinedにBITCODE_GENERATION_MODEをbitcodeと設定することでこの問題を解決できます。
App Storeへ申請する際の注意点
最後に作成したFrameworkをアプリに組み込んで公開する際の注意点を書いておきます。
シミュレータ用のアーキテクチャであるi386やx86_64がEmbedded Frameworkに含まれていると申請時にサポートしていないアーキテクチャがあるとしてエラーになります。
エラーの詳細はこちらを見てください。
対応方法としてはアーカイブ時にRun Scriptでi386やx86_64のアーキテクチャを削除する方法などがあります。
この対応方法としてすでにいくつかの参考となるサイトがありますのでそれらを紹介しておきます。
Realmではstrip-frameworks.shという専用のスクリプトを用意してます
• https://github.com/realm/realm-cocoa/blob/d59c86f11525f346c8e8db277fdbf2d9ff990d98/scripts/strip-frameworks.sh
こちらは削除方法や削除するためのスクリプトとその説明が記載してある記事で参考になります
• http://ikennd.ac/blog/2015/02/stripping-unwanted-architectures-from-dynamic-libraries-in-xcode/
まとめ
ではこれまで話した注意点を踏まえたまとめです。
- iOS8より以前をサポートするならStatic Library(静的 Framework)を使いObjective-Cで実装する
- iOS8以降のサポートでよいならSwiftでEmbedded Frameworkを使うのが一番手軽にできる
- 可能な限り開発者の負担を下げるためCocoaPods、Cartago(今後はSwift Package Managerも)で導入できるようにするとよい
次回はPackage Managerがどのように動いているかについて書いてみたいですね。
それではまた!