iOSビルドについて学習ログ
読んでみた: [iOSDC Japan 2019 リポート]「ライブラリのインポートとリンクの仕組み完全解説」というセッションを聞いてきました
- この記事は、セッションSwiftにおけるインポートとリンクの仕組みを探るのレポート
- 「ライブラリの形式と違い」
- ?: ダイナミックフレームワーク、スタティックフレームワークの区別って、単純に、ダイナミックライブラリを保持しているかスタティックライブラリを保持しているか?両方保持することも可能?
- ?: 「モジュールを含む...フレームワーク」「モジュールを含まない...フレームワーク」のモジュールとは?
- ?: モジュールなしだとどうなる?Swiftからは呼べない?けどObjective-Cからは呼べる?
- フレームワークとライブラリの違い:バンドルを持つか持たないか。
- ?: バンドルは何が嬉しい?リソースをもてるとか、ライブラリをラップして使いやすくできるとかなのかな。
- ?: バンドルを持たないライブラリをSwiftから呼び出す方法。
- スタティックリンク、ダイナミックリンク
- ?: スタティックライブラリ、ダイナミックライブラリをSwiftから利用する方法
- fileコマンドで調べることができる。
- 「インポートとリンク(Xcodeを使用して設定)」
- インポート
- ライブラリが公開しているシンボル(クラス・関数など)を自分のコードで参照できるようにする。
- 各ファイル(コンパイル単位)をコンパイルする時点で解決される。
- リンク
- ソースコードをコンパイルして生成されたオブジェクトファイル、および外部ライブラリをすべて連結(リンク)して1つの実行形式ファイル、あるいはライブラリを生成する
- オブジェクトファイルとは?
- ソースコードから生成されるバイナリコード(機械語)のファイル。単体だと動かなくてリンカでリンクすると実行できるようになる。
オブジェクトファイル (object file) またはオブジェクトコード (object code) とは、コンパイラがソースコードを処理した結果生成される(たいていはアセンブリ言語による assembler code file と、アセンブラによるそれのアセンブルを経由している)、コード生成の結果にしてバイナリコードを含む中間的なデータ表現のファイルである。オブジェクトファイルは共有ライブラリのようにも使われることがある。
https://ja.wikipedia.org/wiki/オブジェクトファイル
- ソースコードから生成されるバイナリコード(機械語)のファイル。単体だと動かなくてリンカでリンクすると実行できるようになる。
- オブジェクトファイルとは?
- 全てのソースコードをコンパイルした後に行われる
- ソースコードをコンパイルして生成されたオブジェクトファイル、および外部ライブラリをすべて連結(リンク)して1つの実行形式ファイル、あるいはライブラリを生成する
- (Embedded Binariesに.frameworkをドラッグアンドドロップすると)「Xcodeが設定するもの」
- FRAMEWORK_SEARCH_PATHSにフレームワークがあるディレクトリの親ディレクトリを追加
- これを削除すると、
import Logger
でNo such module 'Logger'
のコンパイルエラー。
- これを削除すると、
- Linked Frameworks and Librariesにフレームワークを追加
- Xcode11からは、Frameworks, Libraries, and Embedded Contentにドラッグアンドドロップし、Embeddedかどうかはプルダウンから選ぶ。
- ?: このEmbeddedとは?バンドルに含めるということなのだろうか。
- その通りみたい。static frameworks and librariesはリンクされるからembedしなくていい。dynamic frameworks and librariesはする必要がある。
Do not embed static frameworks and libraries (linking happens at build time), only shared ones (dynamic linking happens at run time, so they need to be in your bundle).
https://stackoverflow.com/a/58860158/8834586
- その通りみたい。static frameworks and librariesはリンクされるからembedしなくていい。dynamic frameworks and librariesはする必要がある。
- ?: このEmbeddedとは?バンドルに含めるということなのだろうか。
- Xcode10までは、スライドのように、Embedded BinariesとLinked Frameworks and Librariesとで分かれていた。分かれている方がわかりやすい気がする...。
- Xcode11からは、Frameworks, Libraries, and Embedded Contentにドラッグアンドドロップし、Embeddedかどうかはプルダウンから選ぶ。
- Build PhaseにCopy Files Phaseを追加してフレームワークをアプリケーションバンドルのFrameworksディレクトリにコピーする
- このCopy Files Phaseを削除すると、実行時に
dyld: Library not loaded: @rpath/Logger.framework/Logger
エラー。リンクは通っている。
- このCopy Files Phaseを削除すると、実行時に
- ?: ここで、Embedded Binariesにdrag & dropしているのは、dynamic frameworkのはず。static frameworkは、どうやって追加するのか。
- Xcode10までの場合、Embedded BinariesではなくLinked Frameworks and Librariesにdrag & dropするのかな。
- Xcode11以降なら、Frameworks, Libraries, and Embedded Contentにdrag & dropして、embeddedは選択しない。
- FRAMEWORK_SEARCH_PATHSにフレームワークがあるディレクトリの親ディレクトリを追加
-
-disable-autolink-framework
をOTHER_SWIFT_FLAGS
に指定しAutomatic LInkingを無効にすると、ビルドのldコマンド実行中にリンクエラー。Undefined symbol: ...
。 - 大まかにまとめると...
- FRAMEWORK_SEARCH_PATH -> import文の解決に使われる
- automatic linking(デフォルトON) -> import文の解決と同じ方法でリンク先を探して?自動でリンク。
- 例のLogger.frameworkでも必要なことから、ダイナミックフレームワークでもリンクの処理は行われるみたい。
- スライドでautomatic linkingを無効にする前にLinked Frameworks and Librariesを消しているけど、このLinked Frameworks and Librariesは意味がないのかな?
- Copy Files Phase -> 実行時にダイナミックリンクできるよう、バンドルに組み込む
- インポート
- 「インポートとリンク(マニュアル作業で設定)」
- フレームワークからLogger.framework/Modules/Logger.swiftmoduleディレクトリの.swiftmoduleだけ残して他を削除すると、
import Logger
でコンパイルエラーにならないが、ビルドのldコマンド実行時にld: framework not found Logger
エラーになる。
- フレームワークからLogger.framework/Modules/Logger.swiftmoduleディレクトリの.swiftmoduleだけ残して他を削除すると、
- モジュールとは
- Logger.framework/Modules/Logger.swiftmoduleディレクトリ(内の.swiftmodule等)がモジュール
- アーキテクチャごとに作られているみたい。arm64.swiftmoduleとx86_64.swiftmoduleみたいな。
- .swiftdocとは?名前通りAPIのドキュメントかな。
- ?: .swiftmodule内はどうなっているのか?
- BUILD_LIBRARY_FOR_DISTRIBUTION = YESを指定すると.swiftinterfaceが生成される。
- ?: これの目的がよくわからない。これがなくても利用側で
import Logger
できていたので。 - ライブラリに使ったコンパイラバージョンと違うコンパイラからも使えるようになるModule Stabilityを使うためのものらしい。もともとできなかったのか。コンパイルする時にimport対象をコンパイルしたコンパイラのバージョンをチェックしていた?
Module Stability は異なるバージョンのコンパイラから生成された Module をコンパイル(≒インポート)できるようにするための機能で、Swift5.1 からサポートされました。これにより、Swift5.1 以降でコンパイルされた Module は、Swift5.1 以降に違うバージョンのコンパイラから正常にインポートできるようになります。ただし、これには、.swiftinterface というモジュール記述ファイルが必要で、ライブラリ側で BUILD_LIBRARY_FOR_DISTRIBUTION が Yes に設定されていない場合には、.swiftinterface が生成されず、Module Stability の恩恵を受けることができません。
https://yamatooo.blog/entry/2020/10/08/083000
- ?: これの目的がよくわからない。これがなくても利用側で
- Swift以外だと(スライドではObjective-Cのフレームワークが挙げられている)、xxx.framework/Modules/module.modulemapのようになる。
- ?: なぜ、アーキテクチャごとにファイルを用意しなくていいのか?
- 「独自モジュールを定義する」によると、SwiftのAPIがないライブラリについても、独自にmodule.modulemapを用意して、そこに
header "../...省略.../hoge.h"
のように指定すれば、swiftから呼べる?- フレームワークを使う場合FRAMEWORK_SEARCH_PATHSを指定すれば、そこにあるフレームワーク内のmoduleを使ってくれる。しかし、独自モジュールを定義する場合、SWIFT_INCLUDE_PATHSにmodule.modulemapの(ディレクトリの?)パスを指定する必要がある。
- Bridging Headerとの違い
- Module MapはBridging Headerの上位互換
- Bridgin Headerはアプリケーションターゲットでしか使えない(フレームワークの開発では使えない)
- プロジェクトにObjective-Cのファイルが混ざる場合にだけ使うのがよい(その場合でもModule Mapは使える)
- ?: ↑よくわからない。
- モジュールの役割
- Swiftがライブラリをインポートするにはモジュールが必要(=インポートはモジュール鵜だけあれば良い)
- Objecctive-C?Cのライブラリをインポートするには、ヘッダファイルをModulemapでモジュールに変換する
- Swift製のライブラリではモジュールはコンパイラが自動的に生成する
- Logger.framework/Modules/Logger.swiftmoduleディレクトリ(内の.swiftmodule等)がモジュール
- リンクとは
- フレームワークをリンクするには(以下のどちらか?)
- FRAMEWORK_SEARCH_PATHSに...略
- OTHER_LDFLAGSに -framework <FrameworkName>と指定する。(e.g. -framework RxSwift, -framework "Alamofire")
- ライブラリをリンクするには(以下のどちらか?)
- LIBRARY_SEARCH_PATHSに...略
- OTHER_LDFLAGSに -l <LibraryName>と指定する。(e.g. -lsqlite3, -l"crypto")
- アーキテクチャ・プラットフォームが一致しなければならない(そのための方法が以下の通り?)
- サーチパスの変数化
- Universal (Fat) Binary
- xcrun lipoで作成
- carthageも使っているぽい。スクショのCarthage/Build/iOS/keychainAccess.frameworkにファットバイナリが入っているのかな?実機とsimulator両対応の。
- XCFramework。単にplatformとarchitectureごとにフォルダに分けたもの?
- フレームワークをリンクするには(以下のどちらか?)
?: opencvをiOSから使った時に利用したlibc++.tbdの.tbdとは?
→ libc++のダイナミックライブラリをインストールしてくるためのテキストベースのファイルらしい。SDKの容量を減らすため?
これを開いてみると単なるテキストファイルだということがわかりました。tbd = Text Based Dynamic Library といったところでしょうか。
install-name: /usr/lib/libz.1.dylib という記述があるので、どうやら実体として dylib が SDK の外側に別にあり、tbd はそれらをロードするための設定を記述したテキストファイルのようです。SDK の容量を減らす目的で Mac 側のライブラリを参照する仕組みにしたということでしょうか?(詳しい資料が見つからないので憶測ですが。)
https://qiita.com/usagimaru/items/82cf2a1fb8399c5a1be1
スタティックライブラリを作って、iOSアプリから呼び出してみたい。
- この辺が参考になりそう:
- 【iOS】静的ライブラリ(.aファイル)を作ってみる
- 上記の記事から引用されているiOSにおける静的ライブラリ作成技法