Xcode8を使ってiOSでPush通知をやってみた

Xcode8を使ってiOSでPush通知をやってみたのでメモ。
APNsとの通信は[houston](https://github.com/nomad/houston)を使って実施しました。

# 環境

+ AppleDeveloperProgram参加済み
+ MacOSX El Capitan(10.11.6)
+ XCode8.0
+ Swift2.3
+ 実機(APNsはエミュレーターでは現在検証できないようなので)

# 前提

+ 試験用デバイス（実機）のデバイス登録が既に実施されている（未実施の場合、実施する)

# APNsについて

[Apple Push Notification Service](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/ApplePushService.html)

この公式ドキュメントを一度読むと理解しやすい

# AppIDの作成

[App ID](https://developer.apple.com/library/content/documentation/General/Conceptual/DevPedia-CocoaCore/AppID.html)

AppIDが必要らしいので作成します。
AppleDeveloperにログイン後、Cerfiticates,IDs & Profilesを選択。
次に表示される画面で Identifies->App IDsを選択。
既に選択されているAppID一覧が表示される+ボタンを押して追加の画面に遷移します。

+ App ID Description->AppIDを識別するために分かりやすい名称とします。
+ App ID Suffix->恐らくiOSアプリを作成する時のOrganization Identifierと合わせる必要があるように見受けられる。Explicit App IDでは厳密に指定し、Wildcard App IDではcom.domainname.*という感じで書けるようです。PushNotificationを使う場合、Explicit App IDの必要があるので、今回はこちらを指定
+ App Services-> Push Notificationにチェックを入れます

作成が完了すると一覧画面で登録内容が確認できます。

なお、このタイミングではまだ証明書を登録していないため、以下のように Push Notifications が Confiurable となっているのが確認できます。

![スクリーンショット 2016-09-22 11.18.24.png](https://qiita-image-store.s3.amazonaws.com/0/20193/32c70230-90bd-6f60-681c-1b2b950248de.png "スクリーンショット 2016-09-22 11.18.24.png")


# PUSH通知用の証明書作成

PUSH通知を行う際に利用する証明書の発行に必要な証明書署名要求CSR(Certificate Signing Request)ファイルを事前に作成します。

CSRについては以下のように記載あり。

[申込時に用意しなくてはいけないCSRファイルとは何でしょうか？](https://crosstrust.co.jp/support/faq/faq08/a001)

>CSRとは、「Certificate Signing Request」 の頭文字を取ったもので、認証局に対し、SSLサーバ証明書への署名を申請する内容です。CSR には「公開鍵」とその所有者情報、及び申請者が対応する秘密鍵を持っていることを示すために申請者の署名が記載されています。

APNsの概要を見ると分かるようにProviderとAPNsはTLSで接続を行う必要があり、その際に利用する証明書作成を依頼するために必要なファイルであると理解すると分かりやすいです(違ってたらどなたか教えて頂けると!)

ではCSRファイルをMacのKeyChainアプリより作成します。

+ KeyChain Appを開きます
+ メニューバーよりキーチェーンアクセス>証明書アシスタント>認証局に証明書を要求を選択

画面が表示されるので以下を入力します。

+ ユーザのメールアドレス->AppleDeveloperに登録してあるメールアドレス
+ 通称->概要などの説明を英語で記載
+ CAのメールアドレス->空欄
+ 要求の処理->ディスクに保存を選択

続けるを選択すると任意の場所にCRSファイルを保存します。
証明書要求がディスク上に保存されましたと表示されるので確認しておきます。

出力されたファイルを確認すると以下のようになっているかと思われます。

```CertificateSigningRequest.certSigningRequest
-----BEGIN CERTIFICATE REQUEST-----
MIICkjCCAXoCAQAwTTEjMCEGCSqGSIb3DQEJARYUZnVydXRvbkBhbWF6xxxxx
-----END CERTIFICATE REQUEST-----
```

# APNs証明書作成

次に先程作成したCSRファイルを利用し、APNs証明書を作成します。
再度 AppleDeveloperにログインし、Cerfiticates,IDs & Profilesを選択。
次にCertificates->Allを選択することで証明書一覧が確認できます。
右上の「+」を選択肢、証明書を追加します。

![スクリーンショット 2016-09-22 14.54.56.png](https://qiita-image-store.s3.amazonaws.com/0/20193/a4194775-d9ca-9f09-c86e-e8dbd6954388.png "スクリーンショット 2016-09-22 14.54.56.png")


What type of certificate do you need?　と聞かれるので今回はPUSH通知となるので Development の Apple Push Notification service SSL (Sandbox) を選択します。

次に利用するAppIDを聞かれるので、先程の手順で作成したAppIDを選択します。

次の画面でCSRを作成してねといわれます。作成方法はこの画面でも書いてありますね。

![スクリーンショット 2016-09-22 14.58.25.png](https://qiita-image-store.s3.amazonaws.com/0/20193/9a55e7ee-4bbb-86ee-4774-714bf7ee8ed8.png "スクリーンショット 2016-09-22 14.58.25.png")

次の画面で作成してあるCSRをアップロードします。

アップロード後、以下の画面が表示され、証明書がダウンロードできます。

![スクリーンショット 2016-09-22 15.05.56.png](https://qiita-image-store.s3.amazonaws.com/0/20193/fd5ed1e0-4526-4c22-7d7c-a604ae32cb5e.png "スクリーンショット 2016-09-22 15.05.56.png")

ダウロード後、対象の証明書ファイル(.cer)をダブルクリックすることで、キーチェインに登録します。

登録後はキーチェインアプリでAppIDで検索すると証明書が確認できます。

なお、証明書ファイルは画面にもかいてあるように任意の場所にバックアップしておいた方が良いと思います。

このタイミングでAppIDを確認するとPush Notificationsを確認すると有効化されていることが確認できます。

![スクリーンショット 2016-09-22 15.16.02.png](https://qiita-image-store.s3.amazonaws.com/0/20193/14b294aa-cf49-49ab-0f38-42908b6ba3d9.png "スクリーンショット 2016-09-22 15.16.02.png")

# APNsに対して通信する際に利用する証明書(p12)の作成

キーチェインアプリを使って証明書(p12)ファイルを作成します。

[PKCS #12 個人情報交換ファイルフォーマットについて](http://qiita.com/kunichiko/items/3e2ec27928a95630a73a)

キーチェインアプリ->分類->証明書と選択し、ここでAppIDでフィルタすると恐らく一つの証明書のみとなるかと思います。
こちらの2つ折りになっているものを開き、2つ選択した状態で右クリックし、「2個を書き出す」を選択して、p12形式のファイルをエクスポートします。

![スクリーンショット 2016-09-22 15.29.05.png](https://qiita-image-store.s3.amazonaws.com/0/20193/0d8aa29b-adaa-f673-e9c1-ca38c4cafba3.png "スクリーンショット 2016-09-22 15.29.05.png")

エクスポート途中でパスワードの入力を促されるので指定します。
p12ファイルが生成されたら保管しておきます。

# プロジェクトの作成

Xcodeを開いて以下のようにプロジェクトを作成します。

+ Project->AppID作成時に末尾に指定した文字
+ Language->Swiftを選択
+ Organization Identifier->AppIDに登録した文字のProject名を除く部分を指定

念のため、実機へのインストール及び起動を確認します。

# アプリの設定

CapabilitiesのPush NotificatioをONにします。


![スクリーンショット 2016-09-26 7.00.13.png](https://qiita-image-store.s3.amazonaws.com/0/20193/b9d07bf3-cc4b-1272-3eea-ecf44b72681c.png "スクリーンショット 2016-09-26 7.00.13.png")


また、CapabilitiesのBackdround ModesをONにし、Background fetchとRemote notificationにチェックを入れます。

![スクリーンショット 2016-09-22 22.20.22.png](https://qiita-image-store.s3.amazonaws.com/0/20193/4e7f6951-e897-53ac-0740-119bc2cca359.png "スクリーンショット 2016-09-22 22.20.22.png")

SingingでProvisioningProfileを指定する必要がある記事があったのですが、Xcode8ではAutomaticが選択され、明示的に指定するのはDeprecatedのようだったので自動で設定されることを期待し、このままとします。

また、今回は以下の記事に従い、Swift2系を使います。(情報が少なく、Swiftほぼ書いたことないのでさすがに辛かった)

設定は以下の記事を参考に実施しました。

[Xcode8だけどSwift2.3で実行したい人向け対応](http://qiita.com/Hakota/items/4a5e9201c7dc17e9017a)


# コードの変更

以下を参考に実装します。

[Swift 2.0でのプッシュ通知の設定(registerUserNotificationSettings)](http://qiita.com/koishi/items/e7b9d235870505060c63)

[aws-sdk-ios-samples/SNS-MobileAnalytics-Sample/Swift/Sample_SNS_MobileAnalytics/
](https://github.com/awslabs/aws-sdk-ios-samples/tree/master/SNS-MobileAnalytics-Sample/Swift/Sample_SNS_MobileAnalytics)

アプリ起動後にPush通知設定ダイアログを表示するようにAppDelegate.swiftに以下のコードを追記します。


```AppDelegate.swift
    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        
        let settings = UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound,], categories: nil)
        UIApplication.sharedApplication().registerUserNotificationSettings(settings)
        UIApplication.sharedApplication().registerForRemoteNotifications()
        
        return true
    }

```

また、以下のようにDeviceTokenを受信した時に呼び出されるメソッドを追記します。
受信したDeviceTokenは \<xxxxx xxxxxx xxxxx xxxx\> というような形式で取得されますが、APNsへのPushが出来るPaaSサービスなどではスペースと\<\>が必要ない場合があるため、整形した文字列も表示します。（今回は整形前の文字列を利用します)

```AppDelegate.swift
    func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
        

        print("device token = " + deviceToken.description)
        
        let deviceTokenString = "\(deviceToken)"
            .stringByTrimmingCharactersInSet(NSCharacterSet(charactersInString:"<>"))
            .stringByReplacingOccurrencesOfString(" ", withString: "")
        
        print("device token string = " + deviceTokenString)
        NSUserDefaults.standardUserDefaults().setObject(deviceTokenString, forKey: "deviceToken")
        
    }
```

最後にAPNsからPush通知を行った際に呼び出させるメソッドを実装します。アプリがフォアグラウンドであるか、バックグラウンドであるか呼ばれるメソッドが違うので注意。

```AppDelegate.swift
    //フォアグラウンドでPush通知
    func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
        print("foreground")
        print("userInfo: \(userInfo)")
    }
    
    //バックグラウンドでPush通知
    func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
        
        print("background")
        print(userInfo)
        completionHandler(.NoData)
    }
```

# アプリを実行してみる

アプリをビルドし、実機にインストールします。
実行後、実機でアプリが起動し、Push通知を許可するダイアログが表示されるので許可します。
無事実行できればXcodeではDeviceToken情報が表示されるのでメモします。

# houstonを使ってAPNsのPush通知を行う

APNsへのPushにはPaaSサービスなどを使う方法もありますが、今回はhoustonというRubyのGemを使います。

[nomad/houston](https://github.com/nomad/houston)

まずはインストール

```bash
$gem install houston
```

次にAPNsの通知の際にはTLS通信を行う際に利用する証明書及び秘密鍵を生成します。
xxxxとなっている部分はp12ファイルを出力する際に設定したパスワードで置き換えてください。

```bash
$openssl pkcs12 -in Push.p12 -out push_development.pem -password pass:xxxx -nodes
```

hustonではCLIでも実行できますが、最初はコードを書いて実行してみます。
以下のようにREADMEにかかれている通り記載します。
APN.certificateには先程作成したpemファイル、tokenにはiOSで取得したDeviceTokenを設定します。ここでは<>及びスペースのままで指定してください。

```publish.rb
require 'houston'

# Environment variables are automatically read, or can be overridden by any specified options. You can also
# conveniently use `Houston::Client.development` or `Houston::Client.production`.
APN = Houston::Client.development
APN.certificate = File.read("/xxxxxx/push_development.pem")

# An example of the token sent back when a device registers for notifications
token = "<xxx xxxx xxxx>"

# Create a notification that alerts a message to the user, plays a sound, and sets the badge on the app
notification = Houston::Notification.new(device: token)
notification.alert = "Hello, World!"

# Notifications can also change the badge count, have a custom sound, have a category identifier, indicate available Newsstand content, or pass along arbitrary data.
notification.badge = 57
notification.sound = "sosumi.aiff"
notification.category = "INVITE_CATEGORY"
notification.content_available = true
notification.custom_data = {foo: "bar"}

# And... sent! That's all it takes.
result = APN.push(notification)
```

では実行します。

```bash
$ruby publish.rb
```

無事成功すれば実機でPush通知が見れます。
アプリがフォアグラウンドの場合にはログのみが記載されます。
バックグラウンドであれば、iOSのよくみるPush通知が確認できます。

お疲れ様でしたー。

# (おまけ)hustonでCLIからPush通知

以下のように指定することでCLIからもPush通知が実施できます。

```bash
$apn push "<xxxx xxxxx xxxxx>" -c ~/xxxx/push_development.pem -m "Hello from CLI"
```