注:コードはSwift 5(Xcode 10.2)用に更新されています。(Swift 3とSwift 4.2のバージョンは編集履歴にあります。)また、位置合わせされていないデータも正しく処理されるようになりました。
Data
値から作成する方法
Swift 4.2以降、データは値から簡単に作成できます
let value = 42.13
let data = withUnsafeBytes(of: value) { Data($0) }
print(data as NSData) // <713d0ad7 a3104540>
説明:
から値を取得する方法 Data
Swift 5以降、withUnsafeBytes(_:)
of Data
はUnsafeMutableRawBufferPointer
、バイトに対して「型なし」のクロージャーを呼び出します。load(fromByteOffset:as:)
この方法は、メモリから値を読み取ります。
let data = Data([0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40])
let value = data.withUnsafeBytes {
$0.load(as: Double.self)
}
print(value) // 42.13
このアプローチには1つの問題があります。それは、メモリがそのタイプに合わせてプロパティで揃えられている必要があります(ここでは、8バイトのアドレスに揃えられています)。ただし、データが別のData
値のスライスとして取得された場合など、これは保証されません。
したがって、バイトを値にコピーする方が安全です。
let data = Data([0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40])
var value = 0.0
let bytesCopied = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: $0)} )
assert(bytesCopied == MemoryLayout.size(ofValue: value))
print(value) // 42.13
説明:
の戻り値copyBytes()
は、コピーされたバイト数です。これは宛先バッファのサイズと同じか、データに十分なバイトが含まれていない場合はそれ以下です。
一般的なソリューション#1
上記の変換は、次の汎用メソッドとして簡単に実装できますstruct Data
。
extension Data {
init<T>(from value: T) {
self = Swift.withUnsafeBytes(of: value) { Data($0) }
}
func to<T>(type: T.Type) -> T? where T: ExpressibleByIntegerLiteral {
var value: T = 0
guard count >= MemoryLayout.size(ofValue: value) else { return nil }
_ = Swift.withUnsafeMutableBytes(of: &value, { copyBytes(to: $0)} )
return value
}
}
ここに制約T: ExpressibleByIntegerLiteral
を追加して、値を「ゼロ」に簡単に初期化できるようにします。このメソッドは「trival」(整数および浮動小数点)タイプで使用できるため、実際には制限ではありません。以下を参照してください。
例:
let value = 42.13 // implicit Double
let data = Data(from: value)
print(data as NSData) // <713d0ad7 a3104540>
if let roundtrip = data.to(type: Double.self) {
print(roundtrip) // 42.13
} else {
print("not enough data")
}
同様に、配列を以下に変換できますData
。
extension Data {
init<T>(fromArray values: [T]) {
self = values.withUnsafeBytes { Data($0) }
}
func toArray<T>(type: T.Type) -> [T] where T: ExpressibleByIntegerLiteral {
var array = Array<T>(repeating: 0, count: self.count/MemoryLayout<T>.stride)
_ = array.withUnsafeMutableBytes { copyBytes(to: $0) }
return array
}
}
例:
let value: [Int16] = [1, Int16.max, Int16.min]
let data = Data(fromArray: value)
print(data as NSData) // <0100ff7f 0080>
let roundtrip = data.toArray(type: Int16.self)
print(roundtrip) // [1, 32767, -32768]
一般的なソリューション#2
上記のアプローチには1つの欠点があります。実際には、整数や浮動小数点型などの「自明な」型でのみ機能します。以下のような「複合」タイプArray
とString
なるストレージに(隠された)ポインタを持っているだけで、構造体自体をコピーすることによって周り渡すことはできません。また、実際のオブジェクトストレージへの単なるポインタである参照型では機能しません。
だからその問題を解決し、
変換Data
および変換の方法を定義するプロトコルを定義します。
protocol DataConvertible {
init?(data: Data)
var data: Data { get }
}
プロトコル拡張のデフォルトメソッドとして変換を実装します。
extension DataConvertible where Self: ExpressibleByIntegerLiteral{
init?(data: Data) {
var value: Self = 0
guard data.count == MemoryLayout.size(ofValue: value) else { return nil }
_ = withUnsafeMutableBytes(of: &value, { data.copyBytes(to: $0)} )
self = value
}
var data: Data {
return withUnsafeBytes(of: self) { Data($0) }
}
}
ここでは、提供されたバイト数が型のサイズと一致するかどうかをチェックする失敗可能な初期化子を選択しました。
そして最後に、安全に変換Data
および変換できるすべての型への適合を宣言します。
extension Int : DataConvertible { }
extension Float : DataConvertible { }
extension Double : DataConvertible { }
// add more types here ...
これにより、変換がよりエレガントになります。
let value = 42.13
let data = value.data
print(data as NSData) // <713d0ad7 a3104540>
if let roundtrip = Double(data: data) {
print(roundtrip) // 42.13
}
2番目のアプローチの利点は、不注意で安全でない変換を実行できないことです。欠点は、すべての「安全な」タイプを明示的にリストする必要があることです。
次のような重要な変換が必要な他のタイプのプロトコルを実装することもできます。
extension String: DataConvertible {
init?(data: Data) {
self.init(data: data, encoding: .utf8)
}
var data: Data {
// Note: a conversion to UTF-8 cannot fail.
return Data(self.utf8)
}
}
または、独自の型に変換メソッドを実装して必要なことをすべて行うので、値をシリアル化および逆シリアル化します。
バイトオーダー
上記の方法ではバイトオーダー変換は行われず、データは常にホストバイトオーダーになります。プラットフォームに依存しない表現(「ビッグエンディアン」または「ネットワーク」バイトオーダーなど)の場合は、対応する整数のプロパティまたはそれぞれを使用します。初期化子。例えば:
let value = 1000
let data = value.bigEndian.data
print(data as NSData) // <00000000 000003e8>
if let roundtrip = Int(data: data) {
print(Int(bigEndian: roundtrip)) // 1000
}
もちろん、この変換は、一般的な変換方法で一般的に行うこともできます。