iOS向けのライブラリを提供するのに、framework形式にすると便利です。
以前に、iOS向けのframeworkのためのテンプレートについて書きました。
iOSのFrameworkを簡単に作れるiOS-Universal-Framework
iOSやXcodeのアップデートに伴って、このテンプレートの保守が終了するということもあって、改めて別の方法を書くことにしました。
この方法は、下記に書かれている内容を参考にしています。
jverkoey/iOS-Framework
ユニバーサルでスタティックなframeworkとは?
iOS向けにライブラリ提供する方法はいくつかありますが、「ユニバーサルでスタティックなframework」が一番便利だと思います。
ユニバーサルというのは、複数の実行環境で利用可能、という意味です。iOSシミュレータ向けに作られたライブラリは実機では使えませんし、逆もそうです。複数の環境向けにライブラリをビルドして結合することで、さまざまな実行環境に対応することができます。
iOSのライブラリは、スタティックライブラリが一般的です。iOS 8からダイナミックライブラリが使えるようになりましたが、iOS 7まではスタティックライブラリしか使えなかったからです。
frameworkという形式は、ライブラリの本体だけでなく、ヘッダーファイルやその他のリソースをまとめて1つのファイルにしたものです。バラバラで提供するよりも、配布も導入も楽です。
作り方
1. Static Libraryのテンプレートからプロジェクト作成
まずは、Xcodeで新規プロジェクトを作成します。Cocoa Touch Static Libraryを指定することで、static libraryを作るプロジェクトを作成できます。
2. ソースコードを追加
このプロジェクトに、ライブラリに含めたいソースコードを追加していきます。
3. Deployment Targetを設定
プロジェクト設定から、iOS Deployment Targetを設定します。
今回は、iOS 4には対応せず、iOS 5以降向けに対応するライブラリを作るため、iOS 5.0を指定しました。
4. Public Headers Folder Pathの設定
Public Headers Folder Pathを設定します。
include/$(PRODUCT_NAME)
5. ヘッダーファイルの追加
Build Phaseの設定のCopy Filesの設定で、ヘッダーファイルを指定します。
6. Run Scriptの追加
最後に、Build Phaseの設定に、Run Script設定を作成して、ビルド後の処理をします。
内容としては、下記のスクリプトを指定します。
set -e
mkdir -p "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/Versions/A/Headers"
# Link the "Current" version to "A"
/bin/ln -sfh A "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/Versions/Current"
/bin/ln -sfh Versions/Current/Headers "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/Headers"
/bin/ln -sfh "Versions/Current/${PRODUCT_NAME}" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/${PRODUCT_NAME}"
# The -a ensures that the headers maintain the source modification date so that we don't constantly
# cause propagating rebuilds of files that import these headers.
/bin/cp -a "${TARGET_BUILD_DIR}/${PUBLIC_HEADERS_FOLDER_PATH}/" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework/Versions/A/Headers"
このスクリプトは、次の段階でframeworkを作るための前準備です。
7. Aggregateターゲット追加
ユニバーサルなframeworkにするために、複数の環境向けのライブラリを結合してパッケージする必要があります。
そのために、Aggregateターゲットを追加します。
8. Target Dependencyに作成したstatic libraryを指定
前のステップまでで作成したstatic libraryを、ここに含めるようにします。
9. アーキテクチャの追加
そのままだと対応できるアーキテクチャが足りない場合があります。lipoコマンドで確認できますが、x86_64とarmv7向けのバイナリしか含まれておらず、i386やarm64では利用できません。
$ lipo -info GrowthbeatCore.framework/GrowthbeatCore
Architectures in the fat file: GrowthbeatCore.framework/GrowthbeatCore are: x86_64 armv7
対応アーキテクチャを追加するために、Build Settingより、Build Active Architecture OnlyをNOにします。そして、Valid Architectureにx86_64とi368を追加します。
これで、i386やarm64へのライブラリが追加されました。
$ lipo -info GrowthbeatCore.framework/GrowthbeatCore
Architectures in the fat file: GrowthbeatCore.framework/GrowthbeatCore are: i386 x86_64 armv7 arm64
10. Run Scriptを追加して複数環境向けのビルドを追加
最後に、複数環境向けのライブラリを結合して、frameworkにパッケージするスクリプトを追加します。
set -e
set +u
# Avoid recursively calling this script.
if [[ $SF_MASTER_SCRIPT_RUNNING ]]
then
exit 0
fi
set -u
export SF_MASTER_SCRIPT_RUNNING=1
SF_TARGET_NAME=${PROJECT_NAME}
SF_EXECUTABLE_PATH="lib${SF_TARGET_NAME}.a"
SF_WRAPPER_NAME="${SF_TARGET_NAME}.framework"
# The following conditionals come from
# https://github.com/kstenerud/iOS-Universal-Framework
if [[ "$SDK_NAME" =~ ([A-Za-z]+) ]]
then
SF_SDK_PLATFORM=${BASH_REMATCH[1]}
else
echo "Could not find platform name from SDK_NAME: $SDK_NAME"
exit 1
fi
if [[ "$SDK_NAME" =~ ([0-9]+.*$) ]]
then
SF_SDK_VERSION=${BASH_REMATCH[1]}
else
echo "Could not find sdk version from SDK_NAME: $SDK_NAME"
exit 1
fi
if [[ "$SF_SDK_PLATFORM" = "iphoneos" ]]
then
SF_OTHER_PLATFORM=iphonesimulator
else
SF_OTHER_PLATFORM=iphoneos
fi
if [[ "$BUILT_PRODUCTS_DIR" =~ (.*)$SF_SDK_PLATFORM$ ]]
then
SF_OTHER_BUILT_PRODUCTS_DIR="${BASH_REMATCH[1]}${SF_OTHER_PLATFORM}"
else
echo "Could not find platform name from build products directory: $BUILT_PRODUCTS_DIR"
exit 1
fi
# Build the other platform.
xcrun xcodebuild -project "${PROJECT_FILE_PATH}" -target "${TARGET_NAME}" -configuration "${CONFIGURATION}" -sdk ${SF_OTHER_PLATFORM}${SF_SDK_VERSION} BUILD_DIR="${BUILD_DIR}" OBJROOT="${OBJROOT}" BUILD_ROOT="${BUILD_ROOT}" SYMROOT="${SYMROOT}" $ACTION
# Smash the two static libraries into one fat binary and store it in the .framework
xcrun lipo -create "${BUILT_PRODUCTS_DIR}/${SF_EXECUTABLE_PATH}" "${SF_OTHER_BUILT_PRODUCTS_DIR}/${SF_EXECUTABLE_PATH}" -output "${BUILT_PRODUCTS_DIR}/${SF_WRAPPER_NAME}/Versions/A/${SF_TARGET_NAME}"
# Copy the binary to the other architecture folder to have a complete framework in both.
cp -a "${BUILT_PRODUCTS_DIR}/${SF_WRAPPER_NAME}/Versions/A/${SF_TARGET_NAME}" "${SF_OTHER_BUILT_PRODUCTS_DIR}/${SF_WRAPPER_NAME}/Versions/A/${SF_TARGET_NAME}"
あと、下記も追加しておくと、ビルド後にFinderが開いてすぐに完成したframeworkを見えるので便利です。
# Open products directory
open "${BUILT_PRODUCTS_DIR}"
以上で完了です!