XcodeでStaticライブラリを別のStaticライブラリに依存させる方法

Staticライブラリを別のStaticライブラリに依存させようとしてつまずきました。ツイッターでつぶやいたところ、アドバイスをいただくことができて無事解決したので、その解決方法を共有します。

<blockquote class="twitter-tweet"><a href="https://twitter.com/hironytic/status/1191885943315124224"/></blockquote>

※説明の都合上、Twitter上のやりとりそのままではありません。

## 背景（やりたいこと）

話をわかりやすくするために単純な例で説明します。

登場人物は次の3つです。これらをそれぞれ別プロジェクトとして作成します。

- App（アプリ）
- Logger（Staticライブラリ）
- Repository（Staticライブラリ）

ここで、AppはLoggerの機能も使うし、Repositoryの機能も使います。また、Repositoryはその実装の中でLoggerの機能を使います。

<img width="400" alt="依存関係" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33397/27a51643-3053-af5e-29c9-8be78faed234.jpeg">

具体的なソースコードはこんな感じ（内容に意味がないのには目を瞑ってください）。

```swift:ViewController.swift
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")
    }

    // ...略
}
```

```swift:Repository.swift
import Foundation
import Logger

public struct Repository {
    public init() {}

    public func fetch() -> String {
        Logger().log("fetching...")
        return "result"
    }
}
```

```swift:Logger.swift
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を追加します。

<img width="800" alt="Appにライブラリを追加" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33397/ea3200cd-1548-fb75-ff22-12ccccfc0d12.jpeg">

<img width="397" alt="libLogger.aとlibRepository.aを選択" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33397/2cdf333a-d436-f80f-2203-7a8ee22632cf.jpeg">

これで、AppからLogger、Repositoryをimportできるようになりました。

### RepositoryからLoggerを参照

同じようにRepositoryでもLoggerをリンクするようにします。
Repositoryはライブラリなのでちょっと位置が違います。［Build Phases］タブの［Link Binary With Libraries］のところで＋ボタンを押して、libLogger.aを追加します。

<img width="800" alt="Repositoryにライブラリを追加" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33397/215ad6a7-3a34-4dde-bb58-19d4e421a9d5.jpeg">

<img width="386" alt="libLogger.aを選択" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33397/fcac9cd3-de47-4c42-47fd-27077a6ba8f3.jpeg">

### Appをビルドしてみる

Appをビルドすると…すべてうまくいきました。なんの問題ありません！あれ？つまずかなかった！？ :thinking: 

### `-ObjC` の登場

ここでAppのプロジェクト設定を変更します。［Build Settings］タブで［Other Linker Flags］のところに `-ObjC` を追加します。

<img width="800" alt="Other Linker Flagsに-ObjCを追加" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33397/ec553fec-de8c-3ba1-76a8-21d0284ba609.jpeg">

そして、もう一度Appをビルドすると…リンカエラーが！

```
ld: 5 duplicate symbols for architecture x86_64
```

無事、つまずくことができました！ :sweat: 


## `-ObjC` とは何か

先ほどの `-ObjC` というリンカフラグは何でしょうか。サードパーティ製のライブラリを組み込む際に、これを付けるように指示されているものがあります。例えば、 [Firebase SDKもそのひとつです](https://firebase.google.com/docs/ios/setup#frameworks)。

### Objective-Cのカテゴリメソッドが正しくリンクされるようにするフラグ

このフラグについては、以下に解説があります。

https://developer.apple.com/library/archive/qa/qa1490/_index.html

自分なりに読み解いてみました。

ソースコードをコンパイルする際に、利用しているメソッド（正確にはシンボル）がソースコード中に足りないと、それが足りないという情報が出力されます。
リンク時に足りないものがあれば、ライブラリからそのシンボルを探してきて、できあがるアプリに追加します。

次の例は、funcAが使っているfuncXが足りなかったので、ライブラリから追加された様子です。

<img width="400" alt="リンクの様子" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33397/b3d083c8-92ac-7073-fdfa-6d8b9bc6e0b5.jpeg">

ところが、Objective-Cのメソッドは動的に解決されるため、コンパイル時にはこのメソッドが足りないという情報が出力されません。問題になるのはカテゴリメソッドです。カテゴリメソッドは別のクラスを拡張するメソッドのため、この情報が出力されないとそのカテゴリ自体がライブラリから追加されません。

その結果、実行時に動的に解決した時点でコードが見つからず、実行時エラーになってしまいます。

<img width="400" alt="カテゴリがリンクされない" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33397/8224126c-9f55-bdcd-4299-6de78ab4bda2.jpeg">

そこで、 `-ObjC` フラグを使います。
このフラグをつけると、足りないものだけでなく、ライブラリに存在するものは全部追加せよいう指示になります。

<img width="400" alt="-ObjCフラグ" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33397/dcb99039-aa6e-8239-78ad-a86ce58b90c3.jpeg">

これにより、カテゴリもアプリに追加されるようになりました。が、本来必要なかったfuncYやfuncZも追加されるようになるため、アプリサイズは大きくなります。だから、Objective-C由来のライブラリを使っていなければ `-ObjC` は付けない方が得策です（なので、デフォルトでは付いていません）。

なお、 `-ObjC` はライブラリにのみ作用します。アプリのプロジェクト本体でObjective-Cのカテゴリメソッドを使っているだけなら特に必要ないそうです。

<blockquote class="twitter-tweet"><a href="https://twitter.com/k_katsumi/status/1191926347435778053"/></blockquote>


### なぜエラーになったのか

先ほどのAppと2つのライブラリの例で起こったことを想像してみます。

Staticライブラリの場合、Repositoryの［Link Binary With Libraries］にlibLogger.aを加えると、できあがるlibRepository.aの中にlibLogger.aの内容も含まれるのだと思われます。

`-ObjC` をつける前は、足りないという情報のあるRepositoryとLoggerをそれぞれのライブラリから持ってくることでうまくいったのでしょう。LoggerについてはlibLogger.aとlibRepository.aのどちらから持ってこられたのかはよくわかりませんが。

<img width="550" alt="-ObjCフラグなしの場合" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33397/a6f2a6be-0237-6d31-c1a7-af17845def75.jpeg">

ところが `-ObjC` をつけることで、ライブラリに含まれるものはすべてができあがるアプリに追加されるようになりました。ですから、アプリの中に2つのLoggerが含まれてしまいます。これがduplicate symbolsの原因です。

<img width="550" alt="-ObjCフラグを付けた場合" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33397/24f91d65-e07d-3f3b-20da-720dadae1522.jpeg">


## 解決編（うまくいくやり方）

### RepositoryにlibLogger.aをリンクするのをやめる

libRepository.aにlibLogger.aの内容が入ってしまっているのが問題です。とにかくRepositoryの［Link Binary With Libraries］からlibLogger.aを除きます。

しかし、~~このままではLoggerの存在がわかりませんから~~ Loggerのビルドが先に済んでいないと（**2019/11/19訂正:** 後述の追記を参照）、Repository内でLoggerをimportするところでエラーになります。
ではどうすればいいでしょう。いかにもそれっぽいのが［Dependencies］の部分です。

<img width="800" alt="wanna_add_dependency.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33397/ce6108ac-fc79-9a4a-06b3-e83718ca52fc.jpeg">

Repositoryの［Dependencies］の＋ボタンを押して、libLogger.aを追加したいところですが、残念ながらlibLogger.aを選ぶことができませんでした :sob: 

<img width="391" alt="cant_specify.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33397/470ee6d7-708e-1e87-8ae4-9bab5e31f05a.jpeg">

**2019/11/19追記:**
Twitterで @kishikawakatsumi さんから指摘をいただきました :bow:
［Dependencies］の役割は、自身をビルドする前にそこにあるものをビルドする、というだけでした。ワークスペースのLoggerのビルドを先に済ませていれば、importの解決はできました。
そのビルドが先に済むようにするのが［Dependencies］なんですね。

<blockquote class="twitter-tweet"><a href="https://twitter.com/k_katsumi/status/1196623799090679809"/></blockquote>


### 解決方法1: 1つのプロジェクトにする

<blockquote class="twitter-tweet"><a href="https://twitter.com/omochimetaru/status/1191927199198302210"/></blockquote>

@omochimetaru さんは、もともと1つのプロジェクト内でターゲットを分けるアプローチで検証されていたのですが、その場合は［Dependencies］にlibLogger.aを追加できていました（ツイート中のlibA、libBは、それぞれこの記事のLogger、Repositoryに相当します）。

このやり方で、期待通り、libRepository.aにlibLogger.aの内容を含めることなく、Repositoryをコンパイルできることがわかりました :tada: 

<img width="800" alt="1つのプロジェクトにする" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33397/b47e2430-278f-f00e-ba7c-cf2b2d8b314a.jpeg">

なお、当然ですが、Logger.swiftはLoggerターゲットに、Repository.swiftはRepositoryターゲットに入れてあります。

<img width="800" alt="ターゲット" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33397/cb0c51dc-86c7-c207-8c5c-f628102ba749.jpeg">

### 解決方法2: サブプロジェクトとして参照させる

<blockquote class="twitter-tweet"><a href="https://twitter.com/hironytic/status/1191951522638303232"/></blockquote>

自分のプロジェクト以下に含まれているものが［Dependencies］に追加できるということがわかりました。そこで、ワークスペース＋3つのプロジェクトに分けている場合でも、RepositoryのサブプロジェクトとしてLoggerを追加しておけば、［Dependencies］に追加できて、同じように解決することができました :slight_smile: 

<img width="332" alt="プロジェクトに追加" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33397/b82e03bf-40d9-c229-6875-ff2529ceb9a1.jpeg">

Logger.xcodeprojを選択して追加します。

<img width="800" alt="サブプロジェクトとして参照させた" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33397/6861f217-858e-7763-ec22-a739b6270c89.jpeg">


### 解決方法3: ワークスペースなしでもよい

AppからRepository、Loggerへの依存も同じと考えて、これら2つの依存先プロジェクトをAppのサブプロジェクトとして追加してもOKです。この場合はワークスペースは不要になります。

<img width="432" alt="ワークスペースなし" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33397/206a27bf-7ac4-fd49-2840-4e89ff5b6080.jpeg">

ちなみに、アプリ全体ではなくRepository.xcodeprojを開いてRepository単体でビルド可能なので、機能単位にしぼって開発を行うこともできそうです :slight_smile: 

<img width="469" alt="Repositoryを開く" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/33397/699360e3-e798-eb90-6452-a80fb168d2d6.jpeg">


## 最後に

この記事のスクリーンショットを撮ったりしたサンプルプロジェクトをGitHubに置いてあります。細かいところを確認したいときは、そちらを参照してください。

https://github.com/hironytic/StaticLibDep

また、ツイッターでアドバイスをくださった @omochimetaru さん、 @kishikawakatsumi さん、ありがとうございました！おかげで前に進むことができました :smile: