以下の書き方がオススメです
let token = deviceToken.map { String(format: "%.2hhx", $0) }.joined()
The English version is here: How to convert an APNs token to String
from Data
?
以下、以前微妙な書き方をしていてSwift 3移行時にうまく変換できなくなってしまった反省や、他の書き方などの紹介となっています。
UIApplicationDelegate
のapplication(_:didRegisterForRemoteNotificationsWithDeviceToken:)
で渡ってくるdeviceToken
(デバイストークン)はData
型(Swift 2.2まではNSData
型)ですが、それを16進数文字列に変換するやり方について紹介します。
僕はSwift 2.2までこんな感じで書いてしまっていました。
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
let token = deviceToken.map { String(format: "%.2hhx", $0) }.joined()
reduce
を使う
上の例と似たような感じですが、map
・join
ではなくreduce
で処理しました。
初めはこっちで書いちゃいましたが、よく考えたら上の例の方が読みやすいと思って書き換えました。好みの範囲かもしれません(例えば、コードレビューで、reduce
よりmap
・join
でやった方が良いなどとは僕は指摘しません)。
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
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:)
イニシャライザーを利用
フォーマット指定子を使った方法です。
String(format: "%.2hhx", dataElement)
String
のinit(_:radix:uppercase:)
イニシャライザーを利用
フォーマット指定子は特に今回のように普段あまり使わない種類は、使う時も読む時も分かりにくいですよね。というわけでやっている処理が分かりやすいこちらを採用したいと思ったものの、dataElement
が15以下の時に一桁になってしまうのでダメでした(´・︵・`)
String
のinit(format:)
イニシャライザーを利用した例では、この桁も考慮された書き方になっています。
String(dataElement, radix: 16)
無理矢理0詰めするとしたら、こんな感じですかね( ´・‿・`)
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つ目の書き方がお勧めですヽ(・ω・`)
個人的には、このように定義して使っています。
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
使うのが今思いつく中ではベストの書き方かなと個人的には思いました。