この辺の知識は大事だけど、使ってないと忘れるので完全に自分用のメモとして残しておきます。(昔書いてて、下書きにあったやつです)
ライブラリとは何か
ライブラリには複数の種類がある
作成された言語による違い
見た目ではわからないが、作成された言語によって微妙に違う
- Swift
- Objective-C
- C
たとえば、Swiftのframeworkでは、.swiftmodule
というPublicなAPIを含んだバイナリファイルがあり、Objective-C/Cは、.modulemap
というmodule map言語で記述されているテキストファイルがある
swiftmodule
Swiftファイルを以下のようにコンパイルすると.swiftmodule
と.swiftdoc
ができる。
$ swiftc -emit-module <...>.swift <...>.swift -module-name <...>
modulemap
.modulemap
を作成して、
CPUアーキテクチャによる違い
見た目ではわからないが、対象CPUによって微妙に違う。
- Intel
- ARM
それぞれ32bit、64bitがある。
それぞれ必要になるので、Universal Binaryを作る必要がある。
作るコマンドは以下。
(ちなみにこの方法だとSimulator用と実機用をframeworkに入れておくとApp Storeに提出できないので、Simulator用を削除する必要がある。Carthage利用時は、carthage copy-frameworks
でそれを解決している)
$ xcrun lipo -create <...> <...> -output <...>
詳細に書くとフレームワークのBuild Phaseで以下のようなRunScriptを記述する
#!/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}"
もしくは、XCFramework形式で作る。(この方法は複数のアーキテクチャ/プラットフォームの.framework
を1つにまとめた.xcframework
というディレクトリを作る)
$ xcodebuild archive -scheme MyFramework-iOS \
-destination "iphoneos/platform=iOS" \
-archivePath build/iphoneos \
SKIP_INSTALL=NO
$ xcodebuild archive -scheme MyFramework-iOS \
-destination "iphoneos/platform=iOS Simulator" \
-archivePath build/iphonesimulator \
SKIP_INSTALL=NO
$ xcodebuild -create-xcframework \
-framework build/iphoneos.xcarchive/Products/@rpath/MyFramework.framework \
-framework build/iphonesimulator.xcarchive/Products/@rpath/MyFramework.framework \
-output MyFramework.xcframework
どんなアーキテクチャが含まれているかの確認コマンド
$ xcrun lipo -info <...>
拡張子
見た目で違うのでわかりやすい。
.framework (ダイナミックフレームワーク、スタティックフレームワーク)
それぞれモジュールが含まれるものと含まれないものがある。(インポート時に必要なもので、PublicなApiの宣言をしているだけ)
フレームワークとは、バンドルを持つもの(バンドルを持たないものはライブラリと呼ばれる)。
バンドルとは、コード以外のものを含むもの。便利に使うための一定のルールに基づいたディレクトリ構造な形式のもの。
.frameworkのディレクトリ構造は以下のようになっています。
MyFramework.framework
├── Headers
│ ├── MyFramework-Swift.h
│ └── MyFramework.h
├── Info.plist
├── Modules
│ ├── MyFramework.swiftmodule
│ │ ├── Project
│ │ │ ├── arm64-apple-ios.swiftsourceinfo
│ │ │ ├── arm64.swiftsourceinfo
│ │ │ ├── x86_64-apple-ios-simulator.swiftsourceinfo
│ │ │ └── x86_64.swiftsourceinfo
│ │ ├── arm64-apple-ios.swiftdoc
│ │ ├── arm64-apple-ios.swiftmodule
│ │ ├── arm64.swiftdoc
│ │ ├── arm64.swiftmodule
│ │ ├── x86_64-apple-ios-simulator.swiftdoc
│ │ ├── x86_64-apple-ios-simulator.swiftmodule
│ │ ├── x86_64.swiftdoc
│ │ └── x86_64.swiftmodule
│ └── module.modulemap
└── MyFramework
.dylib
- ダイナミックライブラリのこと
.a
スタティックライブラリのこと
リンクとインポート
リンクとは何か
コンパイルによって生成されたオブジェクトファイルをリンカによって結合する処理をリンクと呼ぶ。全てのシンボルはこのときに解決される。
2種類
- スタティックリンク
リンクの際にターゲットを結合して一つの実行ファイルにする。ビルド時にシンボル(クラスやメソッドなど)はすべて解決される。
リンクしたときに問題がなければ、実行時にも問題が起きにくい。
複数のライブラリをリンクするときにそれぞれで同じライブラリを使っているとシンボルの衝突が起きやすい。
- ダイナミックリンク
ビルド時はシンボルの参照だけが設定される。シンボルの解決は実行時にされる。
リンクが成功してても、実行時に問題になることがある。
当然、複数のライブラリからそれぞれ同じライブラリを使うことは可能。
見分け方
fileコマンドを使う
$ file MyFramework.framework/MyFramework
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
フレームワークのリンクの仕方
FRAMEWORK_SEARCH_PATHS
にフレームワークのディレクトリを指定する。
Auto Linkingが無効の場合は、Other Swift Flags
= OTHER_LDFLAGS
に -framework MyFramework
のように指定する。
Automatic Linking
Swiftは自動でリンク機能が働く。インポート=リンクになる。
たとえば、Other Swift Flags
に -Xfrontend -disable-autolink-framework -Xfrontend MyFramework
とすると無効化することができる。
Ohter Linker Flags
に -framework MyFramework
を記述すると明示的にリンクされる。
ライブラリのリンクの仕方
LIBRARY_SEARCH_PATHS
にライブラリのディレクトリを指定する。
OTHER_LDFLAGS
に -l<ライブラリ名>
を指定する。(例: libssl.aの場合は、-lssl
)
インポートとは何か
外部ライブラリが公開しているシンボル(クラスやメソッドなど)を自分のコードで利用可能にするための言語機能で、Swiftの場合は、コンパイル時点で解決される
Swiftのframeworkで以下のようにswiftmodule
があれば、インポートができる。
MyFramework.framework
└── Modules
└── MyFramework.swiftmodule
├── arm64-apple-ios.swiftmodule
├── arm64.swiftmodule
├── x86_64-apple-ios-simulator.swiftmodule
└── x86_64.swiftmodule
Swift以外は、module.modulemap
ファイルがある。
モジュールとは
Frameworkにバンドルする必要がなくインポート時に必要なもので、PublicなApiの宣言。
モジュールがないフレームワークの場合(ObjectiveーC/C言語製)は、ヘッダファイルをmodulemapでモジュールに変換する必要がある。
Swiftの場合は、コンパイラが自動生成する。
Module Mapとは、Bridging Headerの上位互換。(汎用性が高い)
Bridging Headerはアプリケーションターゲットでしか使えない(独自フレームワークでは使えない)。
Bridging Headerはグローバルに作用するので、必要なところだけでimportすることができない。
XcodeでDynamic Frameworkを追加するとどうなるか
以下の追加をした場合

MySample.xcodeproj/project.pbxproj
で、二つの重要なことが起きる。
一つは、Build Phaseに Embed Frameworks
という名前で対象のフレームワークをアプリケーションのFrameworksディレクトリにコピーする処理が追加されたこと
+/* Begin PBXCopyFilesBuildPhase section */
+ 9F9ACFCD24CB318B003C2EBA /* Embed Frameworks */ = {
+ isa = PBXCopyFilesBuildPhase;
+ buildActionMask = 2147483647;
+ dstPath = "";
+ dstSubfolderSpec = 10;
+ files = (
+ 9F9ACFCC24CB318B003C2EBA /* MyFramework.framework in Embed Frameworks */,
+ );
+ name = "Embed Frameworks";
+ runOnlyForDeploymentPostprocessing = 0;
+ };
+/* End PBXCopyFilesBuildPhase section */

もう一つは、Build Settingsに FRAMEWORK_SEARCH_PATHS
が追加されこと
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_STYLE = Automatic;
+ FRAMEWORK_SEARCH_PATHS = (
+ "$(inherited)",
+ "$(PROJECT_DIR)",
+ );
INFOPLIST_FILE = MySample/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",

Frameworksディレクトリにコピーする処理とは
Build Phaseに Embed Frameworks
を削除してみるとビルドは成功するが実行時に以下のエラーが出る。
dyld: Library not loaded: @rpath/MyFramework.framework/MyFramework
Referenced from: /Users/ko2ic/Library/Developer/CoreSimulator/Devices/1137B367-CB15-4A40-8D09-214A3FBFFF5A/data/Containers/Bundle/Application/FB55EB6E-0F63-4FE8-A7F3-95F68CFD2405/MySample.app/MySample
Reason: image not found
dyld
は動的リンカのことで、それが失敗しているということ。
FRAMEWORK_SEARCH_PATHSとは
フレームワークでモジュールを持つ場合にモジュールのあるフレームワークのパスを指定する場所。
ちなみにフレームワークでモジュールを持たない場合(やライブラリをインポートしたい場合)は、SWIFT_INCLUDE_PATHS
でmodulemapのある親ディレクトリを指定する。
ダイナミックフレークワークで利用されるpath
例
~/Library/Developer/CoreSimulator/Devices/xxxxxx/data/Containers/Bundle/Application/zzzzzzz/MySample.app/MySample
アプリ実行時のアプリ自体のファイルパス
ローダ(dyld)が読み込もうとしているライブラリのパス
Xcodeでは、Runpath Search Paths (LD_RUNPATH_SEARCH_PATH) で設定する。
リンカが検索するパス。動的ライブラリを見つけるためにパスのリストを順に検索する。
参考
https://wincent.com/wiki/%40executable_path%2C_%40load_path_and_%40rpath
https://gist.github.com/cromandini/1a9c4aeab27ca84f5d79#file-universal-framework-sh
https://speakerdeck.com/kishikawakatsumi/swiftniokeruinpototorinkufalseshi-zu-miwotan-ru