Catatan: Kode telah diperbarui untuk Swift 5 (Xcode 10.2) sekarang. (Versi Swift 3 dan Swift 4.2 dapat ditemukan di riwayat edit.) Juga data yang mungkin tidak selaras sekarang ditangani dengan benar.
Cara membuat Data
dari suatu nilai
Mulai dari Swift 4.2, data dapat dibuat dari nilai hanya dengan
let value = 42.13
let data = withUnsafeBytes(of: value) { Data($0) }
print(data as NSData)
Penjelasan:
withUnsafeBytes(of: value)
memanggil closure dengan pointer buffer yang menutupi byte mentah dari nilai.
- Sebuah pointer buffer mentah adalah urutan byte, oleh karena itu
Data($0)
dapat digunakan untuk membuat data.
Cara mengambil nilai dari Data
Pada Swift 5, withUnsafeBytes(_:)
dari Data
memanggil penutupan dengan "untyped" UnsafeMutableRawBufferPointer
ke byte. The load(fromByteOffset:as:)
metode membaca nilai dari memori:
let data = Data([0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40])
let value = data.withUnsafeBytes {
$0.load(as: Double.self)
}
print(value)
Ada satu masalah dengan pendekatan ini: Ini mensyaratkan bahwa memori adalah properti selaras untuk jenis (di sini: selaras dengan alamat 8-byte). Tapi itu tidak dijamin, misalnya jika data diperoleh sebagai bagian dari yang lainData
nilai .
Oleh karena itu, lebih aman untuk menyalin byte ke nilai:
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)
Penjelasan:
withUnsafeMutableBytes(of:_:)
memanggil penutupan dengan penunjuk buffer yang bisa berubah yang mencakup byte mentah dari nilai.
- The
copyBytes(to:)
metode DataProtocol
(yang Data
sesuai) salinan byte dari data ke buffer yang.
Nilai yang dikembalikan copyBytes()
adalah jumlah byte yang disalin. Ini sama dengan ukuran buffer tujuan, atau lebih kecil jika data tidak berisi cukup byte.
Solusi generik # 1
Konversi di atas sekarang dapat dengan mudah diimplementasikan sebagai metode umum dari 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
}
}
Batasan T: ExpressibleByIntegerLiteral
ditambahkan di sini sehingga kita dapat dengan mudah menginisialisasi nilai ke "nol" - itu sebenarnya bukan batasan karena metode ini dapat digunakan dengan jenis "trival" (bilangan bulat dan titik mengambang), lihat di bawah.
Contoh:
let value = 42.13
let data = Data(from: value)
print(data as NSData)
if let roundtrip = data.to(type: Double.self) {
print(roundtrip)
} else {
print("not enough data")
}
Demikian pula, Anda dapat mengonversi array menjadi Data
dan kembali:
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
}
}
Contoh:
let value: [Int16] = [1, Int16.max, Int16.min]
let data = Data(fromArray: value)
print(data as NSData)
let roundtrip = data.toArray(type: Int16.self)
print(roundtrip)
Solusi generik # 2
Pendekatan di atas memiliki satu kelemahan: Ini sebenarnya hanya bekerja dengan tipe "sepele" seperti bilangan bulat dan tipe floating point. Jenis "kompleks" seperti Array
danString
memiliki (tersembunyi) petunjuk ke penyimpanan yang mendasarinya dan tidak dapat disebarkan hanya dengan menyalin struct itu sendiri. Ini juga tidak akan bekerja dengan tipe referensi yang hanya menunjuk ke penyimpanan objek nyata.
Jadi selesaikan masalah itu, seseorang bisa
Tentukan protokol yang mendefinisikan metode untuk mengubah ke Data
dan kembali:
protocol DataConvertible {
init?(data: Data)
var data: Data { get }
}
Menerapkan konversi sebagai metode default dalam ekstensi protokol:
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) }
}
}
Saya telah memilih penginisialisasi yang dapat gagal di sini yang memeriksa bahwa jumlah byte yang diberikan sesuai dengan ukuran jenisnya.
Dan akhirnya nyatakan kesesuaian dengan semua jenis yang dapat dengan aman diubah menjadi Data
dan kembali:
extension Int : DataConvertible { }
extension Float : DataConvertible { }
extension Double : DataConvertible { }
Ini membuat konversi menjadi lebih elegan:
let value = 42.13
let data = value.data
print(data as NSData)
if let roundtrip = Double(data: data) {
print(roundtrip)
}
Keuntungan dari pendekatan kedua adalah Anda tidak dapat secara tidak sengaja melakukan konversi yang tidak aman. Kerugiannya adalah Anda harus membuat daftar semua jenis "aman" secara eksplisit.
Anda juga dapat mengimplementasikan protokol untuk jenis lain yang memerlukan konversi non-sepele, seperti:
extension String: DataConvertible {
init?(data: Data) {
self.init(data: data, encoding: .utf8)
}
var data: Data {
return Data(self.utf8)
}
}
atau terapkan metode konversi sesuai jenis Anda sendiri untuk melakukan apa pun yang diperlukan sehingga membuat serial dan deserialisasi nilai.
Urutan byte
Tidak ada konversi urutan byte yang dilakukan dalam metode di atas, data selalu dalam urutan byte host. Untuk representasi independen platform (misalnya urutan byte "big endian" alias "jaringan"), gunakan properti integer resp. penginisialisasi. Sebagai contoh:
let value = 1000
let data = value.bigEndian.data
print(data as NSData)
if let roundtrip = Int(data: data) {
print(Int(bigEndian: roundtrip))
}
Tentu saja konversi ini juga dapat dilakukan secara umum, dengan metode konversi generik.