Ad hoc配信をやめてTestFlightで配信する

![desc.001.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14789/99c7907c-282d-b762-7e5a-bc5e0b7325f7.jpeg)

iOSアプリをApp Storeではなく、Webサービスなどからベータ版のアプリを配信する方法を **Ad hoc** 配信といいます。
Ad hoc配信はiOSアプリ開発の黎明期から行われておりますが、事前にデバイスを登録したり、Provisioning Profileをアップデートしたり、と配信環境のメンテナンスが煩雑になりがちです。
この記事では、 **Bitrise** などのCI/CDサービスを利用して、Ad hoc配信ではなく、Testflightで継続的に配信する方法を紹介します。

特にBitriseを利用して、App Store Connect API経由でTestFlightにアップロードするのは結構ハマりながら作業することになったので、この記事が参考になれば幸いです。

Bitriseでの配信設定をとりあえずしりたい方は、 [ここ](https://qiita.com/d_date/items/db540e9ed127b1b23e40#bitriseを使ってtestflightに自動で配信する) までジャンプするとよいでしょう。

**2022/01/26更新**
iOS Auto Provision stepは deprecated となり、Xcode archiveにautomatic code signingを指定する形式となりました。

## Ad hoc配信の歴史

Ad hoc配信では、事前にApple Developerにテストを行いたいデバイスのUDID[^1]を登録し、そのUDIDが含まれるProvisioning Profileを用いてArchiveビルドする必要があります。

[^1]: UDIDをテスターが確認するには、MacにデバイスをつないでFinderを開き、デバイスの下にあるサブタイトル？をクリックすると表示されるという昔のゲームみたいな仕様があります。

iPhone黎明期の頃は、自前のファイルサーバーにexportしたipaファイルをアップロードし、インストール用リンクが記載されたhtmlファイルをホストし、Over-the-airで配布していたものでしたが、その後、TestFlightが登場し随分と管理が楽になったものです。

その後、AppleにTestFlightが買収され、Apple公式のベータ版アプリ配信ツールとして、徐々に現在の形に進化していきます。

Ad hoc配信サービスとして有名なものには、Firebase App Distributionがあります。
かつてはCrashlyticsとして知られていた会社ですが、Twitter社に買収されてFabricとなり、今度はGoogleに買収されてFirebaseチームに加わり現在に至ります。

国産のサービスとしては、DeployGateがよく知られています。
Bitriseでも `Deploy to Bitrise.io` ステップを利用すると同じようにAd hoc 配信できます。
その他にもたくさんの配信サービスが存在します。


## TestFlightへの配信は審査が必要？

TestFlightに対するよくある誤解としては、配信にはAppleの審査が必要という点です。
これは半分正しく、半分誤りで、TestFlightで設定できるユーザーのロールを知る必要があります。

### External Tester
emailやパブリックリンクを利用してテスターを招待する方法です。
最大1万人まで招待できます。
テストを開始するにはアプリのレビュー（審査）が必要です。

> An external tester is any user you invite to beta test your app using an invitation email or public link. External testers can include your App Store Connect users, but the users can’t be both internal and external testers. You can invite up to 10,000 external testers per app. You can organize external testers into groups, but the groups aren’t visible to testers. If you invite any external testers, the first build is submitted to TestFlight App Review. Later builds for the same version may not require full review.

### Internal Tester
App Store Connectのユーザーを対象にした内部テスターです。
App Store ConnectにデバイスでサインインしているApple IDを紐づける必要がありますが、テストにはAppleの審査は必要なく、
App Store Connectにバイナリをアップロードするとすぐにテストが行えます。
執筆時点で100名までテスト可能です。

> Internal testers are members of your App Store Connect team with the Admin, App Manager, Developer, or Marketing role who have access to all your active builds. You can invite up to 25 internal testers per app. If you invite internal testers only, additional testing information is not required.

* ドキュメントにはアプリごとに最大25名と書いてあるが、現在は100名までテスト可能

このように、Internal Testerを対象にすると、他のAd hoc配信と同様にテストが行えます。

## Ad hoc配信からTestFlightへ乗り換える

Ad hoc配信からTestFlightへ移行するとどんなメリットがあるのでしょうか。
その前に、Ad hoc配信により生じている問題についてみていきましょう。

#### Provisioning Profileの管理を難しくしているのはAd hoc配信
アプリをアーカイブして配信するには、Distribution用の証明書と、その証明書に紐づくProvisioning Profileが必要です。

Ad hoc配信には、このProvisioning Profileに利用したいデバイスのUUIDが記載されている必要があります。

そのため、すでに配信されているサービスを新しいデバイスで利用しようとしても、利用できないと言われるのはよくあるトラブルシューティングだと思います。

一方で、TestFlightはAd hoc配信ではなく、App Storeと同じ形式でのビルドとなります。
これに利用されるProvisioning Profileにはデバイスの登録は不要のため、デバイスのUDIDの管理は不要となります。

また、dSYM (Debug Symbol) ファイルは、難読化されたバイナリのソースコードの情報を記すもので、ビルド時にバイナリと一緒に生成されます。

これはクラッシュトレースが得られたときに、ソースコードのどのファイルのどのメソッドでクラッシュしたかを解読するのに利用されます。

App Store Connectにアップロードされたビルドでは、XcodeのOrganizerの機能で、Crashの難読化が解かれた状態で（deobufuscate)取得できます[^2]。

[^2]: 願わくば、App Store Connectにクラッシュ数を通知する機能が入って欲しい

### TestFlightに移行してできること

#### ・ デバイスの管理の撤廃
前述の項目に加え、デバイスの削除のことを気にしなくてよいというメリットがあります。
登録したデバイスはdeactivateできますが、厳密に削除できるのは Developer Programの更新のタイミングのみで１年に１回しか行えません。

#### ・ 過去のビルドへの配信
Provisioning Profileに依存しないので、App Store Connectにユーザーを登録して、TestFlightへ招待すると、すぐにテストしてもらうことができます。

#### ・ アプリの自動アップデート
比較的最近利用できるようになった機能で、最新のビルドが公開されたら、自動でアップデートされるようになりました。常に最新のバージョンでテストできるので便利です。

#### ・ スクリーンショットでフィードバック

アプリ内でスクリーンショットを撮ると、コメントを添えてフィードバックが送れます。
クラッシュが発生したときも、次回起動時にフィードバックを送ることができます。
送られたフィードバックはApp Store Connectで確認できます。

#### ・ App Clipsのテスト

現行の方法では、Ad hoc配信でテストする術はありません。

## TestFlightへ配信する

Xcodeを使った方法と、CI/CDでCLIを利用して配信する方法があります。

### Xcodeを使った手動配信

XcodeからArchiveを行い、Distributeを選びます。
export methodは以下のようにApp Store Connectを選択することに注意します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14789/b8c2697b-066d-9e69-a4db-20ca8b4d9dbb.png)

アップロードが完了すると、App Store Connect上でTestFlightが有効になります。[^3]

[^3]: すぐにTestFlightを有効にするには、輸出コンプライアンスがバイナリに設定されている必要があります。Info.plistの `ITSAppUsesNonExemptEncryption` を適切な値に設定しておくことで、毎回の設定をスキップできます。参考: [Export compliance overview](https://help.apple.com/app-store-connect/#/dev88f5c7bf9)

## Bitriseを使ってTestFlightに自動で配信する

Xcodeは　`xcodebuild` や `xcrun` と言ったCLIも提供しており、ビルドからApp Store Connectへのアップロードまで自動化することが可能です。
fastlaneを利用することで、比較的自動化がしやすく、Bitriseのstepでも利用されています。

App Store Connect APIを利用する方式で実行することで、二段階認証をスキップできます。

### 事前準備

Bitriseにアップロードおよびセットしておく環境変数やSecretはこちらです。

#### Code signing certificates

Distributionの証明書の.p12ファイルをCode SigningのCode signing certificatesにアップロードします。

![Screen Shot 2021-07-07 at 22.07.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14789/1c369530-aaf7-5d7b-7a34-8181c5d3a647.png)

#### API Key, Issuer ID and Key ID
App Store Connect APIに利用するAuthKey(.p8)です。

AuthKeyの発行はApp Store Connect から行います。 Users and Access -> Keysから追加します。
初回はAccount HolderにApp Store Connect APIを利用するリクエストを送る必要がありますが、以降はAdmin以上のロールがあればKeyを発行できます。

また、このページに表示される `Issuer ID` と `Key ID` も利用するので、Secretsに必ず追加します。

![Screen Shot 2021-07-07 at 22.13.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14789/274df2c0-e762-cd0f-3728-3c8b89d38008.png)

発行されたKeyをローカルにダウンロードし、BitriseのCode SigningのGeneric File Storageにアップロードします。ファイル名には10桁のKeyIDが含まれている必要があります。

![Screen Shot 2021-07-07 at 22.11.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14789/822ec5ca-db77-0ae1-2693-30c71359131d.png)

#### Apple Service Connection
BitriseのTeamsのタブに `Apple Service Connection` という項目があるので、 `API key authentication` を自分のチームに設定します。

以上の6つでセットアップは完了です。

### Workflowの設定

TestFlightへの配信は主に次のようなステップで構成されます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14789/125526ba-82d0-9086-12fd-d3b581d74fcc.png" width="300">

Note: 上記画像のiOS Auto Provision with App Store Connect API stepは廃止されました。以下の手順の通り、Xcode Archive & Export for iOS step で automatic code signingを指定してください。

ここでは、`Xcode Archive & Export for iOS`, `Deploy to iTunes Connect - Application Loader` の２つのステップについて説明します。

（GUIのスクリーンショットを貼ると説明が大変なのでコードで説明します）

#### Xcode Archive & Export for iOS

このステップではArchiveを実行し、ipaファイルをエクスポートします。
export_methodを app-store にすることに注意します。
`automatic_code_signing` を `api-key` にすると、アップロードした証明書とAPI Keyを利用して、App Store ConnectのDistribution用のiOS Provisioning Profileを取得します。

```yaml
    - xcode-archive:
        inputs:
            - export_method: app-store
            - scheme: XXX # Archiveを実行するscheme
            - distribution_method: app-store
            - automatic_code_signing: api-key # API Keyを利用してProvisioning Profileを生成する
            - team_id: $BITRISE_APPSTORE_TEAM_I```

#### Deploy to iTunes Connect - Application Loader

出力したipaファイルをApp Store Connectにアップロードします。
執筆現在、screenshotsやmetadataもアップロードできる `Deploy to iTunes Connect` のステップはうまく動かなかったので、こちらのstepを利用しています。

```yaml
    - deploy-to-itunesconnect-application-loader:
        inputs:
        - api_key_path: "$BITRISEIO_APPSTORE_CONNECT_API_KEY_URL"
        - api_issuer: "$APP_STORE_CONNECT_API_KEY_ISSUER_ID"
```

ここまでの設定が間違っていなければ、このWorkflowが正常に動き、TestFlightからの配信が行えます。

### (参考) Bitriseでの定期的な配信

TestFlightビルドの有効期限は90日間です。普通はビルドの有効期限が切れても影響はないですが、なんらかの理由で定期的に配信を続けたい場合は、BitriseのSchedule Buildが利用できます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14789/166d9a6d-83e8-42f3-74e5-7528c1b45fc1.png" width="300">

上記のように配信したい曜日（複数選択可能）や時間を指定し、どのブランチでどのワークフローを動かすのかを指定します。

Ad hocと違って、TestFlightでは同一のBundle Versionでは配布できないので、BitriseのBuild Numberを利用して、バージョンをBumpするようにしておくと便利です。これには `Set Xcode Build Number` ステップを利用します。

```yaml
    - set-xcode-build-number:
        inputs:
        - plist_path: "$BITRISE_SOURCE_DIR/App/iOS/Info.plist"
```
## FAQ

想定される質問について、事前に回答を書いておきます。

### Q. サーバーの接続環境ごとに配信できますか
A. できます。App Store Connectで新しいアプリを追加し、別環境用のBundle Identifierを設定します。 (e.g. App (Staging) )

あとは、アップロード時にBundle Identifierに基づき、対応するAppのTestFlightに配信されます。

### Q. アップロードには成功したのですが、バイナリが無効というメッセージがApp Store Connectから来ました

A. エラーメッセージによりますが、XcodeでAppIconの設定をしてください。

### Q. TestFlight版とApp Store版で挙動が違うものはありますか？
A. 一部の機能の差異に気をつければ、同じように利用できます。

例えば、SKStoreReviewControllerの[requestReview()](https://developer.apple.com/documentation/storekit/skstorereviewcontroller/2851536-requestreview)というユーザーにアプリのRatingを促すアラートを表示するメソッドは、ドキュメントに記載されている通り、developmentモードでは毎回表示、TestFlightでは表示されないという挙動になっています。

### Q. TestFlightへユーザーを自動で招待する方法はありますか？

A. Public testerではできますが、Internal testerではできません。

App Store Connect APIにはUser Invitationsという機能があります。
これを利用すると、招待したいユーザーに招待メールを送付することができます。
しかしinternal testerの場合は事前にApp Store Connectにユーザーが作成されている必要があります。
ユーザーが作成されると、App Store Connect UsersというGroupに追加できるようになるので、結局は手動で作業した方が早いと思います。

### Q. Cloud-managed distribution Certificatesにアクセスできないと出る場合

```
You haven't been given access to cloud-managed distribution certificates. Please contact your team's Account Holder or an Admin to give you access...
```

上記のようなエラーメッセージがでる場合は、利用しているAPI Keyの権限が不足しています。
API Keyを生成する場合は、 `Admin` ロールで生成されていることを確認してください。

## まとめ

本記事では、Ad hoc配信ではなく、TestFlightでInternal Testerに向けて配信することで、Ad hoc配信のユースケースをカバーしながら、簡単にベータ版アプリを配信できることを示しました。
また、BitriseをはじめとしたCI/CDサービス上で、App Store Connect APIを利用することで継続的にテスターにベータ版アプリを配信することができます。

ぜひAd hoc配信を今一度見直し、TestFlightでの配信を活用してみてください。

## References

#### ・ [Distributing Your App to Registered Devices](https://developer.apple.com/documentation/xcode/distributing-your-app-to-registered-devices)

Ad hoc配信についてのAppleのドキュメントです。

#### ・ [Distributing Your App for Beta Testing and Releases](https://developer.apple.com/documentation/xcode/distributing-your-app-for-beta-testing-and-releases)

TestFlightについてのAppleのドキュメントです。

#### ・ [Distribute an app using TestFlight (iOS, tvOS, watchOS)](https://help.apple.com/xcode/mac/current/#/dev2539d985f)


#### ・ [TestFlight beta testing overview (iOS, tvOS, watchOS)](https://help.apple.com/app-store-connect/#/devdc42b26b8)
App Store Connect上でのTestFlightの設定方法に関するドキュメントです。

#### Bitriseのドキュメント
- [Managing iOS code signing files - automatic provisioning](https://devcenter.bitrise.io/code-signing/ios-code-signing/ios-auto-provisioning/)
- [Deploying an iOS app to iTunes Connect](https://devcenter.bitrise.io/deploy/ios-deploy/deploying-an-ios-app-to-itunes-connect/)