Edited at

AWS SNSを使ってiOSへpush通知

More than 3 years have passed since last update.

※現在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以上を想定しています。

01

またAWSと連携を行う為、AWS SDKのライブラリを使用します。Podfileに以下のコードを追加して、インストールしてください。CocoaPodsについてよくわからない方はこちらを参考にしてみてください。


Podfile


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についてよく分からない方はこちらを参考にしてください。


apnsTest-Bridging-Header.h


#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と同様の値を入力します。

appID01.png

App Servicesのpush Notificationにも忘れずにチェックを入れます。

appID02.png

あとは流れに沿って作成します。


APNsの証明書作成

続いて以下の画面で右上の+ボタンを押して新たな証明書を作ります。

certificate.png

今回はDevelopment版なので、"Apple Push Notification service SSL (Sandbox)"にチェックを入れてContinueボタンを押します。

cirtificate2.png

そして先ほど作成したAppIDを選択します。

certificate03.png


認証局に証明書を要求

ここでアップロードするためのCSRファイルを作成します。KeyChain Access Appを開いて

キーチェーンアクセス>証明書アシスタント>認証局に証明書を要求

をすると以下のように証明書情報が出てきますので、AppDeveloperに登録したメールアドレスと名前を入力し"ディスクに保存"にチェックを入れ、"続ける"を押し適当な場所に保存します。

certificate04.png

作成したCSRファイルをアップロードし、しばらくするとcerファイルがダウンロードできる画面に移行しますので、ダウンロードします。ダウンロードしたcerファイルをダブルクリックすると、keychain Access Appが開き、自動的に証明書が登録されます。


Provisioning Profile作成

アプリでAPNsを取得するにはDevelopment環境でも固有のProvisioningProfileが必要になります。"Provisioning Profiles"から右上の+ボタンを押し、DevelopmentのProvisioningProfileを作成します。App IDは先ほど作成したものを選択し、あとは流れに沿って最終的にダウンロードします。

certificate06.png


AWS SNS

続いてAWS上でAPNsを実行するための手続きを行います。


IAM(Identity and Access Management)

最初にAWSのサービス一覧からIAMサービスを選択し、AWS SNSのリソースにアクセスするための権限を取得します。左ペインのUsersから"Create New Users"をクリックします。

aws01.png

そうするとuser作成の画面が出てきますので、適当に名前をつけます。

aws05.png

作成に成功するとAccessKeyとSecretKeyを発行してくれるので、ダウンロードします。ダウンロードする機会は1度きりらしいので、必ず落としましょう。

aws06.png

次に作成したユーザーに、SNSに関する制御の権限を与えます。

作成したユーザーをクリックし、PermissionのAttach Policyをクリックします。Policy一覧が表示されますので、"AmazonSNSFullAccess"の項目にチェックを入れて"Attach Policy"をクリックします。

aws08.png

以下のように権限が与えられます。

aws04.png


SNSのplatform application作成

さて、続いてSNSの"Platform application"を作成します。AWSのサービス一覧からSNSを選択します。ちなみに"platform application"はAWSからAppleのサーバーにpush通知を依頼するためのものです。

SNS Home>Create platform application

を選択すると以下のような画面が出てきます。

aws11.png

"Application name"には適当な名前を入れます。

"Push notification platform"は、今回はiOSのdevelopment版なので、"Apple Push Notification Service Sandbox(APNS_SANDBOX)"を選択します。ちなみにProduction版の場合は"Apple Push Notification Service"を選択することになると思います(多分ですが)。

ここで、先ほどMemberCenterで作成したAPNsの証明書をAWSに渡します。再び"Keychain Access"を開き、作成した証明書を右クリックし、"書き出す"を選択します。

aws10.png

証明書の名前は適当でいいのですが、日本語ではなくローマ字にしておいた方が無難のようです。同時にパスワードも求められますが、こちらも適当でOKです。作成に成功するとp12ファイルが出来上がるので、こちらを"Choose P12 File"で選択します。"Enter Password"にはp12ファイル作成時と同じパスワードを入力します。最後にLoad Credentials from Fileをクリックすると、それぞれのKey欄に情報が自動的に入力されます。

aws09.png

作成が完了すると以下のように、ARN(Amazon Resource Name)が割り振られます。

aws12.png

ようやく下準備が終わりました。


iOS側実装その1

さて、まずはRemotePush通知を受け取るためにCapabilitiesの"Background Modes"をオンにして、"Background fetch"と"Remote notification"にチェックを入れます。

ios01.png

また固有のProvisioningProfileを指定する必要があるので、先ほどダウンロードしたProvisioningProfileを指定します。

ios02.png

続いてAppDelegate.swiftに以下のようにRemotePushを受け取るための依頼処理をapplication:didFinishLaunchingWithOptionsに記述します。iOS7と8では処理コードが異なりますので、場合分けしてあります。


AppDelegate.swift


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つを追加してください。


AppDelegate.swift


func application(application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: NSError) {
println(error)
}

func application(application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {

}


1つ目はエラー時、2つ目は依頼成功時に呼ばれるDelegateになっています。成功するとAppleのサーバーからDeviceTokenが送られてくるので、2つ目の関数に処理を記述していきますが、その前にAWSのprotocolを追加しておきましょう。AWSCredentialsProviderprotocolを追加し、またaccessKeyと、secretKeyをAppDelegateの変数に追加しておきます。


AppDelegate.swift


class AppDelegate: UIResponder, UIApplicationDelegate,AWSCredentialsProvider {

var window: UIWindow?
//変数accessKey,secretKeyを追加
var accessKey:String?
var secretKey:String?

...


続いて依頼成功時に呼ばれる関数application:didRegisterForRemoteNotificationsWithDeviceTokenに必要な処理を記述します。


AppDelegate.swift

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を更新することができます。


AppDelegate.swift


//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が呼ばれていれば成功です。

01

AWS SNSでも確認してみましょう。作成したApplicationのEndpointsに、登録されたデバイスの情報が1つあるはずです。またUser Dataのカラムにも言語情報が追加されました。

ios04.png


サーバー側実装

さて今度はサーバー側に処理コードを記述し、実際にpush通知を飛ばします。前述しましたが、本記事ではrubyで実装します。

まずはruby向けのAWS SDKをgemでインストールします。また今回はjsonも使用するので同様にinstallします。

gem install aws-sdk

gem install json

installができたら以下のように処理コードを書いていきます。


publish.rb


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を表示させてみます。


AppDelegate.swift


//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が出れば成功です。お疲れさまでした。

01

01