Swiftでプッシュ通知用のDevice Token(Data型)を16進数文字列へ変換する方法

# 以下の書き方がオススメです

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

**The English version is here: [How to convert an APNs token to `String` from `Data`?](https://dev.to/mono/how-to-convert-an-apns-token-to-string-from-data-c8e)**

以下、以前微妙な書き方をしていてSwift 3移行時にうまく変換できなくなってしまった反省や、他の書き方などの紹介となっています。

---

`UIApplicationDelegate`の`application(_:didRegisterForRemoteNotificationsWithDeviceToken:)`で渡ってくる`deviceToken`(デバイストークン)は`Data`型(Swift 2.2までは`NSData`型)ですが、それを16進数文字列に変換するやり方について紹介します。

僕はSwift 2.2までこんな感じで書いてしまっていました。

```swift
let token = (deviceToken.description.trimmingCharacters(in: CharacterSet(charactersIn: "<>")) as NSString).replacingOccurrences(of: " ", with: "")
```

(詳しくは後述しますが、元々良くない書き方でした。`Swift`にまだ慣れてない時にググってとりあえず動くコードで対処しちゃった匂いがします🤔)

Swift 3.0・Xcode 8で実行すると、この結果がおかしくなっていました。

```
(lldb) po token
"32bytes"

(lldb) po token.description
"32 bytes"
```

本当は、`8a66140da4c2daa37aacc56aaaaa3aed5354329acdec8f766e557f784515da97`のような64文字を期待していました。
(そういえば、 http://qiita.com/mono0926/items/df03c61adc56934e2e7a#device-tokenサイズが大きくなる-ios-9リリースタイミングでは無いです- に書いてたDevice Tokenのサイズ大きくなる対応がまだですね🤔)

# Swift 3.0での対応法

気に入った順に3通りの解を載せておきます。

## `map`で`deviceToken`の要素を文字列に変換して`join`

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

## `reduce`を使う

上の例と似たような感じですが、`map`・`join`ではなく`reduce`で処理しました。
初めはこっちで書いちゃいましたが、よく考えたら上の例の方が読みやすいと思って書き換えました。好みの範囲かもしれません(例えば、コードレビューで、`reduce`より`map`・`join`でやった方が良いなどとは僕は指摘しません)。

```swift
let token = deviceToken.reduce("") { $0 + String(format: "%.2hhx", $1) }
```

## `NSData`にキャストする

Swift 3.0で`NSData`型の`deviceToken`が`Data`値型にブリッジされて、`deviceToken.description`の結果が変わってしまった、というのがこの問題の本質であり、ではキャストで`NSData`に戻すとどうなる？と試したら、適切な結果が得られました。
参考: [swift-evolution/0069-swift-mutability-for-foundation.md at master · apple/swift-evolution](https://github.com/apple/swift-evolution/blob/master/proposals/0069-swift-mutability-for-foundation.md)

```swift
let token = ((deviceToken as NSData).description.trimmingCharacters(in: CharacterSet(charactersIn: "<>")) as NSString).replacingOccurrences(of: " ", with: "")
```

いい加減な対応に見える気がしますが、意外とありな気がします(　´･‿･｀)
ただ、`description`は文字通り説明的な値なので、こういう値を処理に使うのはやはり適切ではないかなと思いました(つまり、元々の書き方が良くなかったと反省)。


# UInt8 → 16進数文字列への変換方法

上記処理内に、UInt8 → 16進数文字列への変換処理がありますが、それについても2通り良さそうな書き方思いつきましたが、前者使うべきですねという結論になりました。

`Data`型の`UInt8`型要素を`dataElement`としたとしてコード例を示します。

## `String`の`init(format:)`イニシャライザーを利用

フォーマット指定子を使った方法です。

```swift
String(format: "%.2hhx", dataElement)
```

## `String`の`init(_:radix:uppercase:)`イニシャライザーを利用

フォーマット指定子は特に今回のように普段あまり使わない種類は、使う時も読む時も分かりにくいですよね。というわけでやっている処理が分かりやすいこちらを採用したいと思ったものの、`dataElement`が15以下の時に一桁になってしまうのでダメでした(´・︵・｀)
`String`の`init(format:)`イニシャライザーを利用した例では、この桁も考慮された書き方になっています。

```swift
String(dataElement, radix: 16)
```

無理矢理0詰めするとしたら、こんな感じですかね(　´･‿･｀)

```swift
var s = "00" + String(dataElement, radix: 16)
let range = s.range(of: s)
let end = s.endIndex
let start = s.index(end, offsetBy: -2)
s[start..<end]
```

---

というわけで、[1つ目の書き方](http://qiita.com/mono0926/items/3cf0dca3029f32f54a09#mapでdevicetokenの要素を文字列に変換してjoin)がお勧めですヽ(・ω・｀）

個人的には、このように定義して使っています。

```swift
extension String {
    public init(deviceToken: Data) {
        self = deviceToken.map { String(format: "%.2hhx", $0) }.joined()
    }
}

func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data!) {
    let token = String(deviceToken: deviceToken)
}
```

http://stackoverflow.com/a/24979958/1524942 にも色々載っていましたが、`map`・`join`使うのが今思いつく中ではベストの書き方かなと個人的には思いました。