XcodeGen、Embedded Frameworkを導入しているOSSにwatchOSアプリを導入する

watchOS用アプリ作成を学ぶために、既にある程度設計や機能が確立しているOSSを題材にしてみました。
その際に既存の採用技術や設計にフィットさせていく際に詰まったところや困ったことをまとめてみました。
watchOSの開発は情報がかなり少ないので、同じく困った人の助けになれば幸いです。

# 前提

[Pokedex](https://github.com/Frog-Frog/Pokedex)というOSSを題材にさせていただきました。
watchOSアプリを入れるにあたって、以下の技術を採用していることを前提に本記事を書いております。
- Embedded Framework
- Carthage
- XcodeGen
- Mint

watchOSアプリの導入がメインなので詳しくは割愛しますが、アプリ概要や採用している技術についての詳細は[こちら](https://qiita.com/fr0g_fr0g/items/2a71d055af4824e94bff)をご覧ください。

また、今回はAppleWatch独立ではなく、`Watch App for iOS App`のターゲットによって追加される、iPhoneと連携して動作するwatchOSアプリを前提としております。

# WatchOSアプリ導入の手順
上記のような技術を採用しているPokedexに対して、以下の手順でwatchOSアプリを導入しました。

1. 既存プロジェクトにまず手動でwatchOSアプリのターゲットを追加して、実機でビルドできることを確認する
2. XcodeGen対応 - 手動で追加したwatchOSアプリのターゲット（WatchApp / WatchApp Extension）をXcodeGenでの導入に以降
3. Carthage対応 - 導入ライブラリをwatchOSにも対応させる
4. Embedded Frameworkの利用 - iOSと共有するソースをマルチモジュールwatchOS用のターゲットとしてマルチモジュール化してApple Watch側から利用できるようにする


## Xcodegenで最初にプロジェクト作成した後、storyboardのリファレンスを繋ぎなおす

ターゲットの追加後project.ymlに加筆してビルドすると、以下のようなエラーを吐いて、ランタイムエラーになることがありました。

```"NO". Couldn't instantiate class _TtC7Pokedex19InterfaceController```

その際には、apple watch側で用意しているstoryboardのリファレンスが切れているので、`Interface.storyboard -> Attributes Inspector`を選択し、Custom Classを再度指定してリファレンスを繋ぎ直すと解消されました。

## Carthageのビルド対象となるプラットフォームにwatchOSを追加する

CocoaPodsやCarthageなどのパッケージマネージャーを利用して、watchOSのターゲットにもライブラリを導入したい場合、iOSに加えてwatchOSもプラットフォーム指定しなければなりません。

### CocoaPods

``` Podfile
// watchOSアプリ
target '[watchOS Extensionのターゲット]' do 
　platform :watchos, '2.0'
　pod 'Hoge'
end
```

### Carthage

```
carthage bootstrap --platform iOS,watchOS
```

### Carthage + Mint
今回のOSSではMintを使ってCarthageを導入しているのでこちら

```
mint run Carthage/Carthage carthage bootstrap --platform iOS,watchOS --cache-builds
```

生成される`Carthage/Build`に`watchOS`が追加されていればOK

## watchOSApp ExtensionにFrameworkを関連づけた際に、実機デバッグでランタイムエラーになる
PokedexではEmbededd Frameworkを使用してappとは別にPresentation, Domain, DataStoreを用意してマルチモジュール化していました。
このうち例えば、DataStoreのソースをベースにwatchOS用にビルドして、AppleWatch側のターゲットと関連づけすれば、通信処理を共通化して利用できますし、依存関係もスッキリ保てます。

この際XcodeGenを採用しているので、project.ymlからソースコードベースでwatchOS用にFrameworkを関連づけなければなりませんが、僕の場合以下のランタイムエラーになりました。
WatchDataStoreはframework、watchOSApp Extensionはframeworkを関連づけるtargetです。

```
dependent dylib '@rpath/WatchDataStore.framework/WatchDataStore' not found for '[watchOSApp Extensionのpath]', tried but didn't find: '[WatchDataStoreのpath]' ...
```

FrameworkのEmbed設定が正しくないために発生しているエラーのようでした。また、シミュレーターだと通常通り動くので、実機で初めて発覚したバグでした。

これは、WatchOSAppExtensionとWatchDataStoreの関連づけに`embed: true`のオプションを追加することで解消しました。
今回のwatchOSターゲットの追加では以下のようにproject.ymlに追加しました。

```project.yml
targets:
  # iOS app
  Pokedex:
    type: application
    platform: iOS
    sources: [Pokedex]
    settings:
    # some setting
    dependencies:
    # some target
    - target: WatchOSApp
      codeSign: false
      embed: true      
  # iOS framework
  # some framework

  # watchOS
  WatchOSApp:
    type: application.watchapp2
    platform: watchOS
    sources: [WatchOSApp]
    settings:
      PRODUCT_NAME: Pokedex
      PRODUCT_BUNDLE_IDENTIFIER: pokedex.watchkitapp
      CODE_SIGN_STYLE: Automatic
    dependencies:
      - target: WatchOSAppExtension
  WatchOSAppExtension:
    type: watchkit2-extension
    platform: watchOS
    sources: [WatchOSApp Extension]
    settings:
      PRODUCT_NAME: WatchOSApp Extension
      PRODUCT_BUNDLE_IDENTIFIER: pokedex.watchkitapp.watchkitextension
      CODE_SIGN_STYLE: Automatic  
    dependencies:
      - target: WatchDataStore
        embed: true
  WatchDataStore:
    type: framework
    platform: watchOS
    sources: [DataStore]
    settings:
      PRODUCT_BUNDLE_IDENTIFIER: pokedex.WatchDataStore
      CODE_SIGN_STYLE: Automatic
    dependencies:
     - carthage: Alamofire
     - carthage: Nuke
```

## その他

### iOSアプリとwatchOSアプリでソースを共有する

iOSアプリと共有したいソースにwatchOSに対応していないライブラリを使用している場合、以下のように条件付きコンパイルを行う必要があります。
今回はSpotlight検索機能を提供する`CoreSpotlight`がwatchOSに対応していないので、watchOS用のターゲットに共有するソースコードから以下のようにOSごとの制御を行います。

```Swift
#if os(iOS) || os(macOS)
import CoreSpotlight
#endif
// 以下watchOSでも使うライブラリ
import Foundation
import MobileCoreServices
```

### 複数のsimulatorを同時に起動しない

iPhoneと連動して動作するwatchOSのアプリを起動する場合、一度の起動でiPhoneとApple Watchの二つのシミュレーターを起動することになります。シミュレーターの端末を変えて確かめたくなった時に、iPhoneのシミュレータも異なるモデルに変わるので、試したい端末の倍のシミュレーターが動作しPCがフリーズしやすくなるので注意が必要です。

### 早めに実機で確認する

試験用watch端末を登録したプロビジョニングプロファイルを用意しなければいけないので後回しにしがちですが、watchターゲットを追加した段階で実機で確認するのが良いと思います。シミュレーターでは動くのに実機では起動できないなどシミュレーターではわからないことがたくさんある印象でした。
ただ、実機だとiPhone経由でビルドする分時間がかかるので、早期にまず実機でビルドできることを確認して、その後はシミュレーターで動作確認しつつ開発すると良いかと思います。

# リンク
[PokéAPIを利用してMVP+CleanArchitectureのiOSアプリを作ったので解説する](https://qiita.com/fr0g_fr0g/items/2a71d055af4824e94bff)

# 最後に
watchOSアプリの導入で詰まったところをまとめました。
情報が少なく、かなり試行錯誤した感があるので、もし間違えているところなどありましたら、そっと教えていただけますと幸いです。