A Day In The Life

とあるプログラマの備忘録

Swift3で追加されたData構造体の使い方レシピ

Swift3 から Data 構造体が追加され Objective-C 時代の NSData クラスはお役御免になりました。この Data 構造体は NSData の置き換えなので、できることに違いはほとんどありません。ただしメソッドに互換性がないため NSData を使ったコードを Data に置き換えるのが大変です。というわけで Data 構造体の使い方をまとめてみました。

文字列を Data オブジェクトに変換する

文字列を Data オブジェクトに変換するには以下のように String 構造体の data メソッドを使います。

let str = "abcdefghijk"
let data = str.data(using: .utf8)!

Data型のオブジェクトを文字列に変換する

反対に Data オブジェクトを文字列に変換するには String 構造体のイニシャライザを使って以下のように変換します。

let str = String(data: data, encoding: .utf8)!

ただしこの方法は Data オブジェクトの中が文字列で構成されている場合しか使えません。 Data オブジェクトの中が文字列以外の時は、次の方法で変換します。

Data オブジェクトを16進数文字列に変換する

print 関数に Data オブジェクトを渡してもバイト数しか出力されません。

let data = Data(bytes: [0, 1, 127, 128, 255])
// 出力結果は 5 bytes
print(data)
// NSData だとちゃんと内容が出力されるのに…
let nsdata = NSData(data: data)
// 出力結果は <00017f80 ff>
print(nsdata) 

こんなときは Data 構造体の map メソッドを使って以下のように1バイトづつ16進数文字に変換します。

let data = Data(bytes: [0, 1, 127, 128, 255])
let hexStr = data.map {
  String(format: "%.2hhx", $0)
}.joined()
// 出力結果は 00017f80ff
print(hexStr)

ワンライナーで書くと以下のようになります。

print(data.map { String(format: "%.2hhx", $0) }.joined())

デバッグで出力を確認するだけであれば以下の方法もあります。

// 出力結果は<61626364 65666768 696a6b>
print(String(format: "%@", data as CVarArg))

Data オブジェクトを固定長の型(UInt8とかDoubleなど)のオブジェクトに変換する

Data オブジェクトを UInt8 や Double など固定長の型のオブジェクトに変換するには Data 構造体の withUnsafeBytes メソッドを使います。以下は Data オブジェクトを Double オブジェクトに変換する例です。

let data = Data(bytes: [0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40])
let value = data.withUnsafeBytes { (ptr: UnsafePointer<Double>) -> Double in
  return ptr.pointee
}
// 出力結果は42.13
print(value)

上記の方法は文字列など可変長のデータには使えませんのでご注意下さい。

バイトデータにアクセスしたい

CommonCrypto ライブラリを使ってデータをハッシュ化したい時なんかにバイトデータにアクセスしなきゃいけない場面があります。Swift2.xでは以下のように書けました。

let data = NSData(bytes: bytes, length: 15)
let dataBytes = UnsafePointer<CUnsignedChar>(data.bytes)

Swift3では UnsafePointer の仕様が変わったので Data オブジェクトを NSData に変換してから上記の方法を使ってもコンパイルエラーになります。少し面倒ですが UnsafeRawPointer を使って以下のように処理します。

let bytes: [UInt8] = [
    0xe3, 0x81, 0x82,
    0xe3, 0x81, 0x84,
    0xe3, 0x81, 0x86,
    0xe3, 0x81, 0x88, 
    0xe3, 0x81, 0x8a
] 
let data = Data(bytes: bytes)
let charPtr = UnsafeMutablePointer<CUnsignedChar>.allocate(capacity: data.count)
charPtr.initialize(from: data)
let dataBytes = UnsafeRawPointer(charPtr)
// メモリを解放する
defer {
  charPtr.deinitialize(count: data.count)
  charPtr.deallocate(capacity: data.count)
}
:
: なんか処理
:

メモリ解放しなきゃいけないのがちょっと辛いです。

メモリ解放を気にせずバイトデータにアクセスしたい場合

「Data オブジェクトを固定長の型に変換する」で使った withUnsafeBytes メソッドを使うとバイトデータにアクセスできます。クロージャの外からはポインタが無効なのでメモリ安全にバイトデータにアクセスできます。

let bytes: [UInt8] = [
    0xe3, 0x81, 0x82,
    0xe3, 0x81, 0x84,
    0xe3, 0x81, 0x86,
    0xe3, 0x81, 0x88, 
    0xe3, 0x81, 0x8a
] 
let data = Data(bytes: bytes)
data.withUnsafeBytes { (ptr: UnsafePointer<CUnsignedChar>) in {
    let dataBytes = UnsafeRawPointer(ptr)
    :
    : なんか処理
    :
}

withUnsafeBytes メソッドに渡すクロージャは任意の戻り値に設定できるので上記のように戻り値なしにもできますし、逆にクロージャ内でハッシュ化の処理をいろいろしてから文字列で返すみたいなこともできます。

参考