※現在iOSのバージョンupにつき、記事修正中です。
コメントにてエラーが報告されていますので、あくまでご参考までにお願いいたします。
この記事では、AWSからiOS端末へRemotePush通知を送る方法について解説を行います。
iOSではSwift、サーバーサイドではRubyでコードを記述しています。またローカライズにも対応させています。
##参考サイト
https://github.com/aws/aws-sdk-ios
http://akisute.com/2014/08/apple-push-notification-apn-delegate.html
##プロジェクト作成
ひとまずXCodeにてプロジェクトを作成しておきます。Project名は"apnsTest"としました。ちなみにiOS7以上を想定しています。
またAWSと連携を行う為、AWS SDKのライブラリを使用します。Podfile
に以下のコードを追加して、インストールしてください。CocoaPodsについてよくわからない方はこちらを参考にしてみてください。
pod 'AWSCore'
pod 'AWSAutoScaling'
pod 'AWSCloudWatch'
pod 'AWSDynamoDB'
pod 'AWSEC2'
pod 'AWSElasticLoadBalancing'
pod 'AWSKinesis'
pod 'AWSS3'
pod 'AWSSES'
pod 'AWSSimpleDB'
pod 'AWSSNS'
pod 'AWSSQS'
pod 'AWSCognito'
インストールが終わったら、Bridging-Headerにimportするヘッダーファイルを記述します。Bridging-Headerについてよく分からない方はこちらを参考にしてください。
#import <AWSCore/AWSCore.h>
#import <AWSS3/AWSS3.h>
#import <AWSDynamoDB/AWSDynamoDB.h>
#import <AWSSQS/AWSSQS.h>
#import <AWSSNS/AWSSNS.h>
#import <AWSCognito/AWSCognito.h>
##APNsのための証明書&ProvisiongProfile発行
続いてMemberCenterにて証明書を発行します。
AppIDの作成
まずはAppIDの作成から行います。
App Nameは適当な名前でも大丈夫ですが、Explicit App IDにはXCodeのBundleIdentifierと同様の値を入力します。
App Servicesのpush Notificationにも忘れずにチェックを入れます。
あとは流れに沿って作成します。
###APNsの証明書作成
続いて以下の画面で右上の+ボタンを押して新たな証明書を作ります。
今回はDevelopment版なので、"Apple Push Notification service SSL (Sandbox)"にチェックを入れてContinueボタンを押します。
そして先ほど作成したAppIDを選択します。
###認証局に証明書を要求
ここでアップロードするためのCSRファイルを作成します。KeyChain Access Appを開いて
キーチェーンアクセス>証明書アシスタント>認証局に証明書を要求
をすると以下のように証明書情報が出てきますので、AppDeveloperに登録したメールアドレスと名前を入力し"ディスクに保存"にチェックを入れ、"続ける"を押し適当な場所に保存します。
作成したCSRファイルをアップロードし、しばらくするとcerファイルがダウンロードできる画面に移行しますので、ダウンロードします。ダウンロードしたcerファイルをダブルクリックすると、keychain Access Appが開き、自動的に証明書が登録されます。
Provisioning Profile作成
アプリでAPNsを取得するにはDevelopment環境でも固有のProvisioningProfileが必要になります。"Provisioning Profiles"から右上の+ボタンを押し、DevelopmentのProvisioningProfileを作成します。App IDは先ほど作成したものを選択し、あとは流れに沿って最終的にダウンロードします。
##AWS SNS
続いてAWS上でAPNsを実行するための手続きを行います。
###IAM(Identity and Access Management)
最初にAWSのサービス一覧からIAMサービスを選択し、AWS SNSのリソースにアクセスするための権限を取得します。左ペインのUsersから"Create New Users"をクリックします。
そうするとuser作成の画面が出てきますので、適当に名前をつけます。
作成に成功するとAccessKeyとSecretKeyを発行してくれるので、ダウンロードします。ダウンロードする機会は1度きりらしいので、必ず落としましょう。
次に作成したユーザーに、SNSに関する制御の権限を与えます。
作成したユーザーをクリックし、PermissionのAttach Policyをクリックします。Policy一覧が表示されますので、"AmazonSNSFullAccess"の項目にチェックを入れて"Attach Policy"をクリックします。
以下のように権限が与えられます。
SNSのplatform application作成
さて、続いてSNSの"Platform application"を作成します。AWSのサービス一覧からSNSを選択します。ちなみに"platform application"はAWSからAppleのサーバーにpush通知を依頼するためのものです。
SNS Home>Create platform application
を選択すると以下のような画面が出てきます。
"Application name"には適当な名前を入れます。
"Push notification platform"は、今回はiOSのdevelopment版なので、"Apple Push Notification Service Sandbox(APNS_SANDBOX)"を選択します。ちなみにProduction版の場合は"Apple Push Notification Service"を選択することになると思います(多分ですが)。
ここで、先ほどMemberCenterで作成したAPNsの証明書をAWSに渡します。再び"Keychain Access"を開き、作成した証明書を右クリックし、"書き出す"を選択します。
証明書の名前は適当でいいのですが、日本語ではなくローマ字にしておいた方が無難のようです。同時にパスワードも求められますが、こちらも適当でOKです。作成に成功するとp12ファイルが出来上がるので、こちらを"Choose P12 File"で選択します。"Enter Password"にはp12ファイル作成時と同じパスワードを入力します。最後にLoad Credentials from Fileをクリックすると、それぞれのKey欄に情報が自動的に入力されます。
作成が完了すると以下のように、ARN(Amazon Resource Name)が割り振られます。
ようやく下準備が終わりました。
iOS側実装その1
さて、まずはRemotePush通知を受け取るためにCapabilitiesの"Background Modes"をオンにして、"Background fetch"と"Remote notification"にチェックを入れます。
また固有のProvisioningProfileを指定する必要があるので、先ほどダウンロードしたProvisioningProfileを指定します。
続いてAppDelegate.swift
に以下のようにRemotePushを受け取るための依頼処理をapplication:didFinishLaunchingWithOptions
に記述します。iOS7と8では処理コードが異なりますので、場合分けしてあります。
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
let osVersion = UIDevice.currentDevice().systemVersion
if osVersion < "8.0" {
//iOS7
application.registerForRemoteNotificationTypes( UIRemoteNotificationType.Badge | UIRemoteNotificationType.Sound | UIRemoteNotificationType.Alert )
}
else {
//iOS8以降
let settings = UIUserNotificationSettings(forTypes: UIUserNotificationType.Badge | UIUserNotificationType.Sound | UIUserNotificationType.Alert, categories: nil)
application.registerUserNotificationSettings(settings)
application.registerForRemoteNotifications()
}
return true
}
さらにAppDelegate.swift
にDelegateを追加します。以下の2つを追加してください。
func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError) {
println(error)
}
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
}
1つ目はエラー時、2つ目は依頼成功時に呼ばれるDelegateになっています。成功するとAppleのサーバーからDeviceTokenが送られてくるので、2つ目の関数に処理を記述していきますが、その前にAWSのprotocolを追加しておきましょう。AWSCredentialsProvider
protocolを追加し、またaccessKey
と、secretKey
をAppDelegateの変数に追加しておきます。
class AppDelegate: UIResponder, UIApplicationDelegate,AWSCredentialsProvider {
var window: UIWindow?
//変数accessKey,secretKeyを追加
var accessKey:String?
var secretKey:String?
...
続いて依頼成功時に呼ばれる関数application:didRegisterForRemoteNotificationsWithDeviceToken
に必要な処理を記述します。
func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
println(deviceToken);
//①DeviceTokenを成形
let deviceTokenString = "\(deviceToken)"
.stringByTrimmingCharactersInSet(NSCharacterSet(charactersInString:"<>"))
.stringByReplacingOccurrencesOfString(" ", withString: "")
//DeviceTokenを端末に保存
NSUserDefaults.standardUserDefaults().setObject(deviceTokenString, forKey: "deviceToken")
//AWS SNSへ接続するためのKey設定
self.accessKey = "IAMで取得したAccessKeyを設定"
self.secretKey = "IAMで取得したSecretKeyを設定"
//customUserDataに保存するために、端末の言語情報をjson形式で記述
let languages = NSLocale.preferredLanguages()
let lang = languages.first as String!
let json = "{\"lang\":\"\(lang)\"}"
let serviceConfiguration = AWSServiceConfiguration(region:AWSRegionType.APNortheast1, credentialsProvider: self) //regionはAWS SNSで作成したApplicationのARNに記述してあるものと同様のTypeを選択
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = serviceConfiguration
let sns = AWSSNS.defaultSNS()
let input = AWSSNSCreatePlatformEndpointInput()
input.token = deviceTokenString //DeviceTokenを設定
input.customUserData = json //任意の情報を設定(今回は言語情報を記述)
input.platformApplicationArn = "AWS SNSで作成したApplicationのARNを設定"
//②AWS SNSへ端末情報を登録
sns.createPlatformEndpoint(input).continueWithExecutor(BFExecutor.mainThreadExecutor(), withBlock: { (task: BFTask!) -> AnyObject! in
if task.error != nil {
println("Error: \(task.error)")
} else {
//AWS SNSに端末情報(Endpoint)の登録が成功すると、自身の端末のARN(endpointArn)がSNSから送られてくる
let createEndpointResponse = task.result as AWSSNSCreateEndpointResponse
println("endpointArn: \(createEndpointResponse.endpointArn)")
//割り振られたEndpointArnを保存
NSUserDefaults.standardUserDefaults().setObject(createEndpointResponse.endpointArn, forKey: "endpointArn")
}
return nil
})
}
①まずはDeviceTokenをNSUserDefaults
等を使って保存しておきます。ただ送られてきたDeviceTokenはそのままでは余計なものが含まれているので、適切な形に成形して保存します。次に送られてきたDeviceTokenをAWS SNSへ登録します。IAMで取得したAccessKeyとSecretKeyをそれぞれ設定して、AWSに接続できるようにします。
②AWS SNSにDeviceTokenを含めた端末情報を送信します。今回端末情報には言語設定の情報も一緒に送信しています。この情報を送ることで、サーバー側からローカライズされたデータ(文字)を送ることができるようになります。詳しくは後述します。
またinput.platformApplicationArnにはAWS SNSで作成したapplicationのArnを設定します。そして登録が成功すると、クロージャのBFTaskにはAWS SNSから割り振られたEndpointArnが入っています。こちらもDeviceTokenと同様にNSUserDefaults
などで保存しておきましょう。EndpointArnを保持することで、AWS SNSのCustomUserDataの更新が可能になります。例えば端末の言語情報が変更された場合、以下のようにしてAWS SNS上のCustomUserDataを更新することができます。
//CustomUserDataはendpointArnがあれば更新可能.
func updateCustomUserDataForSNS() {
let endpointArn = NSUserDefaults.standardUserDefaults().stringForKey("endpointArn")
var serviceConfiguration = AWSServiceConfiguration(region: .APNortheast1, credentialsProvider: self)
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = serviceConfiguration
let sns = AWSSNS.defaultSNS()
let languages = NSLocale.preferredLanguages()
let lang = languages.first as String!
let json = "{\"lang\":\"\(lang)\"}"
let input = AWSSNSSetEndpointAttributesInput()
//CustomUserDataを設定
input.attributes = NSDictionary(object: json, forKey: "CustomUserData")
input.endpointArn = endpointArn
//SNSに端末情報を送信
sns.setEndpointAttributes(input).continueWithSuccessBlock { (task) -> AnyObject! in
return nil
}.continueWithBlock { (task:BFTask!) -> AnyObject! in
if let error = task.error {
println(error)
}
return nil
}
}
では実機で動かしてみます。以下のようにpush通知の確認画面が出てOKボタンを押した後、application:didRegisterForRemoteNotificationsWithDeviceToken
が呼ばれていれば成功です。
AWS SNSでも確認してみましょう。作成したApplicationのEndpointsに、登録されたデバイスの情報が1つあるはずです。またUser Dataのカラムにも言語情報が追加されました。
##サーバー側実装
さて今度はサーバー側に処理コードを記述し、実際にpush通知を飛ばします。前述しましたが、本記事ではrubyで実装します。
まずはruby向けのAWS SDKをgem
でインストールします。また今回はjsonも使用するので同様にinstallします。
gem install aws-sdk
gem install json
installができたら以下のように処理コードを書いていきます。
require 'aws-sdk-v1'
require 'json'
#①accessKeyとsecretKeyを設定
aws_access_key = 'IAMで取得したaccessKeyを設定'
aws_secret_key = 'IAMで取得したsecretKeyを設定'
region = 'ap-northeast-1' #platform applicationと同様のregionを設定
AWS.config(
access_key_id:aws_access_key,
secret_access_key:aws_secret_key,
region:region
)
sns = AWS::SNS.new
client = sns.client
#②AWS SNSで作成したApplicationのArnを設定
endpoints = client.list_endpoints_by_platform_application(
platform_application_arn: 'AWS SNSで作成したApplicationのArnを設定',
)
#それぞれのEndpointの処理
endpoints[:endpoints].each do |endpoint|
#③endpointのcustomDataを参照
customUserData = endpoint[:attributes]["CustomUserData"]
json_info = JSON.parse(customUserData)
alert_message = ""
body_title = ""
body_message = ""
body_button_positive_string = ""
body_button_negative_string = ""
#userDataの情報からローカライズ分岐
if json_info["lang"] == "ja" then
alert_message = "おはようございます"
body_title = "朝です"
body_message = "今日もいい天気ですね"
body_button_positive_string = "はい"
body_button_negative_string = "いいえ"
else
alert_message = "Good Morning"
body_title = "It's morning"
body_message = "It's nice weather again today"
body_button_positive_string = "YES"
body_button_negative_string = "NO"
end
#============ここからメッセージ===============
apns_payload = {
#apsには決められた形式で記述を行う
"aps" => {
"alert" => alert_message, #通知バーに表示される文字 コメントアウトするとサイレント通知モードになる
"sound" => 'default', #通知が来た時になる音. カスタム音も流せる
"content-available" => 1, #バッククラウンド処理を有効化
"priority" => 10 #これを書いておくことで通知の成功率がupする.デフォでは5
},
# 以下に任意の情報を入れていく.
"body" => {
"title" => body_title,
"message" => body_message,
"positiveButton" => body_button_positive_string,
"negativeButton" => body_button_negative_string
}
}.to_json
message = { "default" => "default", "APNS_SANDBOX" => apns_payload }.to_json
#============ここまでメッセージ===============
client.publish(
target_arn: endpoint[:endpoint_arn],
message: message,
message_structure: 'json'
)
end
いろいろと書いてありますが、大事なところは
①accessKeyとsecretKeyの設定
②applicationArnの設定
③CustomUserDataの活用
くらいだと思います。
①はiOS側で記述した理由と同様、AWS SNSのデータを扱うための権利を与えてあげます。②ではどのapplicationにpush通知を依頼するかを指定しています。そして③ですが、iOS側からあらかじめ端末の言語情報を取得することで、push通知を送る段階でローカライズが可能になります。
また"メッセージ"の部分に関しては、"aps"に決められた形式で記述を行う必要がありますが、その外側では自由にデータの記述が可能です。ただしデータ(ペイロード)の最大サイズは256byteとなっているので、注意が必要です。 apsの詳細に関してはこちらを参考にしてみてください。以上でサーバー側の準備は完了です。
##iOS側実装その2
さてサーバー側ではpush通知を飛ばす準備ができましたが、iOSの方では肝心のpush通知を受け取るDelegateを記述していなかったので、以下のように記述します。今回は受け取ったpush通知のデータを元にAlertViewを表示させてみます。
//RemotePush通知取得関数
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
println(userInfo)
//userInfoにはサーバー側で記述した"apns_payload"が格納されている
if let customData = userInfo["body"] as? NSDictionary {
var alertView = UIAlertView()
alertView.title = customData["title"] as NSString
alertView.message = customData["message"] as NSString
alertView.addButtonWithTitle(customData["positiveButton"] as String!)
alertView.addButtonWithTitle(customData["negativeButton"] as String!)
alertView.cancelButtonIndex = 1
alertView.delegate = self
alertView.show()
}
completionHandler(.NoData)
}
##Push通知
ようやくすべての準備が揃いましたので、サーバーからpush通知を飛ばしてみます。
ruby publish.rb
と入力し、以下のように通知、およびAlertViewが出れば成功です。お疲れさまでした。