XcodeでStaticライブラリを別のStaticライブラリに依存させる方法
Staticライブラリを別のStaticライブラリに依存させようとしてつまずきました。ツイッターでつぶやいたところ、アドバイスをいただくことができて無事解決したので、その解決方法を共有します。
※説明の都合上、Twitter上のやりとりそのままではありません。
背景(やりたいこと)
話をわかりやすくするために単純な例で説明します。
登場人物は次の3つです。これらをそれぞれ別プロジェクトとして作成します。
- App(アプリ)
- Logger(Staticライブラリ)
- Repository(Staticライブラリ)
ここで、AppはLoggerの機能も使うし、Repositoryの機能も使います。また、Repositoryはその実装の中でLoggerの機能を使います。
具体的なソースコードはこんな感じ(内容に意味がないのには目を瞑ってください)。
import UIKit
import Logger
import Repository
class ViewController: UIViewController {
let logger = Logger()
let repository = Repository()
@IBAction func fetch(_ sender: Any) {
logger.log("Fetching start")
_ = repository.fetch()
logger.log("Fetching end")
}
// ...略
}
import Foundation
import Logger
public struct Repository {
public init() {}
public func fetch() -> String {
Logger().log("fetching...")
return "result"
}
}
import Foundation
public struct Logger {
public init() {}
public func log(_ message: String) {
print(message)
}
}
それではつまずいてみましょう
ワークスペースを作成
ワークスペースを作成し、App、Logger、Repositoryの3つのプロジェクトをワークスペース内に追加します。
AppからLogger、Repositoryを参照
Appのプロジェクトで、LoggerやRepositoryを使いたいので、Appのプロジェクト設定にてこれらのライブラリを追加します。
[General]タブの[Frameworks, Libraries, and Embedded Content]の部分で+を押して、libLogger.aとlibRepository.aを追加します。
これで、AppからLogger、Repositoryをimportできるようになりました。
RepositoryからLoggerを参照
同じようにRepositoryでもLoggerをリンクするようにします。
Repositoryはライブラリなのでちょっと位置が違います。[Build Phases]タブの[Link Binary With Libraries]のところで+ボタンを押して、libLogger.aを追加します。
Appをビルドしてみる
Appをビルドすると…すべてうまくいきました。なんの問題ありません!あれ?つまずかなかった!?
-ObjC
の登場
ここでAppのプロジェクト設定を変更します。[Build Settings]タブで[Other Linker Flags]のところに -ObjC
を追加します。
そして、もう一度Appをビルドすると…リンカエラーが!
ld: 5 duplicate symbols for architecture x86_64
無事、つまずくことができました!
-ObjC
とは何か
先ほどの -ObjC
というリンカフラグは何でしょうか。サードパーティ製のライブラリを組み込む際に、これを付けるように指示されているものがあります。例えば、 Firebase SDKもそのひとつです。
Objective-Cのカテゴリメソッドが正しくリンクされるようにするフラグ
このフラグについては、以下に解説があります。
自分なりに読み解いてみました。
ソースコードをコンパイルする際に、利用しているメソッド(正確にはシンボル)がソースコード中に足りないと、それが足りないという情報が出力されます。
リンク時に足りないものがあれば、ライブラリからそのシンボルを探してきて、できあがるアプリに追加します。
次の例は、funcAが使っているfuncXが足りなかったので、ライブラリから追加された様子です。
ところが、Objective-Cのメソッドは動的に解決されるため、コンパイル時にはこのメソッドが足りないという情報が出力されません。問題になるのはカテゴリメソッドです。カテゴリメソッドは別のクラスを拡張するメソッドのため、この情報が出力されないとそのカテゴリ自体がライブラリから追加されません。
その結果、実行時に動的に解決した時点でコードが見つからず、実行時エラーになってしまいます。
そこで、 -ObjC
フラグを使います。
このフラグをつけると、足りないものだけでなく、ライブラリに存在するものは全部追加せよいう指示になります。
これにより、カテゴリもアプリに追加されるようになりました。が、本来必要なかったfuncYやfuncZも追加されるようになるため、アプリサイズは大きくなります。だから、Objective-C由来のライブラリを使っていなければ -ObjC
は付けない方が得策です(なので、デフォルトでは付いていません)。
なお、 -ObjC
はライブラリにのみ作用します。アプリのプロジェクト本体でObjective-Cのカテゴリメソッドを使っているだけなら特に必要ないそうです。
なぜエラーになったのか
先ほどのAppと2つのライブラリの例で起こったことを想像してみます。
Staticライブラリの場合、Repositoryの[Link Binary With Libraries]にlibLogger.aを加えると、できあがるlibRepository.aの中にlibLogger.aの内容も含まれるのだと思われます。
-ObjC
をつける前は、足りないという情報のあるRepositoryとLoggerをそれぞれのライブラリから持ってくることでうまくいったのでしょう。LoggerについてはlibLogger.aとlibRepository.aのどちらから持ってこられたのかはよくわかりませんが。
ところが -ObjC
をつけることで、ライブラリに含まれるものはすべてができあがるアプリに追加されるようになりました。ですから、アプリの中に2つのLoggerが含まれてしまいます。これがduplicate symbolsの原因です。
解決編(うまくいくやり方)
RepositoryにlibLogger.aをリンクするのをやめる
libRepository.aにlibLogger.aの内容が入ってしまっているのが問題です。とにかくRepositoryの[Link Binary With Libraries]からlibLogger.aを除きます。
しかし、このままではLoggerの存在がわかりませんから Loggerのビルドが先に済んでいないと(2019/11/19訂正: 後述の追記を参照)、Repository内でLoggerをimportするところでエラーになります。
ではどうすればいいでしょう。いかにもそれっぽいのが[Dependencies]の部分です。
Repositoryの[Dependencies]の+ボタンを押して、libLogger.aを追加したいところですが、残念ながらlibLogger.aを選ぶことができませんでした
2019/11/19追記:
Twitterで @kishikawakatsumi さんから指摘をいただきました
[Dependencies]の役割は、自身をビルドする前にそこにあるものをビルドする、というだけでした。ワークスペースのLoggerのビルドを先に済ませていれば、importの解決はできました。
そのビルドが先に済むようにするのが[Dependencies]なんですね。
解決方法1: 1つのプロジェクトにする
@omochimetaru さんは、もともと1つのプロジェクト内でターゲットを分けるアプローチで検証されていたのですが、その場合は[Dependencies]にlibLogger.aを追加できていました(ツイート中のlibA、libBは、それぞれこの記事のLogger、Repositoryに相当します)。
このやり方で、期待通り、libRepository.aにlibLogger.aの内容を含めることなく、Repositoryをコンパイルできることがわかりました
なお、当然ですが、Logger.swiftはLoggerターゲットに、Repository.swiftはRepositoryターゲットに入れてあります。
解決方法2: サブプロジェクトとして参照させる
自分のプロジェクト以下に含まれているものが[Dependencies]に追加できるということがわかりました。そこで、ワークスペース+3つのプロジェクトに分けている場合でも、RepositoryのサブプロジェクトとしてLoggerを追加しておけば、[Dependencies]に追加できて、同じように解決することができました
Logger.xcodeprojを選択して追加します。
解決方法3: ワークスペースなしでもよい
AppからRepository、Loggerへの依存も同じと考えて、これら2つの依存先プロジェクトをAppのサブプロジェクトとして追加してもOKです。この場合はワークスペースは不要になります。
ちなみに、アプリ全体ではなくRepository.xcodeprojを開いてRepository単体でビルド可能なので、機能単位にしぼって開発を行うこともできそうです
最後に
この記事のスクリーンショットを撮ったりしたサンプルプロジェクトをGitHubに置いてあります。細かいところを確認したいときは、そちらを参照してください。
また、ツイッターでアドバイスをくださった @omochimetaru さん、 @kishikawakatsumi さん、ありがとうございました!おかげで前に進むことができました
Author And Source
この問題について(XcodeでStaticライブラリを別のStaticライブラリに依存させる方法), 我々は、より多くの情報をここで見つけました https://qiita.com/hironytic/items/3651d339acb48bb7eb86著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .