iOSでのリモートプッシュ通知(Apple Push Notification service)をRubyでやってみた

証明書作成

証明書は、開発環境用と本番環境用で別々の物を作成しなければいけません。 この記事では、開発用のみに焦点を当てていきます。 開発と本番で違うのは、証明書を新たに作ることと、APNsのホストが違うこと、、くらいなんでしょうか(不安)。

  1. "Certificates, Identifiers & Profiles" の "App IDs" からプッシュ通知を使いたいアプリの設定へ行き "Push Notifications" のチェックを入れる
  2. "Development SSL Certificate" の "Create Certificate..." ボタン
  3. 証明書を作成して登録する
    1. キーチェーンアクセスからいつも通り証明書リクエストを作成
    2. Appleに投げる
    3. 証明書をゲット!
  4. Keychainで証明書をp12形式で書き出す
  5. openssl pkcs12 -in input.p12 -out output.pem -nodes -clcerts を実行してpem形式のファイルを作成する

クライアント側の処理

次のようなコードを AppDelegate の application:didFinishLaunchingWithOptions: に書くと、 初回起動時に「プッシュ通知がどうのこうの」というアラートビューが表示され、 リモートの通知に登録されます。

[[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeBadge)];

このメソッドの実行結果は、

  • application:didRegisterForRemoteNotificationsWithDeviceToken:
  • application:didFailToRegisterForRemoteNotificationsWithError:

のどちらかのデリゲート呼び出しによって返ってきます。 名前を見れば明らかですが、前者は正しく動作した場合、後者は何かしらの問題が発生した場合に呼び出されます。

前者のメソッドの引数として与えられるバイナリデータを16進数で表現した文字列が 「デバイストークン」という物体です。

https://github.com/jpoz/APNS のREADMEに良い感じの変換関数があるのでそれを使います。 というか、この記事自体、このREADMEを主な情報源としています。

NSString *stringFromDeviceTokenData(NSData *deviceToken)
{
    const char *data = [deviceToken bytes];
    NSMutableString *token = [NSMutableString string];
    for (int i = 0; i < [deviceToken length]; i++) {
        [token appendFormat:@"%02.2hhX", data[i]];
    }
    return [token copy];
}

今回は、この関数を例のメソッドから呼んでデバイストークンを文字列化して コンソールに出力します。

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
    NSString *deviceTokenAsString = stringFromDeviceTokenData(deviceToken);

    // 実際の処理ではここでサーバにデバイストークンを送信する?
    NSLog(@"device token: %@", deviceTokenAsString);
}

サーバ側の処理

自分で実装するのはアレなので、先人の功績を利用させていただきましょう。 https://github.com/jpoz/APNS ちなみにMITライセンスです。

Gem "apns" をインストール。 Bundlerを使った方が良い気もしますが、とりあえずのテスト用なのでテキトーに(Bundler使ったこと無い><)。

sudo gem install apns

次に、先に作成しておいたpemファイルを適当な場所に置きます。

1つのデバイスにプッシュ通知を送るサンプルコードは次のようになります。 Rubyがよくわからずに書いていますが、一応動くのは確認済みです。 このGemを使った場合に日本語を使えないと言っているブログを見かけましたが、今回は日本語のメッセージも送信できました。

# -*- coding: utf-8 -*-

require 'apns'

# 本番では 'gateway.push.apple.com'
APNS.host = 'gateway.sandbox.push.apple.com'
APNS.pem = '/path/to/pem/file'
APNS.pass = ''
APNS.port = 2195

token = 'device_token_1'
APNS.send_notification(token, :alert => '(´へωへ`*)', :badge => 9, :sound => 'default')

このコードを実行すると、device_token_1 の端末に「(´へωへ`*)」というメッセージ、バッジに数字9、例の通知音による通知が届きます。

複数の端末に通知を送るには、次のような感じでやればいけます。 この方法で万とか十万とかのオーダーのデバイスに送信したときの負荷とか速度がどうなるかはわからないです。

# -*- coding: utf-8 -*-

require 'apns'

# 本番では 'gateway.push.apple.com'
APNS.host = 'gateway.sandbox.push.apple.com'
APNS.pem = '/path/to/pem/file'
APNS.pass = ''
APNS.port = 2195

t1 = 'device_token_1'
t2 = 'device_token_2'

notifications = []
for token in [t1, t2]
  notifications << APNS::Notification.new(token, :alert => '(´へωへ`*)', :badge => 9, :sound => 'default')
end
APNS.send_notifications(notifications)

このコードでは、device_token_1, device_token_2 の端末に「(´へωへ`*)」というメッセージ、バッジに数字9、例の通知音による通知が届きます。

とりあえずこれでリモートからのプッシュ通知ができるようになりました!
めでたしめでたし。

注意事項

  1. ワイルドカードな Provisioning Profile を使っているとハマります
    そのアプリ専用の Provisioning Profile を作って使用しましょう。 詳細とか解決方法とかはこのブログを参照 http://blog.kawauso.com/kawauso/2010/07/aps-environment_no_valid_aps-e.html

  2. 開発環境と本番環境で APNS のホストが異なる
    開発用の証明書では、本番用のホストに通知リクエストを投げても無効です。

    • 開発: gateway.sandbox.push.apple.com
    • 本番: gateway.push.apple.com
  3. 本番環境のテストができない
    プッシュ通知が本当に必要なアップデートの前に、こっそりとプッシュ通知テスト用のアップデートを織り交ぜて本番環境でのプッシュ通知テストを実施すべきです。