iOS向けのユニバーサルでスタティックなframeworkを作る

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を作るプロジェクトを作成できます。
スクリーンショット 2014-11-07 7.51.15

2. ソースコードを追加

このプロジェクトに、ライブラリに含めたいソースコードを追加していきます。

3. Deployment Targetを設定

プロジェクト設定から、iOS Deployment Targetを設定します。
スクリーンショット 2014-11-07 7.56.00
今回は、iOS 4には対応せず、iOS 5以降向けに対応するライブラリを作るため、iOS 5.0を指定しました。

4. Public Headers Folder Pathの設定

Public Headers Folder Pathを設定します。

include/$(PRODUCT_NAME)

5. ヘッダーファイルの追加

Build Phaseの設定のCopy Filesの設定で、ヘッダーファイルを指定します。
スクリーンショット 2014-11-07 8.04.03

6. Run Scriptの追加

最後に、Build Phaseの設定に、Run Script設定を作成して、ビルド後の処理をします。
スクリーンショット 2014-11-07 7.57.06
内容としては、下記のスクリプトを指定します。

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ターゲットを追加します。
スクリーンショット 2014-11-07 8.05.43

8. Target Dependencyに作成したstatic libraryを指定

前のステップまでで作成したstatic libraryを、ここに含めるようにします。
スクリーンショット 2014-11-07 8.10.22

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を追加します。
スクリーンショット 2014-11-07 8.13.09
これで、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}"

以上で完了です!

タイトルとURLをコピーしました