AWS SNSを使ってiOSへpush通知

  • 264
    いいね
  • 1
    コメント
この記事は最終更新日から1年以上が経過しています。

※現在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