【iPhone】Push Notificationの実装方法

Posted by: daichi  /  Category: iphone開発

pic1pic2pic3
新作でTwitter,はてな,Google Readerから横断的に情報収集し、マルチポストするアプリを作っているのですが、そこでTwitterのリプライPush機能を実装したので、Push Notificationを実装する方法をまとめてみます。

Push Notificationの流れ

Push Notificationに関する登場人物は、
  • iPhone
  • Apple Push Notification Service(APNs)
  • Provider
の3者です。

iPhoneはいわずもがな、みんなの手元にあるiPhone。APNsはAppleが用意しているPushしてくれるやつです。Providerは、開発者が用意するもので、こいつがPushしたい情報を送る役割を持ちます。

この3者間でのデータの流れは大きくデバイス登録とPush通知の2つのフェーズに分かれます。

デバイス登録

デバイス登録フェーズではAPNsへPush通知するiPhoneを登録します。このときAPNsから一意なデバイストークンが発行され、これをProviderが保持することで、Push通知を送りたいiPhoneをProviderが特定できるようになります。
流れはこんな感じ。
  1. iPhoneにアプリをインストール
  2. アプリ起動時にiPhoneからAPNsへデバイスの認証通知をする
  3. APNsから認証されると、デバイストークンを受け取る
  4. iPhoneからデバイストークンをProviderへ送る

Push通知

Push通知フェーズでは、ProviderからAPNsへPush情報と共に認証情報を送ります。APNsから認証されると、送信先iPhone宛にPush通知が送られ、晴れてiPhone上であのAlertが表示されるようになります。
流れはこんな感じ。
  1. 任意のタイミングでProviderからPush情報と認証情報をAPNsへ送る
  2. APNsから認証されるとiPhoneへPush通知が送られる
  3. iPhone上でポップアップ!
というわけで、以降デバイス登録から順に書いていきます。

鍵を作る

デバイス登録手順1のアプリインストールは飛ばすとして、2のiPhoneからAPNsへデバイス認証通知をするためには、鍵が必要です。

なのでApple Developer Connection – iPhone Dev Center – Overviewへ行って鍵を作りましょう。
手順はこんな感じです。
  1. 左メニューApp IDsからNew App ID
  2. Descriptionには自分のわかりやすい説明を
  3. Bundle Seed IDはGenerate NewでOK
  4. Bundle Identiferは*は使えません。net.longearth.earth等、一意な値となるようにドメイン名を逆から綴ったものにしておく
  5. 以上を入力してsubmit
  6. App IDsのManage画面で登録したApp IDのConfigureリンクを押す
  7. ひとまず開発用の設定をするため、Development Push SSL CertificateのConfigureを押す
  8. Lightbox風に鍵作れといわれるのでキーチェインを起動
  9. キーチェーンアクセス→証明書アシスタント→認証局に証明書を要求…
  10. ユーザのメールアドレスは登録したメールアドレスを
  11. コモンネームは他の証明書の時と同じ名前を
  12. ディスクに保存をチェック→続ける→保存
  13. ブラウザに戻りContinueを押す
  14. さっき作った証明書を選択してGenerate
  15. 認証されたらContinue
  16. Downloadなうして、Done
  17. Lightbox風が終わるので画面上でステータスがenabledになっていることを確認
  18. ダウンロードした証明書をダブルクリックしてキーチェーンに保存
  19. キーチェーンの「自分の証明書」分類にあるApple Development Push Servicesを選択して、メニュー→ファイル→書き出す…→保存(apns-dev-cert.p12)
  20. 同様にキーチェーン画面からApple Development Push Services横の三角を開き、秘密鍵を選択→メニュー→ファイル→書き出す…→保存(apns-dev-key.p12)
長い。。。

ここまでがApple上のドキュメントに書いてあるのですが、p12ファイルとやらをどう使えばよいのかよくわからないので、pemフォーマットに変換(正直この部分はわかってない。男は黙って以下のコマンド群を打ち込む。)
  1. openssl pkcs12 -clcerts -nokeys -out apns-dev-cert.pem -in apns-dev-cert.p12
  2. openssl pkcs12 -nocerts -out apns-dev-key.pem -in apns-dev-key.p12
  3. openssl rsa -in apns-dev-key.pem -out apns-dev-key-noenc.pem
  4. cat apns-dev-cert.pem apns-dev-key-noenc.pem > apns-dev.pem
これでapns-dev.pemとapns-dev-key-noenc.pemができあがるので、これで鍵の準備は完了。

鍵ができたら、ここで作成したApp IDを指定したProvisioningファイルを作成して、Xcodeにインストール。code signに設定しておくことも忘れずに。

デバイス認証通知

認証通知は簡単です。UIApplicationのregisterForRemoteNotificationTypes:で通知処理を設定します。これだけでcode signされた内容でデバイス認証しに行く様子。アプリ起動直後あたりに仕込んでおきます。ちなみにここでPush時にバッヂ表示させるか、音をならすか、アラート画面を出すかを選択できます。以下はすべてを通知させるような例。

?View Code OBJECTIVE-C
- (void)applicationDidFinishLaunching:(UIApplication *)application {
	[[UIApplication sharedApplication] 
	 registerForRemoteNotificationTypes:
             (UIRemoteNotificationTypeBadge| 
	      UIRemoteNotificationTypeSound|
		UIRemoteNotificationTypeAlert)];
        // 他の処理...
}


デバイストークンを受け取る

APNsから認証されるとデバイストークンを受け取ることができます。認証後はUIApplicationのapplication:didRegisterForRemoteNotificationsWithDeviceToken:メソッドが呼ばれるのでここで受け取ります。

?View Code OBJECTIVE-C
- (void)application:(UIApplication*)app 
didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)devToken{ 
	NSLog(@"deviceToken: %@", devToken); 
	self.devToken;
        [self sendProviderDeviceToken:devToken];
}

sendProviderDeviceToken:メソッド内でProviderへデバイストークンを送る処理をしています。NSLogで送られてきたdevTokenをログ出力してますが、このとき表示される32バイトの値がデバイストークンです。後のproviderでのテストのために控えておきます。
認証エラー時は以下のメソッドが呼ばれます。

?View Code OBJECTIVE-C
- (void)application:(UIApplication*)app 
didFailToRegisterForRemoteNotificationsWithError:(NSError*)err{ 
	NSLog(@"Errorinregistration.Error:%@",err); 
}

code signが間違っている等、分かりやすい日本語で表示してくれているので助かります。

Providerへデバイストークンを送る

ここは好きなようにProvider宛にデバイストークンをPostしてあげてください。
ちなみにこんな感じでポストできるはずです。

?View Code OBJECTIVE-C
- (void)sendProviderDeviceToken:(NSData *)token {
		NSMutableData *data = [NSMutableData data];
		[data appendData:[@"device=" dataUsingEncoding:NSUTF8StringEncoding]];
		[data appendData:token];
 
 
		self.request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"ホスト名"]];
		[request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];		
		[request setHTTPMethod:@"POST"];
		[request setHTTPBody:data];
		[NSURLConnection connectionWithRequest:request delegate:self];
}


サーバ側では、このデバイストークンを控えておきます。実用的な所でいうと、このデバイストークンと何かしらのユーザを識別するIDをサーバへ送ることになると思います。
ここまでで、デバイスの登録が完了です。

ProviderからAPNsへPush通知を送る

ProviderからAPNsへの通知は以下のようなフォーマットで送る必要があります。

aps_provider_binary
始めの3バイトは固定で、0,0,32。16進では0×00,0×00,0×20です。その後デバイストークンと続き、0×00,送信データサイズ、送信データという流れです。
このあたりはバイナリにまとめたものをファイルに書き出し、ターミナルから
xxd ファイル名
として中身を確認することをおすすめします。アプリ側のNSLogで出力したデバイストークンは16進表記なので、その値がきれいに上のデバイストークンの場所に収まっていればだいじょぶだと思います。

送信処理をrubyで書くとこんな感じです。以下のスクリプトを実行する時は、作成した証明書と秘密鍵を同じディレクトリに置いておいてください。

#!/usr/bin/ruby
 
require 'openssl'
require 'socket'
 
    device = ['デバイストークンのバイナリ値']
 
    socket = TCPSocket.new('gateway.sandbox.push.apple.com',2195)
 
    context = OpenSSL::SSL::SSLContext.new('SSLv3')
    context.cert = OpenSSL::X509::Certificate.new(File.read('apns-dev.pem'))
    context.key  = OpenSSL::PKey::RSA.new(File.read('apns-dev-key-noenc.pem'))
 
    ssl = OpenSSL::SSL::SSLSocket.new(socket, context)
    ssl.connect
 
    payload = <<-EOS
{
  "aps":{
    "alert":"New Message!",
    "badge":1,
    "sound":"default"
  }
}
EOS
    (message = []) << ['0'].pack('H') << [32].pack('n') << device.pack('H*') << [payload.size].pack('n') << payload
 
 
    ssl.write(message.join(''))
    ssl.close
    socket.close

上の例では、Alert表示に”New Message!”を出して、アプリアイコンバッヂに1を表示させ、デフォルトの音をならすような通知設定をしています。設定方法の詳細はApple Push Notificationサービス プログラミングガイドにあります。

証明書と鍵さえ正しいものを使っていれば、ローカルのmacからでもPushすることができるので、ローカルでごにょごにょ確認するのがいいと思います。ここまでくれば、送る相手のデバイストークンさえ分かればPushできるので、登録されたデバイストークンを全取得する方法を用意しておけば、ローカルから最新情報告知なんていう使い方もできそうです。

特に気の知れた相手のデバイストークンが分かっていれば、「うんこ」なんて通知もできてしまいそうです。もちろん自重します。絶対。

これから

あとはアプリの任意のタイミングで上記のように送りたい相手のデバイストークンを取得して、メッセージを送信することでPushすることができます。
麻雀アプリなら上家がパイを切った後に、Providerへ通知させて、次の相手のデバイストークンを引っ張ってきてPushという形になるでしょう。

Twitterのリプライ通知をどのようにしようかすごく悩んでいたのですが、Stream APIが使えそうです。
Twitter API Wiki / Streaming API Documentation
試してみた所、まだアルファテスト版ということでだいぶ不安定な気もします。Twit Pushとかどうやっているのか気になります。

長いことおつかれさまでした。
しかし、めんどくさい。
もうちょっと楽にできないかな。

参考

後で気づいたけど日本語のドキュメントがあった。
iPhone Dev Center
cocoa*life – Apple Push Notification Serviceを利用した、iPhone クライアントと、Rubyによるサーバの作成。
How to build an Apple Push Notification provider server (tutorial) « Boxed Ice Blog

タグ: API, iphone, iphone dev center, mac, objecti, objective-c, Push Notification, twitter, Xcode, アイコン, アプリ, スクリプト, ダウンロード, 変換, 証明書, ,

関連する投稿

5 Responses to “【iPhone】Push Notificationの実装方法”

  1. naokits Says:

    詳細な解説お疲れさまです。私も最初に実装したときはずいぶん悩みました。「本当にこれで合ってるのか?」とかw。
    しかし本当に面倒くさすぎです。アプリ内課金はさらにめんどいですね。受託開発でも実装したいという要望はあるのですが、依頼元がこの面相臭さ(テストも)を理解してくれないと、開発者側が無くはめになります。
    毎回こんな事をやるのかと思うと気が遠くなりますね。ということで、サービスを探してみたところ、次のサイトでプロバイダ側の作業を軽減してくれるサービスを見つけました。

    http://urbanairship.com/
    http://www.httpush.com/

    urbanairshipの方はアプリ内課金にも対応しています。
    かなり安い金額だと思いますので、これらを使うのも手だと思います。iPhoneアプリからの接続を担当するプログラムをGAEなどに置けば、全体的なコストはかなり安くなるのではないでしょうか。

    実際に「http://urbanairship.com/」を試してみましたが、簡単にサービスを実装できました。お試しください。

    ではでは。

  2. daichi Says:

    >naokitsさん
    こんなサービスがあったんですね!
    確かに月額この程度の料金ならば、委託してしまった方がいいかもしれませんね。
    貴重な情報ありがとうございました。
    検討してみます。

  3. naokits Says:

    daichiさん、

    こんなのもありました。こちらもなかなか良いです。価格も少し安めですね。

    iLime™ - Push Notification and In App Purchase
    https://www.ilime.com/

    アプリ内課金、滅茶めんどいです。凝った事をしないのであれば、有料サービス使った方がいいですね。あまりたいした事で来ませんが、金額はかなり安いと思います。

    日本でも同様のサービスが出てますが、海外の方が安いですね。それにしても、国内で開発者を食い物にしようとするサービスがいろいろ出てきてますね。私は絶対に使いませんけどw。

  4. hiromieee Says:

    非常に参考になりました!
    有益な記事を公開いただきありがとうございます!

  5. honishi Says:

    有益な情報ありがとうございます。

    私だけかもしれませんが、鍵を作る手順のところで、lightbox風のダイアログがfirefoxだとうまく反応せず、すこしはまりかけました。

    safariだとうまく行きますね。。

コメント

Get Adobe Flash playerPlugin by wpburn.com wordpress themes