Embedded Framework使いこなし術

iOS 8・Xcode 6から、[Embedded Frameworkが使えるようになりました](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/ExtensibilityPG/ExtensionScenarios.html)。

その導入法について書かれた記事などはよく見かけます(["ios embedded framework" でググってください](https://www.google.co.jp/search?client=safari&rls=en&q=ios+embedded+framework&ie=UTF-8&oe=UTF-8&gfe_rd=cr&ei=_95SVr7nMIvN8gfI-aT4Dg))が、実践的な説明・ノウハウ系はあまり見たことが無かったので、ご紹介します。
以下の2本+ステルス開発中の1本で1年程度の利用経験があります。

- [JOIN US - 今日飲み会がしたい！今夜飲める人だけのコミュニティ](https://itunes.apple.com/jp/app/join-us/id918363882?mt=8)
- [Player! - 熱狂型スポーツアプリ、スコア・試合経過をライブ中継!ニュースも配信中!(プレイヤー)](https://itunes.apple.com/jp/app/player!-re-kuang-xingsupotsuapuri/id897872395?mt=8)

というわけで以下、`Embedded Framework`について語っていきます。

# 利点

Staticライブラリというけっこう前からある別の仕組みもありますが、以下の利点全てを享受出来るのは`Embedded Framework`です。

## コード共有

最近、iOS本体アプリ以外にもApp ExtensionsやApple Watchなどターゲット(以下、メインターゲットと表現)が増えてきました。
もし、`Embedded Framework`を使わない場合、共有したいソースコード自体を複数ターゲットに紐付けることになります。

動作的には基本的に一緒になるはずですが、その方法では以下のデメリットが発生します。

- ビルドが別途走るので時間かかる
- ビルド生成物が重複するので、アプリサイズが増える

共有したいソースファイルを`Embedded Framework`とすることで、同一のフレームワークを複数ターゲットが参照・利用することが可能になり、上記問題が解決します。
なので、メインターゲットが複数の場合は、基本的に共通部分を`Embedded Framework`にまとめることをオススメします。

## レイヤー分割・依存関係が強制される

こちらは設計の絡む話です。
後述の[Embedded Frameworkへの分け方](http://qiita.com/mono0926/items/e29cd17789fd1d1548aa#embedded-frameworkへの分け方の例)を見た方が伝わりやすいかもしれません。

今まで、外部ライブラリ参照以外は、ベタに1つのプロジェクトに各種クラスを入れつつ、それをグループやフォルダで分ける、みたいなやり方をしている人が多いのではないでしょうか。
これでも依存関係がごっちゃになったりすることを気をつければ何とか防げるかもしれませんが、仕組み的に整えておいた方が確実です。

例えば、プロジェクト設定で以下のようにしておけば、例えばライブラリBがライブラリCに依存するような処理を書くことを強制的に防ぐことが可能となります。

- ライブラリA: 依存無し
- ライブラリB: ライブラリAに依存
- ライブラリC: ライブラリA・ライブラリBに依存

メインターゲットがこれらライブラリA・B・Cを参照する構成の時、もちろんライブラリからはメインターゲットを参照出来ないので、相互に弄ってコードがぐちゃぐちゃになることも防げます。

また、このようにキレイに分けて呼び出しが限定的になっていると、このクラス・処理はどこに入れるべきかな？とより真剣に悩んだりして良いなと思っています。

## ビルド時間短縮

`Objective-C`時代から、大規模なアプリになっていくとビルド時間がかかるのが気になることがありましたが、`Swift`でより顕著になったかと思います。
(最新のXcode 7・Swift 2ではかなり改善されていますが。)

**[Swift 1.2で差分ビルドが導入されてビルドが速くなりました](https://developer.apple.com/swift/blog/?id=22)が、`Embedded Framework`に分けていると、この恩恵を受けやすいです。**
「画面側のコードを1行変えただけなのに全体にビルドかかっちゃった(´・︵・｀)」みたいなことが、かなり減ります。

先日、[Wantedly](https://www.wantedly.com)の[susieyy](http://qiita.com/susieyy)さん達とこのあたりの話しましたが、以下のビルド時間問題の改善にもなるかもしれません。
[メッセージングアプリSync開発の舞台裏(iOS) - Wantedly Engineer Blog](http://engineer.wantedly.com/2015/09/09/sync-messenger-ios-project.html)

メインターゲット実行時に差分ビルドが効きやすく速くなるだけでなく、特定のEmbedded Frameworkのテスト実行なども軽快に出来て良いです。

ただ、これは`Whole module optimization`をオンにしていると効かないので、デフォルトのオフにするように気をつけます。
`Whole module optimization`がオンになっていると、差分が少しでもリビルドして全体最適化がされるようになります。

オススメの設定は、Debugビルドではオフで、Releaseビルドではオンです。
(ただし、実行時の挙動が稀に変わることあるので、Releaseビルドで要テスト)

そして、本記事を書きながら気づいたのですが、`Whole module optimization`は以前はビルド設定の独立した項目でしたが、Xcode 7.1.1で以下のようにSwift最適化レベルとセットになっていました。

![Screen Shot 2015-11-23 at 6.59.14 PM.png](https://qiita-image-store.s3.amazonaws.com/0/19398/83b0971a-47d5-8f5d-33cf-c8603fb778d1.png "Screen Shot 2015-11-23 at 6.59.14 PM.png")

セットになっているとはいえ、ちゃんと以前の設定を引き継ぎつつ変更されていたので多分大丈夫だと思いますが、念のため以前の設定が保たれているかチェックした方が良いかもしれません。
デフォルトだとReleaseビルドの設定は`Fast [-O]`ですがその下の選択肢を選ぶと`Whole module optimization`がオンになって下記のような挙動となります。

- 最適化のためにリビルドが走りやすくなってビルド時間はかかる
- 一方、文字通り最適化がかかるので実行パフォーマンスは良くなる
  - [Swiftのfinal・private・Whole Module Optimizationを理解しDynamic Dispatchを減らして、パフォーマンスを向上する - Qiita](http://qiita.com/mono0926/items/f5f271b7d2bde68207b2)

## 名前空間が分かれる

これも`Swift`だけの利点で、`Objective-C`では当てはまらないようです。

importの有無や、モジュール名を明示的に指定することなどにより、呼び出したいクラスなどを制御出来ます。
[Swift の Embedded Framework と namespace](http://www.toyship.org/archives/2099)
フレームワーク間で名前の衝突が発生してもコンパイルエラーになったり、その回避のために呼び出し先を明示出来たりと、危なっかしかった`Objective-C`よりかなり良い感じですね(　´･‿･｀)

とはいえ、JavaやC#など、概ねフォルダ単位レベルで名前空間が分かれる感じの粒度では無いですよね（´・ω・｀）

細かく分ける用途だと、`Nested Type`などですかね。
[iOS - Swiftで名前空間を利用する - Qiita](http://qiita.com/yimajo/items/56c51228ca29c817f3c1)

上の例のようなクラスじゃなくて`struct`・`enum`を使ったソースもよく見かけます。
[Namespaced constants in Swift · Jesse Squires](http://www.jessesquires.com/swift-namespaced-constants/)

# Embedded Frameworkへの分け方の例

特に活用してなかったり、あるいは共通部分(`Common`など)を1つ作って、iOSアプリ・App Extensions・Apple Watchアプリなどから参照する、というやり方が世間で多い気がします。
(何となく話したり目にする記事からの印象です)

ただ、僕は上記の[レイヤー分割・依存関係が強制される](http://qiita.com/mono0926/items/e29cd17789fd1d1548aa#レイヤー分割依存関係が強制される)や[ビルド時間短縮](http://qiita.com/mono0926/items/e29cd17789fd1d1548aa#ビルド時間短縮)のメリットを最大限活用するために、もう少し細かく分けています。

分け方の基準としては、「レイヤーとして分けるべき単位」で考えています。
(この基準に従ったとしても、人によって結果は異なると思っていますが。)
元々、.NETメインで開発していましたが、その感覚だとソリューションの下に関連プロジェクトをいくつか作りますが、その感覚でやっています。

- 参照元のメインターゲット(以下など)で、いわゆるUI層
  - iOSアプリ
  - App Extensions
  - Apple Watchアプリ
- `Library`
  - アプリ関係無く使うであろう共通クラス・関数など定義
    - UIKit・Foundationクラスを拡張してよく使う処理をメソッドとして生やしたり
  - ロガーなども
  - http://qiita.com/mono0926/items/c53b2e46e51a0b0bbf06#embedded-frameworkを利用 でも使用について言及
- `WebApiClient`
  - サーバーとの通信処理周り
  - 内部的に[Alamofire](https://github.com/Alamofire/Alamofire)・[SwiftyJSON](https://github.com/SwiftyJSON/SwiftyJSON)など使いつつ、インターフェースは[SwiftTask](https://github.com/ReactKit/SwiftTask)のみに依存するようにしてます
- `Model`
  - いわゆるモデル層
    - モデル定義・ロジック周りはここに集約
  - `Library`・`WebApiClient`に依存
  - 各種サービスクラスを定義して、内部的に`WebApiClient`を使いつつ、永続化など経て、UI側に結果を返す
  
  ちなみに、`Library`は開発している各アプリで同一のものを参照した方がキレイだなと思いつつ、Swift仕様がまだ変更多いことや、他のアプリに影響を与えずにささっとコード改善したい時などあり、別定義にしちゃっています。
  Swiftの仕様変更が落ち着いたり、`Library`が成熟してきたら同一のものを参照する方式に変えようかなとも思っています。

# 躓きどころ

本質的なデメリットは無いに等しいと思っていますが、けっこう躓きポイントがあるので、実質的にデメリットとなり得るかもしれません。
僕はただでさえSwiftやXcodeバージョン間の差異や不安定さ真っ只中で、Embedded Frameworkも活用してたのでたまに悩ましかったですが、今はもうかなり安定してきているのであまり問題無い気がします。

## CocoaPodsライブラリインストール

以前は、Embedded Framework内でCocoaPodsライブラリを使えるようにすることがうまく出来ず(コンパイルエラー・実行時エラーなど)、制約として諦めつつ開発していましたが、最近は下記方法でうまくいっています。
(以前から問題無かったしれませんが、僕はうまく出来ずに困っていました)

- `use_frameworks!`指定でSwiftクラスから`import ライブラリ名`でインポートする方式にする
- メインターゲット + 利用する複数ターゲット向けに`pod`指定する
  - メインターゲット直接使用していなくてもメインターゲット向けにもインストール必要の模様(インストールしないと実機実行時に`dyld: Library not loaded`でクラッシュしました)

結果、こんな感じになりますが、手探りなのでベターな書き方あったら教えてください。

```rb
platform :ios, '8.0'

use_frameworks!

def common    
    pod 'FBSDKLoginKit'
end

# メインターゲット
common
pod 'SVProgressHUD'

target 'Model' do
    common
end
```
  
ただ、[Google Analytics](https://developers.google.com/analytics/devguides/collection/ios/v3/sdk-download?hl=ja)は`use_frameworks!`指定での利用時にコンパイル通らず、直接取り込みしています(´・︵・｀)

### @kishikawakatsumi さんに、ネストさせて共通のPodをまとめる書き方を教えていただけました😋

[本記事の @kishikawakatsumi さんのコメント](http://qiita.com/mono0926/items/e29cd17789fd1d1548aa#comment-6647af913309c54ef058)をご覧くださいヽ(・ω・｀）

## Carthageライブラリインストール

> メインターゲット直接使用していなくてもメインターゲット向けにもインストール必要の模様

こちらも、CocoaPodsと同様、これに気をつける必要があります。

ただ、それ以外は特にハマったこと無いですね。


メインターゲットでは、Carthageライブラリ`Embedded Binaries`に放り込むと`Linked Frameworks and Libraries`にも自動追加されますが、`Embedded Framework`ターゲットには`Embedded Binaries`が無く、手動で`Linked Frameworks and Libraries`に放り込む感じになります。


メインターゲット:
<img width="655" alt="Screen Shot 2015-11-30 at 5.16.07 PM.png" src="https://qiita-image-store.s3.amazonaws.com/0/19398/88c8aa5a-ebdf-60ef-9466-8a3526f613af.png">

Embedded Framework:
<img width="662" alt="Screen Shot 2015-11-30 at 5.16.18 PM.png" src="https://qiita-image-store.s3.amazonaws.com/0/19398/cee7914e-a1a6-d215-c26e-83f24d2f5f69.png">


## ビルド設定

ビルド設定が原因でたまにビルドやアーカイブに失敗することがありますが、ちゃんとエラー文を読めば対処容易なものがほとんどでした。

- bitcode設定
  - 無効にする場合、`Embedded Framework`ターゲットのも無効に(Xcode 7.1までは弄らずOKだった)
  - [Swift - Embedded Framework で Carthage を使ったさいの問題と回避策 - Qiita](http://qiita.com/kumapo/items/7c94c064146144eeaef2) など情報ありますが、僕は今のところ有効にする拘り無いので無効にしちゃっています
- 署名周りで引っかかった記憶がありますが以下でOKかと思います
  - Provisioning Profile: Automatic
  - Code Signing Identity
      - Debug: iOS Developer
      - Release: iPhone Distribution

# 実例

## [FireFox iOS版](https://github.com/mozilla/firefox-ios)

[Firefox web browser on the App Store](https://itunes.apple.com/nz/app/firefox-web-browser/id989804926?mt=8)のソースが公開されています(　´･‿･｀)

こちらのソース見たら、Embedded Frameworkが活用されていました。
複数個使っている実コード例は貴重だと思うので、ご参考にヽ(・ω・｀）

- メインターゲット
    - `Client`・`SendTo`・`ViewLater`
- Embedded Framework
    - Shared: 上記の`Library`相当かな
    - Storage: 永続化層
    - Account: 上記の`Model `相当かな
    - Sync: 上記の`Model `相当かな
    - ReadingList: 上記の`Model `相当かな

上で書いた[Embedded Frameworkへの分け方の例](http://qiita.com/mono0926/items/e29cd17789fd1d1548aa#embedded-frameworkへの分け方の例)とは少し違いますが、何かしらのポリシーを持って適当な粒度でレイヤー分けれていれば良いと思っています。

<img width="459" alt="Screen Shot 2015-11-25 at 2.18.23 PM.png" src="https://qiita-image-store.s3.amazonaws.com/0/19398/a6ccd229-0a19-39c8-88d8-f3a8679c5ce8.png">

分け方が違うとはいえ、こういう大手のアプリが同じような構造でアプリを組んでいることが分かって良かったです。

> As of August 28, 2015, this project requires Xcode 7 beta 6.

ビルドしてみたりしたかったのですが、この制約のせいか、`Storage`のビルドで詰まってしまって出来ずでした（´・ω・｀）
今手元にXcode 7.1.1しか無くて、環境用意するのも面倒だったので…。

他にもソース読むと色々発見ありそうですʕ ·ᴥ·ʔ

---

というわけで、是非活用していきましょう！
ネット上にもノウハウやトラブル解決事例が少ない状態なので、もっと利用が活発になれば僕も嬉しいです(　´･‿･｀)