独自のライブラリ(.framework)を組み込んで苦労した話


iOSプロジェクトにライブラリを導入する時、
多くの人は `Cocoapods` or `Carthage` を用いると思います。

しかし先日、やむを得ず
どちらのやり方もできないライブラリを導入する機会がありました。
この導入に際して、たくさんのエラーとぶつかったので、記録として残します。
※以下そのライブラリを、仮名 `R.framework` と称すことにします。

# 前提

## frameworkとは

そもそもframeworkとは、いくつかのリソースを1つまとめたパッケージで、
Projectファイルに埋め込むことで、framework内のリソースを
Projectで使えるようになります。

※参考：

- [What are Frameworks?](https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPFrameworks/Concepts/WhatAreFrameworks.html#//apple_ref/doc/uid/20002303-BBCEIJFI)
- [Xcodeでフレームワークを追加する方法【初心者向け】](https://techacademy.jp/magazine/2703#sec3)

参考文献で分かりやすいと思った一文：

> Frameworkとは、動的共有ライブラリや nibファイル、イメージファイル、ローカライズファイル、ヘッダファイル、ドキュメント等のリソースファイルを1つのパッケージにまとめたディレクトリ

## 今回のケース

`R.framework` は自作ではなく、知り合いの方が作成したもの。
自分の扱うProjectに一度cocoapodsで導入して、ビルド成功させます。
そこで生成できた `R.framework` を用います。
cocoapodsで導入すると不都合がことがあったというレアケースだと思います。

## 開発環境

OS: Mac
Xcode: v10.1

# こうやればできるはず

まず、ぼくが試した正常パターンであろう手順を説明します。

## 1. 自分のProjectにpod install&ビルド成功

本当に導入したいProjectでも、自身のサンプルProjectでもよいですが、
一度pod installで `R.framework` を導入します。

```
$ bundle exec pod install
```

※自分の場合、bundlerでcocoapodsのバージョン管理してるので上記コマンド。

pod install完了後、Xcodeで `MyProject.scworkspace` を立ち上げ、
ビルド成功させます。

## 2. R.frameworkを取得

ビルドを成功させたら、Xcodeの設定ファイルを開いて、
下記の矢印ボタンを押します。Finderが開きます。

<img src="https://i.imgur.com/ruii4Q6.png" width=600px>

DerivedDataというフォルダが開かれます。
このDerivedDataとは、いわゆるXcodeのキャッシュです。
普段は、Xcodeを完全にクリーンするときに削除するものですね。

このDerivedData上に `R.framework` が生成されます。
階層は下記。

`DerivedData/MyProject-{ランダム文字}/Build/Products/Debug-iphonesimulator/R/R.framework`

これで `R.framework` の取得完了です。

## 3. R.frameworkをProjectに埋め込む

`R.framework` をProjectファイルにドラッグ&ドロップします。
※今回は `FunctionConfirm` Projectに導入してみます。

<img src="https://i.imgur.com/QzbIb3a.png" width=320px>

するとこんな感じのポップアップが出てきます。

<img src="https://i.imgur.com/CgqEjD0.png" width=600px>

Projectフォルダに `R.framework` ファイルを保存した方がよいので、
`Copy items If needed` にはチェックを入れておきましょう。
※チェックを入れ忘れた場合は、あとで `Build Phases>Copy File` で設定すれば同様のことができたりします。（詳細省略）

`R.framework` をドラッグ&ドロップした箇所に入れるだけなら、
`Create folder references` のままに、Groupに入れたい場合は、
`Create groups` にチェックします。

Targetはよしなに必要なものを選んでおいて、Finishボタンを押します。

## 4. Embedded BinariesにR.frameworkを追加

つまり、 `FunctionConfirm` プロジェクトに埋め込むファイル一覧に、
`R.framework` を追加するということです。

`General>Embedded Binaries` に `R.framework` を+します。

<img src="https://i.imgur.com/sAkTXuQ.png" width=600px>

## 5. 重複するR.frameworkを削除

上記4を行うと、下の `Linked Frameworks and Librares` に、
`R.framework` が2つあることが分かります。

3の操作と4の操作で、計2回追加されてしまったんですね。
複数ある必要は特にないので、片方を削除しておきます。

![](https://i.imgur.com/ELw6XKf.png)

## 6. ビルドで完了

ビルドして成功すれば完了です。
コード内で `import R` ができるようになります。

# 本題：困ったこと

ここからが本題です。出くわしたエラーたちを紹介していきます。

## そもそもpod install&ビルドできない

エラーログ：

```
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
```

エラー見ても分からない、なんやねん...

### 原因と対処

そもそもcocoapodsのバージョンがダメでした。初歩的...
実は `$ bundle exec pod install` ではなく、
`$ pod install` をやっちゃっていたんですね...

bundlerでcocoapodsを管理していないと、
勝手にcocoapodsのバージョンが最新になってしまいます。
`R.framework` が最新のcocoapodsに対応していませんでした。

cocoapodsのバージョンをbundlerで管理するように変えました。

## Pods参照できない

エラーログ：

```
/Users/XXX/XXX/XXX/XXX/Pods/Pods/Target Support Files/Pods-XXX/Pods-XXX.debug.xcconfig: unable to open file
```

なんやねん...
Pods/Pods ってなんやねん...

### 原因と対処

cocoapodsをダウングレードすると起きる現象らしいです。
project.pbxprojファイル内に、

```
name = Pods;
path = Pods;
```

という記述を見つけました。これか...
片方消したらビルド通るようになりました。

※参考：

- [[1.6.0.beta.2] XCConfig files are not properly deintegrated from the user project](https://github.com/CocoaPods/CocoaPods/issues/8091)

## R.framework見つからないとか言われる

`R.framework` を埋め込んでビルドしようとしたときのエラーです。

エラーログ：

```
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
```

※エラーログちょっと違うかも...とりあえずnot foundがあったのは確実。

ちゃんと `Embedded Binaries` したのになぜ...

### 原因と対処

Target複数あったのを忘れていました :innocent: 
自分あほ...
Targetが複数ある場合は、どのTargetの `Embedded Binaries` にも
`R.framework` を追加する必要があります。

自分が設定したTargetの `Embedded Binaries` に設定してなかった
だけだったので、修正したらエラーは直りました。


## Simulatorではビルドできるのに実機だとできない

今回ぼくが一番悩んだエラーです。

warningログ：

```
ld: warning: ignoring file /Users/.../R.framework/R, file was built for x86_64 which is not the architecture being linked (arm64): /Users/.../R.framework/R
```

エラーログ：

```
PhaseScriptExecution failed with a nonzero exit code
```

`R.framework` ファイルなんか無視されるし、
権限上のなにかしらでダメらしい...
ぼくが悪かったなら謝るから、何が悪かったか教えてくれよ...

※参考：

- [PhaseScriptExecution failed with a nonzero exit code](https://github.com/aws-amplify/aws-sdk-ios/issues/1045)

### 状態を確認する

教えてくれないなら、こちらから調べにいくしかありません。
調べていたら、 `arm64` やら `x86_64` というのは、
iOS・tvOS・watchOSデバイスのアーキテクチャであることが分かりました。

[この記事](https://blog.ymyzk.com/2015/12/ios-tvos-watchos-architectures/)の表がとても参考になりました。

<img src="https://i.imgur.com/YOX3wyb.png" width=600px>

Simulator(x86_64)はビルドできるけど、実機(arm64)はビルドできない...
もしかして、`R.framework` がx86_64はサポートしているけど、
arm64はサポートしてない的な...という仮説を考えました。

実際に調べてみます。
`$ xcrun lipo -info [binary]` を使えば、
サポートしているデバイスアーキテクチャが調べられるとのこと。
※参考：https://teratail.com/questions/61071


別の有名なライブラリ `Perfect.framework` と比べてみました。

```
$ xcrun lipo -info Perfect.framework/Perfect
Architectures in the fat file: Perfect.framework/Perfect are: i386 x86_64 armv7 arm64
```

やはりPerfect。i386、x86_64、armv7、arm64をサポートしていました。
続いて `R.framework` を調べてみます。

```
$ xcrun lipo -info R.framework/R
Non-fat file: R.framework/R is architecture: arm64
```

...!?

### 原因

原因は、「`R.framework` が実機をサポートしていなかったから」でした。
なぜこんなことに。。。ふと、気付きました。

> このDerivedData上に `R.framework` が生成されます。
> 階層は下記。
> `DerivedData/MyProject-{ランダム文字}/Build/Products/Debug-iphonesimulator/R/R.framework`

... **Debug-iphonesimulator/** だと...!?

そうです、この `R.framework` はSimulator階層にあったもの。
だからSimulator専用の `R.framework` だったのです。

無事、ぼくが悪いことが判明しました。ごめんなさい。

### 対処

謝るのは簡単ですが、それでは何も解決しません。
対応策として考えられるのは、
**「Simulatorも実機もサポートしている `R.framework` を作成すること」**
です。

`MyProject.scworkspace` のビルドを実機を対象に行うと、
`DerivedData/MyProject-{ランダム文字}/Build/Products/Debug-iphoneos/R/` 
階層に `R.framework` が見つかります。

しかし、これは逆に「実機しかサポートしていない `R.framework` 」です。
これでは実機でビルドできるだけで、根本の解決にはなりません。

[この記事](http://jimaoka.hatenablog.jp/entry/ios-framework)によると、
Simulatorも実機もサポートしているframeworkを、
`Universal Framework` と呼ぶことが分かりました。これを作ればいい...!!

...

＿人人人人人人人人人人人人人＿
＞　作り方がわからない <- NOW　＜
￣Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y￣

frameworkを自作していた場合の `Universal Framework` 
の作り方は調べて分かりましたが、今回は知り合いの方が作った場合なので、
自分ではどうすれば良いか分かりませんでした_(:3」∠)_
※やり方分かる方いましたら教えてくださいm(__)m

今回は、知り合いの方に `Universal Framework` の作成を依頼しました。

※Framework作成系参考：

- http://www.abt.jp/kb/2013/06/mac-os-x-create-framework/
- https://www.raywenderlich.com/2430-how-to-create-a-framework-for-ios
- https://medium.com/@syshen/create-an-ios-universal-framework-148eb130a46c
- https://qiita.com/wai21/items/3a2a7a7170ab9c5c1952
- https://qiita.com/wheel/items/156248c98faa433aecfe

# まとめ

以上、独自のライブラリ(.framework)を組み込んで苦労した話でした。
今回自分がエラーを対処するために気をつけたポイントは下記。

- エラーログを見る
- 状態を確認する
- ググる

そしてライブラリの扱いの件について思ったまとめは下記。

- cocoapods等で取得したライブラリを無闇に.frameworkにしない
- というか、ライブラリはcocoapods等で取得したまま利用する
- 知見として、自分でライブラリ作る経験は必要ですわ...

以上です。

※その他参考：

- https://qiita.com/aframeworks-blog/items/db45373e1ccb3c32fae3
- https://qiita.com/sutchan/items/0a78042261ca3f0a7d91
- https://qiita.com/kakipo/items/3cd87e00bc96bc3888b2