しめ鯖日記

swift, iPhoneアプリ開発, ruby on rails等のTipsや入門記事書いてます

Swiftでframeworkを作成する

アプリ開発で良く出てくる.frameworkという拡張子のライブラリを自作してみました。

frameworkを作成する

Xcodeで新規プロジェクト作成時にFrameworkを選択します。

f:id:llcc:20200412163759p:plain

プロジェクトは下のようなファイル構成になっています。

f:id:llcc:20200412164450p:plain

Productsの中に入っているMyFramework.frameworkの構成は下の通りです。

f:id:llcc:20200412164630p:plain

続けてライブラリにファイルを追加してみます。
追加したのは下のようにメソッドが一つだけあるクラスです。

別プロジェクトから呼び出すためにアクセス修飾子はpublicにしています。

import UIKit

public class MyClass {
    static public func test() {
        print("MyFramework")
    }
}

ビルドするとMyFramework.frameworkも更新されます。
Finderで見るとSwift用のヘッダーが作られている事が分かります。

f:id:llcc:20200412164908p:plain

frameworkを使ってみる

まずはプロジェクトにライブラリを追加します。
追加はXcodeFrameworks, Libraries, and Embedded Contentにframeworkファイルをドラッグするだけです。

f:id:llcc:20200412165056p:plain

以下のように書くことでMyClassのメソッドを使う事ができます。

import UIKit
import MyFramework

class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        MyClass.test()
    }
}

Frameworkがプロジェクト外に配置されている場合、Framework Search Pathsにライブラリのパスを追加する必要があります。

f:id:llcc:20200412165840p:plain

frameworkのユニバーサル対応

上記の方法では、シミュレータか実機のどちらかでだけ動くライブラリのみ作れます。
これを両方の環境で動くように修正してみようと思います。

修正は下URLを参考にしました。

Swiftでフレームワークを作成する(第1回) | GMOアドパートナーズグループ TECH BLOG byGMO

まずは両対応ライブラリをつくためのターゲットを作成します。
XcodeのFile → New → Targetからターゲットを選んで、その中のAggregateを選びます。

f:id:llcc:20200412175631p:plain

AggregateにRun scriptを追加して下コードを貼り付けます。

#!/bin/sh
 
UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal
 
# make sure the output directory exists
mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"
 
# Step 1. Build Device and Simulator versions
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
 
# Step 2. Copy the framework structure (from iphoneos build) to the universal folder
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework" "${UNIVERSAL_OUTPUTFOLDER}/"
 
# Step 3. Copy Swift modules from iphonesimulator build (if it exists) to the copied framework directory
SIMULATOR_SWIFT_MODULES_DIR="${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule/."
if [ -d "${SIMULATOR_SWIFT_MODULES_DIR}" ]; then
cp -R "${SIMULATOR_SWIFT_MODULES_DIR}" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule"
fi
 
# Step 4. Create universal binary file using lipo and place the combined executable in the copied framework directory
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}"
 
# Step 5. Convenience step to copy the framework to the project's directory
cp -R "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework" "${PROJECT_DIR}"
 
# Step 6. Convenience step to open the project's directory in Finder
open "${PROJECT_DIR}"

あとはAggregateを実行するだけで両対応フレームワークを作る事ができます。

続けて上記スクリプトの内容を見ていきます。
下ではユニバーサルフレームワーク用のディレクトリの作成をしています。

#!/bin/sh
 
UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal
 
# make sure the output directory exists
mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"

以下ではシミュレータ/実機それぞれ向けのフレームワークを作っています。
xcodebuildはXcodeに付いているコマンドラインツールで、プロジェクトのビルドをする事ができます。

# Step 1. Build Device and Simulator versions
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

以下では生成したフレームワークやモジュールファイルのコピーをしています。

# Step 2. Copy the framework structure (from iphoneos build) to the universal folder
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework" "${UNIVERSAL_OUTPUTFOLDER}/"
 
# Step 3. Copy Swift modules from iphonesimulator build (if it exists) to the copied framework directory
SIMULATOR_SWIFT_MODULES_DIR="${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule/."
if [ -d "${SIMULATOR_SWIFT_MODULES_DIR}" ]; then
cp -R "${SIMULATOR_SWIFT_MODULES_DIR}" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule"
fi

以下ではlipoというツールを使ってユニバーサルフレームワークを作っています。
lipoはXcodeに付属しているツールで、シミュレータ/実機向けのフレームワークを結合する事ができます。

# Step 4. Create universal binary file using lipo and place the combined executable in the copied framework directory
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}"

最後にプロジェクトのルートディレクトリにフレームワークをコピーしています。

# Step 5. Convenience step to copy the framework to the project's directory
cp -R "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework" "${PROJECT_DIR}"
 
# Step 6. Convenience step to open the project's directory in Finder
open "${PROJECT_DIR}"

実際にUniversal対応になったかどうかはfileコマンドで確認する事ができます。

file MyFramework.framework/MyFramework

fileコマンドを実行すると下のように両環境に対応できている表示が出てきます。

MyFramework.framework/MyFramework: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit dynamically linked shared library x86_64] [arm64:Mach-O 64-bit dynamically linked shared library arm64]
MyFramework.framework/MyFramework (for architecture x86_64):    Mach-O 64-bit dynamically linked shared library x86_64
MyFramework.framework/MyFramework (for architecture arm64): Mach-O 64-bit dynamically linked shared library arm64

iOSシミュレータ用と実機用のバイナリの場合は下のような表示になります。

# iOSシミュレータ
MyFramework.framework/MyFramework: Mach-O 64-bit dynamically linked shared library x86_64
# 実機
MyFramework.framework/MyFramework: Mach-O 64-bit dynamically linked shared library arm64