Skip to content
Hironobu Iga

Getting the device token string needed for push notifications

How to convert the Data handed to you by didRegisterForRemoteNotificationsWithDeviceToken into a String, and what changed about description in iOS 13.

Published

This article is also published elsewhere. https://iganin.hatenablog.com/entry/2019/10/14/173102

Originally written in Japanese. This is a translation of the same piece.

Introduction

To send push notifications from a server, the server needs to hold the device token string and talk to APNs (Apple Push Notification service) with that token. The device token arrives via the application(_:didRegisterForRemoteNotificationsWithDeviceToken:) method after you call registerForRemoteNotifications().

The token you receive is of type Data, though, so in most cases you need to convert it to a String before sending it to the server. This article covers that conversion, along with the change to the description method in iOS 13 and later.

Environment

  • iOS 13.1.2
  • Xcode 11.1.0

What changed in iOS 13

First, the change to what description returns in iOS 13 and later.

Before iOS 13, (deviceToken as NSData).description gave you a string like <1xxxxxx5 55xxxxca xxxxx572 00c3xxxx xxxxxxx5 92xxxx90 xxxxxxx7d bxxxxxxa>. Depending on the service, you might have stripped the <> and the spaces with a method like the one below before sending it, or sent it to the server as-is.

// Example of a method that strips the <> and the spaces
let token = ((deviceToken as NSData).description.trimmingCharacters(in: CharacterSet(charactersIn: "<>")) as NSString).replacingOccurrences(of: " ", with: "")

In iOS 13 and later, however, NSData’s description changed to return something like {length=32,bytes=0x3dd8xxxxxxxx0be290cxxxxxxxxxx2e0...7xxxxxxxxxxxx0a}. The format is very different, and because the string portion now includes a ... elision, the device token can no longer be recovered from it.

You can see a number of bug-fix releases on the App Store that look like they were caused by exactly this.

How to handle it

If you simply want the string portion, this method from @mono0926 is nice and simple. It gives you a string like 12xxxx6a5xxxxxxxcad80xxxxxxxxxx3b93exxxxxx9xxxxxxx32xxxxxxb83967be.

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

That said, if you have been sending NSData’s description return value directly, circumstances may require you to keep sending the device token string with the <> and spaces included. In that case you can reproduce the same string as NSData’s description like this.

Taking the original string as the reference, this converts the Data into a string in 4-byte groups. Adding <> at the end reproduces what NSData’s description returned before iOS 13. Accounting for capacity when creating the array would probably make it more efficient, but I judged execution speed not to be a concern here and left it as-is. Use it as String(deviceToken: deviceToken) to get the string.

extension String {
    /// An initialiser that builds a device token from Data.
    /// Produces the token needed to run remote notifications.
    ///
    /// - Parameter deviceToken: the device token string built from Data
    ///   e.g. <70a7ff15 cxxx3c44 bd0xxx2c f75xxxxb dxxxc23f ff14xxxe 6xxxx3f2 b6xxxxxb>
    init(deviceToken: Data) {
        let groupingUnit: Int = 4 // build the string in groups of 4 bytes
        let groupCount: Int = 8 // build 8 groups of 4 bytes

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

Summary

That covers getting the device token string needed to send push notifications, both with and without the <> and spaces. If you were using NSData’s description and had to deal with this in iOS 13, I hope it helps.

References