本文へスキップ
Hironobu Iga

Push 通知に必要な DeviceToken の文字列取得

didRegisterForRemoteNotificationsWithDeviceToken で渡ってくる Data を String に変換する方法と、iOS 13 での description の変更について。

公開日

この記事は別サイトにも掲載しています。 https://iganin.hatenablog.com/entry/2019/10/14/173102

はじめに

サーバーからの Push 通知を実行するためには、サーバーに DeviceToken の文字列を保持し、その DeviceToken を指定して APNs(Apple Push Notification Service)に通信を行う必要があります。DeviceToken は registerForRemoteNotifications() の実行によって、application(_:didRegisterForRemoteNotificationsWithDeviceToken:) メソッドを通して通知されます。

しかし、通知される DeviceToken は Data 型であるため、Server に送信するためには多くの場合、ここから String 型に変換する必要があります。本記事では、変換に関して iOS 13 以降での description メソッドに関する変更も交え記載します。

環境設定

以下の環境を使用しています。

  • iOS 13.1.2
  • Xcode 11.1.0

iOS 13 以降での変更点

まず iOS 13 以降で description メソッドの返却値が変更になった点に関して記載します。 iOS 13 以前では (deviceToken as NSData).description にて <1xxxxxx5 55xxxxca xxxxx572 00c3xxxx xxxxxxx5 92xxxx90 xxxxxxx7d bxxxxxxa> という文字列が取得できていました。サービスによってはここから以下のメソッドで <> と半角スペースを取り除いて送ったり、そのまま Server に送信していたりしていたかと思います。

// <> と半角スペースを取り除く場合のメソッド例
let token = ((deviceToken as NSData).description.trimmingCharacters(in: CharacterSet(charactersIn: "<>")) as NSString).replacingOccurrences(of: " ", with: "")

しかし、iOS 13 以降で NSData の description メソッドの返却値が下記のように変更になりました。{length=32,bytes=0x3dd8xxxxxxxx0be290cxxxxxxxxxx2e0...7xxxxxxxxxxxx0a} 形式が大きく変わっていますし、文字列部分に ... という省略が加わったことにより、deviceToken の取得ができなくなりました。

AppStore に出ているアプリでも本事象が原因と思われるバグ修正のリリースがいくつか見られます。

対応方法

純粋に文字列部分のみ取得したい場合は、@mono0926 さんの以下のメソッドがシンプルで良いかと思います。12xxxx6a5xxxxxxxcad80xxxxxxxxxx3b93exxxxxx9xxxxxxx32xxxxxxb83967be こちらのような文字列が取得できます。

let token = deviceToken.map { String(format: "%.2hhx", $0) }.joined()

ただ、NSData の description メソッドの返却値を今までそのまま送っており、諸般の事情で <> や半角スペースを含んだ状態で DeviceToken の文字列を送らなければならない場合もあるかと思います。その場合は、例えば以下のようにして NSData の description メソッドと同様の文字列を取得できます。

元の文字列を参考に Data を 4bytes 区切りで文字列に変換しています。最後に <> を付与して返却することで iOS 13 未満での NSData の description メソッドの返却値を再現しています。配列生成時に capacity を考慮するとより処理効率が上がりそうですが、実行速度はそれほど問題にはならないと判断しこのままにしています。String(deviceToken: deviceToken) のように使用して文字列を取得できます。

extension String {
    /// DeviceToken を Data から生成する初期化メソッドです。
    /// RemoteNotification の実行に必要な Token を生成します。
    ///
    /// - Parameter deviceToken: Data から生成した DeviceToken 文字列
    ///   例: <70a7ff15 cxxx3c44 bd0xxx2c f75xxxxb dxxxc23f ff14xxxe 6xxxx3f2 b6xxxxxb>
    init(deviceToken: Data) {
        let groupingUnit: Int = 4 // 4bytes ずつ区切って文字列を作成する
        let groupCount: Int = 8 // 4bytes 区切りの文字列を 8 つ作成する

        var deviceTokenStrings = [String](repeating: "", count: groupCount)
        deviceToken.enumerated().forEach { deviceTokenStrings[Int($0) / groupingUnit] += String(format: "%.2hhx", $1) }
        self = "<" + deviceTokenStrings.joined(separator: " ") + ">" // 前後を < > で囲う
    }
}

まとめ

Push 通知の送信に必要な DeviceToken の文字列取得に関して、<> や半角スペースを含めない場合の方法と含める場合の方法を記載しました。NSData の description メソッドを使用しており、iOS 13 以降で対応が必要になった場合はご参考にしていただけますと幸いです。

参考