[iOS] [社内勉強会資料] ディープリンクについて

# 「ディープリンク」とは

ディープリンクとは、Webページやスマートフォンアプリからアプリの特定コンテンツへ移動するリンクのことです。

ディープリンクは元々、あるWebサイトのページから他のWebサイトのページやコンテンツに直接リンクすることを指して使われている言葉でしたが、近年になってスマートフォンやアプリの利用が増加したことに伴い、現在利用されているような意味へと再定義されました。

（引用：[AIアナリストBLOG](https://wacul-ai.com/blog/site-improvement/method/deeplink/)）

iOSにおける実現方法は以下です。

- カスタムURLスキーム
- Universal Links
- Firebase Dynamic Links

# カスタムURLスキーム

- URLスキームとは、URLの“://”より前の部分で、リソースにアクセスするための手段を示します。
- http/https, ftp, mailto などなどが既定のURLスキームです。
- アプリ独自で決めたURLスキームをカスタムURLスキームと呼びます。myapp://foo/bar のようなURLになります。
- アプリでの設定/実装によって、カスタムURLスキームによるディープリンクを実現できます。

## アプリがインストールされていない場合の挙動

- メーラー等でリンクを踏んでも何も起こりません。
- Safariで開くと「ページを開けません。アドレスが無効です。」アラートが表示されます。

## 実装方法

- TARGETS > Info > URL Typesの設定だけです。

![URLTypes.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122917/c1223ee6-1c58-43d3-c35b-c675bb09656a.png)

## アプリでのURL取得

- `func application(_: open: options:)`にて取得可能です。
    - （注）iOS13以降でSceneDelegateを使用する場合は、
        - アプリ未起動状態では`func scene(_: willConnectTo: options:)`
        - アプリ起動中は`func scene(_: openURLContexts:)`

![URLスキームDebug.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122917/d7b291f3-3c29-29ee-5644-88e1e18a7e85.png)

## カスタムURLスキームの弱点と注意点

- ブラウザやメールソフトからのカスタムURLスキーム起動においては「アプリがインストールされていない場合に○○する（例：App Storeを起動する）」という要件を満たすことが困難です。
    - アプリ同士においては可能です。`UIApplication.shared.canOpenURL`にてインストール済みか否かを判定できます。
    - ブラウザでもフロントエンドで無理やり実装をすればできなくは無いらしい？
        - （参考記事）https://qiita.com/makoto_kw/items/5a99e37f7172337ae4be
- 誰かが管理しているわけではないので、他のアプリとスキームが競合する可能性があります。
- 端末内に同じカスタムURLスキームを持つアプリが複数インストールされている場合、
    - Androidの場合、どのアプリを起動するかを選択するためのダイアログが表示されます。
    - iOSの場合、先にインストールしたアプリが起動されます（古いiOSバージョンの場合は不定のようです）。
- したがって「ユーザーが気づかないうちに意図に反した挙動をしてしまう可能性がある」という弱点があり、それを狙った攻撃を「URLスキーム・ハイジャック」と呼びます。
    - （参考記事）https://blog.trendmicro.co.jp/archives/22020

# Universal Links

AppleがiOS/watchOS/macOS/tvOS向けに提供しているサービスです。
後述する通り、URLとアプリとの「関連付け」という仕組みがあるため、カスタムURLスキームのように競合はしません。

見た目は"https"で始まる単なるWebページのようなURLです。
iOS端末でユニバーサルリンクにアクセスすると、ターゲットのアプリがインストールされている場合は起動しますし、インストールされていない場合は任意のWebページを表示してくれます。

※リンクのtapにて直接App Storeへの遷移はできないため、当該Webページ内にApp Storeへのリンクを用意する必要があります。

## アプリがインストールされていない場合の挙動

- Safariにて自前サーバーに設置したWebページが開きます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122917/ba72f6b4-d19e-e9f2-a120-df3440893d82.jpeg" width=30%>

## 仕組みと実装方法

- アプリインストール時に、iOSは「Apple CDN」経由で自前サーバーに関連づけファイル (apple-app-site-association/AASA) を取得しに行きます。
    - ※iOS14未満はApple CDNを経由せず直接取得しに行きます。
![AASA.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122917/c5d5d4fb-a796-3ce8-a9cc-ef411b654de6.png)
- AASAは「このドメイン・パスのURLリンクはこのアプリと関連づきますよ」という定義をするためのファイルです。
- AASAは、独自ドメインの、自前のWebサーバーに配置する必要があり、AASAにはアプリを一意にするBundle IDなどを設定します。
- また、アプリインストール未済の場合にブラウザに表示させるHTMLも自前サーバーに配置します。

【TIPS】
以下のような場合には関連付けが上手く行われず、Universal Linksおよび、その上で動作するFirebase Dynamic Links（後述）は正常動作しませんので要注意です。

- アプリインストール時にプロキシツール（mitmproxy、Proxyman、Charlesなど）を利用している場合
- AASAを配置したサーバーにHTTPSでpublicアクセス出来ない場合（例：IP制限などのアクセス制限を設けている場合）

## Webサーバーの構築

今回の検証環境ではAWSに構築しました。

- AASAとHTMLはS3に配置しました。

![S3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122917/610a3e9e-cf17-5b3d-98dd-7ca55c5b8560.png)

```json:apple-app-site-association
{
  "applinks": {
       "apps": [],
        "details": [
           {
               "appID":"{チームID}.{Bundle ID}",
               "paths":[ "*" ]
           }
         ]
    }
}
```

![S3-HTML.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122917/06f09625-741f-4bcf-8c51-2bcc681830f8.png)

- AASAおよびHTMLは「Apple CDNから`HTTPS`でpublicアクセスできる」という条件が必要なので、CloudFrontで静的Webサーバーをホスティングしました。

![CloudFront.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122917/2f923638-da2f-94e3-9e32-aca3e3ddc4fc.png)

## アプリ側の対応

- Apple Developerサイトにて、プロビジョニング・プロファイルに「Associated Domains」を追加し、Xcodeにimport。

![provisioning.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122917/ec713483-8520-0e1e-f8ea-c300c2df0de4.png)

- Xcodeにて TARGETS > Signing & Capabilities > Associated Domains に`applinks:Webサーバーのドメイン`（今回の検証環境ではCloudFrontで構築したモノ）を設定。

![Capabilities.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122917/20f8f5d6-7327-9965-2aef-5c4e230c9970.png)

## アプリでのURL取得

- `func application(_: continue: restorationHandler:)`にて取得可能です。
    - （注）iOS13以降でSceneDelegateを使用する場合は、
        - アプリ未起動状態では`func scene(_: willConnectTo: options:)`
        - アプリ起動中は`func scene(_: continue:)`

![UniversalLinksDebug.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122917/ce64fb6f-1f62-d831-af85-3f1757af4b82.png)

# Firebase Dynamic Links

AndroidにはUniversal Linksと似たApp Linksという仕組みがあります。

Universal Links/App Linksは、カスタムURLスキームの弱点を解消した強力な仕組みではありますが、「自前のドメインとWebサーバーを用意し、OSごとに異なる"仕込み"を行う必要がある」というデメリットがあります。

Firebase Dynamic LinksはUniversal Links/App Linksを使う上での手間を解消することを主目的とし、

- 関連づけファイルを配置するWebサーバーをホスティングしてくれる
- Dynamic Links既定の `https://{任意のサブドメイン}.page.link` というドメインで使える、または独自ドメインも指定可能
- 関連づけファイルの設定をコンソール上から行える

というサービスです。
また、

- リンクURLを開いたユーザーのiOS/Androidデバイスにアプリがインストールされていない場合にストアに遷移させる
- インストール完了後のアプリ起動時にリンクURLを起動パラメータとして受け取る
 
といったこともできます。

さらに「どこから流入してアプリをインストールしたか」といった解析サービスも付いています。

## アプリがインストールされていない場合の挙動

- Safariにて、Firebaseコンソールで定義した「アプリのプレビューページ」が開きます。
- 「OPEN」をタップするとApp Storeに遷移し、アプリのページが開きます。FirebaseコンソールでアプリIDを定義しておきます（後述）。
- Dynamic Linksを作成する際に「アプリのプレビューページをスキップする」をONにすると、いったんSafariに遷移するものの瞬時にApp Storeに遷移します（後述）。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122917/85654d15-9fa9-4b65-49ad-c29313c75456.png" width=30%>

## FirebaseコンソールでのDynamic Links作成

プロジェクト（アプリ）の設定
![DynamicLinks_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122917/90bf5c75-6deb-6ec6-0800-b889931476a7.png)

プロジェクトごとのDynamic Linksの利用開始設定
![DynamicLinks_1_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122917/ecd34b4b-38ed-1c13-e4f6-4358f70c683c.png)

新しいDynamic Linksの作成
![DynamicLinks_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122917/372f181d-c405-ce46-0697-bf0d088c9aca.png)
![DynamicLinks_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122917/b7310daf-339e-06a4-e8f7-8a5c9b6e4603.png)
![DynamicLinks_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122917/36cd97eb-333b-49c1-2355-c4d8e5444039.png)
![DynamicLinks_5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122917/46ca203f-07af-c10d-45a4-192db62a5611.png)
プレビューページはスキップした方がいいようです。
UX的な観点と、iOS14以降にてペーストボード参照警告が表示されてしまうためです。

ただし、「ディファードディープリンク」（App Storeからアプリをインストールした後の起動パラメータとして受け取るリンク）が効かないケースがあるようです。
（参考記事）https://qiita.com/shinokichi/items/5f1dac5adbca878f2cc4

![DynamicLinks_6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122917/88ea9463-c9ec-bd9e-a0e8-5bfc131db638.png)
![DynamicLinks_7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122917/e4ed60cc-f5fd-86cf-3411-5e94ddafb9e0.png)

- Firebaseコンソールにて作成する方法以外にも、アプリのSDK、REST API、手動、で作成することもできます。
- 手動で作成する場合、クエリパラメータを仕様に従って組み合わせるだけで動的にダイナミックを作成できます（ただし短縮URLは利用できません）。

公式ドキュメント：
[Dynamic Linksを作成する](https://firebase.google.com/docs/dynamic-links/create-links?authuser=3&hl=ja#manually_constructing_a_url)
[ダイナミックリンクURLを手動で構築する](https://firebase.google.com/docs/dynamic-links/create-manually?hl=ja)

## アプリ側の対応

- 「Associated Domains」については[Universal Linksと同様に設定](https://qiita.com/y-some/private/3bfcc2535610ee155eee#%E3%82%A2%E3%83%97%E3%83%AA%E5%81%B4%E3%81%AE%E5%AF%BE%E5%BF%9C)
    - XcodeにはFirebaseコンソールで設定したドメイン（規定では`{任意のサブドメイン}.page.link`）を設定
- Dynamic Links SDKの導入など

公式ドキュメント：
[iOS でダイナミック リンクを受信する](https://firebase.google.com/docs/dynamic-links/ios/receive?hl=ja)

## アプリでのURL取得

- 実体はUniversal LinksなのでDelegateメソッドはUniversal Linksと同じです。
- Dynamic Links作成時に自らが設定したURLリンク（ディープリンクURL）はDynamic Links SDKを利用してパースします（非同期関数）。

![DynamicLinksDebug.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122917/a895ce70-ba03-d850-bcb0-e0f1bd50a5a7.png)

- アプリインストール未済＞Dyamic LinksのURLをタップ＞App Storeからインストールした直後の起動はカスタムURLスキーム起動になるので、`func application(_: open: options:)`にて取得します。

![DynamicLinksDebug2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/122917/f0ee9620-8698-ea60-58cc-fe055dd76652.png)

## 蛇足

中国ではDynamic Links(Firebase)は国の規制によって利用できませんので、中国でサービス展開するアプリの場合は採用できません。
あるいは中国でオフショア開発する場合、中国ではDynamic Links関連機能の開発・テストが出来ませんので、それを見越したプロジェクト計画が必要になります。