久々の iOS のトピック。今回はiOSで初めてプッシュ通知(Push Notification)を利用したので、それのメモ。
プッシュ通知について
サーバーの任意のタイミングで情報を端末に送信する仕組み。これぞアプリのできること、として欠かせない機能だ。何か最新ニュースが出たりとか、リアルタイムで情報を送りたいときによく使われる。てか人気アプリで使ってないところはないくらいな機能だ。
Webアプリでは任意のタイミングでブラウザに情報を渡す仕組みを実装するには、やや特殊なことをしないと難しい。(Web Socket や Polling などを利用するだろう)
設定手順
Apple Developer で証明書作成
iOSはコードは簡単だけど、Apple Developer での登録がめんどくさい。まずはPush Notification を利用できるようにするために証明書を作成する必要がある。
AppIDでプッシュ通知したいアプリを作成し、 ここのPush Notifications の項目をオンにして作成。そんで作成したAppIDを選択し、Edit から CreateCertificate を選択。キーチェーンアクセスで作成したcertSigningRequest ファイルをアップロードし、証明書を作成する。
その証明書をキーチェーンアクセスへドラッグアンドドロップして、それをディスクに書き出す。これがp12形式にして書き出す。これに合わせてプロビジョニングファイルも作成しておこう。
Amazon SNS でのプッシュ
Amazon SNS を使えば、 iOS, Androidの区別をほとんどきにすることなく、プッシュ通知を送ることが可能だ。しかも何万通もプッシュ通知送っても料金は非常に低価格。これは使わない手はない。
Application で Apple Development を選択し、 p12 ファイルをアップロード。その後Load Credential from file を押すと作成ボタンが押せるようになる。作成したApp Arn をサーバーから参照できるようにしよう。今回はAWS_SNS_APP_ARNとして環境変数に保存する。
サーバーにてAmazon SNSの通知を発行
今回は Ruby を例に、発行方法を記述。bundler にて gem "aws-sdk"
でsdkをインストール。
config/initializers/aws.rb
Aws.config.update({ region: ENV['AWS_REGION'], credentials: Aws::Credentials.new(ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY']) })
app/controllers/samples_controller.rb
sns = Aws::SNS::Client.new response = client.create_platform_endpoint( platform_application_arn: ENV['AWS_SNS_APP_ARN'], token: device_uuid ) target_arn = response[:endpoint_arn] message = { "APNS" => { "aps" => { "content-available" => true }, "other_data" => obj }.to_json, "APNS_SANDBOX" => { "aps" => { "content-available" => true }, "other_data" => obj }.to_json } push_parameters = { target_arn: target_arn, message_structure: "json", message: message.to_json } resp = sns.publish(push_parameters)
obj
に送りたいデータ、target_arn に送り先のTargetArnを選択しよう。target arn は iOSアプリから送信されたトークンを元に作成する。今回の変数でいうdevice_uuid
だ。
iOSアプリで通知を受け取る
これは簡単。 AppDelegate にて任意のメソッドを実装するだけだ。JSON解析のためにSwiftyJSONを利用している。
class AppDelegate: UIResponder, UIApplicationDelegate { func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { // 通知を許可 var types: UIUserNotificationType = UIUserNotificationType.Badge | UIUserNotificationType.Alert | UIUserNotificationType.Sound var settings: UIUserNotificationSettings = UIUserNotificationSettings( forTypes: types, categories: nil ) application.registerUserNotificationSettings( settings ) application.registerForRemoteNotifications() return true } // Device Token を取得 func application( application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData ) { var characterSet: NSCharacterSet = NSCharacterSet( charactersInString: "<>" ) var deviceTokenString: String = ( deviceToken.description as NSString ) .stringByTrimmingCharactersInSet( characterSet ) .stringByReplacingOccurrencesOfString( " ", withString: "" ) as String println( deviceTokenString ) // このトークンをサーバーに送って保存 } // プッシュを受け取ったとき func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) { let json = JSON(userInfo) let notification = NSNotification( name:"my_push", object: nil, userInfo:[ "title": json["other_data", "title"].stringValue, ] NSNotificationCenter.defaultCenter().postNotification(notification) } completionHandler(.NoData) } }
NSNotificationCenter
からmy_push
という名の通知がアプリ内に飛ぶので、それをViewController側でキャッチしてあげます。
class ViewController: UIViewController { override func viewDidAppear(animated: Bool) { NSNotificationCenter.defaultCenter().addObserver(self, selector: "handlePushNotification:", name: "my_push", object: nil) } override func viewDidDisappear(animated: Bool) { NSNotificationCenter.defaultCenter().removeObserver(self) } // observer fired func handlePushNotification(notification: NSNotification) { // 通知を受け取ったときの処理 } }
終わりに
Androidは設定は簡単だけど、実装が面倒。 iOSは設定が面倒だけど実装が簡単。そんな印象。iOSではデフォルトでAndroidでいうEventBusのような仕組み(NSNotificationCenter)を提供してくれてるのがありがたい。乱用するとカオスになりかねないが、こういうときには役立つ機能だと思う。
ただ通知する中身が空だったり間違ったりすると通知が来なかったりするパターンがあるので、送るデータがちゃんと入っているかを確認したほうがいい。これにかなりつまずいた。