【iOS】Push通知に必要なDeviceTokenの文字列取得に関して

はじめに

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

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

環境設定

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

  • iOS 13.1.2
  • Xcode11.1.0

iOS13以降での変更点

まずiOS13以降でdescriptionメソッドの返却値が変更になった点に関して記載します。 iOS13以前では(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: "")

しかし、iOS13以降で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区切りで文字列に変換しています。最後に<>を付与して返却することでiOS13未満での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のdesctiptionメソッドを使用しており、iOS13以降で対応が必要になった場合はご参考にしていただけますと幸いです。

参考