Help us understand the problem. What is going on with this article?

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした