VoIP Push を簡単に発行する

iOS8から対応された　**PushKit** を使うことで、アプリが起動していない場合でもPushをアプリで処理できるようになっており、VoIPアプリの利便性を向上させることができます。（PushKitは VoIP機能を持ったアプリでしか利用できません）
**追記[2019/9/20]：
iOS 13 SDKでビルド & iOS上での実行の組み合わせにおいて、PushKitは VoIP の着信通知としてしか利用できません。**

iOS11となり今更ですが、
今回は PushKit の使い方は他の記事にお任せするとして、Push の確認のために簡単に　VoIP Push を発行する方法を紹介したいと思います。

# アプリ (NWPusher) を使う！
環境を整え、スクリプト書いてPushを発行することもできますが、簡単なのはGUIを持ったアプリから使うことでしょう。
オススメのアプリは NWPusher です！
>[noodlewerk/NWPusher](https://github.com/noodlewerk/NWPusher)
>OS X and iOS application and framework to play with the Apple Push Notification service (APNs)

APNSへの投稿を行うアプリであり、フレームワークとして自分のアプリに取り込むこともできます。

今回は Mac 上で動作するアプリを使う方法を紹介します。
NWPusher の　Installation に書かれていますが、Homebrew cask を使った方法が簡単でしょう。
　　１．　ターミナルを開く
　　２．　以下をタイプして実行
　　　　　brew cask install pusher
これだけです。
途中、ログインパスワード（管理者権限）を聞いて来ますので、入力が必要です。

アプリとしてインストールされますので、Launchpad から独特なアイコンを持った Pusher を起動するだけです。

## 証明書の準備
実装も含め、証明書の準備に関しては、以下の記事を参考にしました。
　　[VoIP プッシュ通知 (PushKit) を試す](https://qiita.com/imamurh/items/32c704c3a32367453106)
通常のPushとVoIP Pushは証明書が異なりますので、必ず VoIP Services Certificate を作成してください。

ダウンロードした *.cer ファイルを、Mac のキーチェーンアクセスに登録し、そこから証明書をp１２形式でエクスポートします。（秘密鍵ではなく証明書を右クリックして「＊を書き出す」を選択する。）

## デバイストークン
ここでは、実装は済んでいるものとします。
デバイストークンを取得するには、以下の関数内でデバイストークンをログに書き出します。
NWPusher を使ってPush送信する前に、このデバイストークンを書き留めておいてください。

```Swift:pushRegistry(_：didUpdate：for：)
    func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {

        var formattedToken = ""
        for i in 0..<credentials.token.count {
            formattedToken = formattedToken + String(format: "%02.2hhx", arguments: [credentials.token[i]])
        }
        NSLog("didUpdatePushCredentials: \(formattedToken)")
    }
```
最終的に製品化する場合は、必要な情報とデバイストークンをPushを管理するサーバーに送信する、という処理をここに実装することになります。

## Pushを送る
1. Launchpad から Pusher を起動します。
2. "Select Push Certificate" から "Import CKPS #12 file (.p12)" を選択、ファイル選択画面が開くので用意した証明書(.p12)を選択します。
3. "Device push token" の欄に、デバイストークン（64文字)を入力します。
4. "Expiry", "Priority" はお好みで。
5. "Payload" に送信したい情報を入力します。
6. 最後に "Push" ボタンを押します。<br>
"Payload has been pushed" と左下に表示されればPush送信成功です。

デバイスにPushが到達していれば、以下の関数が呼ばれます。
Local Notification を表示するようにでも実装しておけば、デバッガを使わずに確認できるでしょう。

```Swift:pushRegistry(_：didReceiveIncomingPushWith：for：completion：)
   func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) {
        guard type == .voIP else { return }
        
        NSLog("didReceiveIncomingPushWithPayload: \(payload.dictionaryPayload)")
    }
```

## 感想
通常のPushではタップされない限り非起動状態のアプリは処理ができませんでしたので、ユーザーのアクションがないとSIPアプリなどではINVITEを受けることもできませんでした。
またユーザーがタップではなく、通知を削除すると、アプリに処理がいかないので着信履歴も残すことができませんでした。

PushKit対応により、これらの問題が解決できるのはユーザーの利便性向上に役立ちますので VoIPアプリでは絶対に対応すべきですね。

iOS10 以降であれば、CallKit も合わせて使用すれば、ロック画面での着信応答や、Bluetoothヘッドセットでの応答などさらに便利になります。

今回、これらの技術に触れる経験をしましたので、忘れないうちに投稿させていただきました。
お役に立てば幸いです！

>**追記[2019/9/20]：
>iOS13 SDK でビルドして、iOS 13 上で実行した場合、[VoIP Push を受けた際](https://developer.apple.com/documentation/pushkit/pkpushregistrydelegate/2875784-pushregistry)は必ず CallKit の [reportNewIncomingCall](https://developer.apple.com/documentation/callkit/cxprovider/1930694-reportnewincomingcall) を呼び出さなければならない。
>つまり、VoIP Push は着信通知以外には使えなくなったということですね！
>~~Apple さん、いぢわる・・・~~**