Lưu ý: Mã hiện đã được cập nhật cho Swift 5 (Xcode 10.2). (Bạn có thể tìm thấy các phiên bản Swift 3 và Swift 4.2 trong lịch sử chỉnh sửa.) Cũng có thể dữ liệu chưa được căn chỉnh hiện được xử lý chính xác.
Cách tạo Data
từ một giá trị
Kể từ Swift 4.2, dữ liệu có thể được tạo từ một giá trị chỉ với
let value = 42.13
let data = withUnsafeBytes(of: value) { Data($0) }
print(data as NSData) // <713d0ad7 a3104540>
Giải trình:
withUnsafeBytes(of: value)
gọi bao đóng với một con trỏ đệm bao phủ các byte thô của giá trị.
- Con trỏ đệm thô là một chuỗi các byte, do đó
Data($0)
có thể được sử dụng để tạo dữ liệu.
Cách lấy một giá trị từ Data
Tính đến Swift 5, withUnsafeBytes(_:)
các Data
viện dẫn việc đóng cửa với một “untyped” UnsafeMutableRawBufferPointer
để các byte. Các load(fromByteOffset:as:)
phương pháp đọc giá trị từ bộ nhớ:
let data = Data([0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40])
let value = data.withUnsafeBytes {
$0.load(as: Double.self)
}
print(value) // 42.13
Có một vấn đề với cách tiếp cận này: Nó yêu cầu bộ nhớ được căn chỉnh thuộc tính cho kiểu (ở đây: căn chỉnh thành địa chỉ 8 byte). Nhưng điều đó không được đảm bảo, ví dụ: nếu dữ liệu được lấy dưới dạng một phần của Data
giá trị khác .
Do đó, sẽ an toàn hơn khi sao chép các byte sang giá trị:
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
Giải trình:
Giá trị trả về copyBytes()
là số byte được sao chép. Nó bằng với kích thước của bộ đệm đích hoặc nhỏ hơn nếu dữ liệu không chứa đủ byte.
Giải pháp chung # 1
Các chuyển đổi trên hiện có thể dễ dàng được thực hiện như các phương pháp chung của 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
}
}
Ràng buộc T: ExpressibleByIntegerLiteral
được thêm vào đây để chúng ta có thể dễ dàng khởi tạo giá trị thành “không” - đó không thực sự là một hạn chế vì phương thức này có thể được sử dụng với các kiểu “trival” (số nguyên và dấu phẩy động), xem bên dưới.
Thí dụ:
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")
}
Tương tự, bạn có thể chuyển đổi mảng sang Data
và lùi:
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
}
}
Thí dụ:
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]
Giải pháp chung # 2
Cách tiếp cận trên có một nhược điểm: Nó thực sự chỉ hoạt động với các kiểu "tầm thường" như số nguyên và kiểu dấu phẩy động. Các loại "phức tạp" nhưArray
và String
có (ẩn) con trỏ đến bộ nhớ bên dưới và không thể được chuyển đi xung quanh bằng cách chỉ sao chép chính cấu trúc. Nó cũng sẽ không hoạt động với các kiểu tham chiếu chỉ là con trỏ đến bộ lưu trữ đối tượng thực.
Vì vậy, giải quyết vấn đề đó, người ta có thể
Xác định một giao thức xác định các phương thức chuyển đổi tới Data
và lui:
protocol DataConvertible {
init?(data: Data)
var data: Data { get }
}
Triển khai các chuyển đổi làm phương thức mặc định trong tiện ích mở rộng giao thức:
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) }
}
}
Tôi đã chọn một cái có sẵn khởi tạo có sẵn ở đây để kiểm tra xem số byte được cung cấp có khớp với kích thước của loại không.
Và cuối cùng tuyên bố sự phù hợp với tất cả các loại có thể được chuyển đổi qua Data
lại một cách an toàn :
extension Int : DataConvertible { }
extension Float : DataConvertible { }
extension Double : DataConvertible { }
// add more types here ...
Điều này làm cho việc chuyển đổi trở nên thanh lịch hơn:
let value = 42.13
let data = value.data
print(data as NSData) // <713d0ad7 a3104540>
if let roundtrip = Double(data: data) {
print(roundtrip) // 42.13
}
Ưu điểm của cách tiếp cận thứ hai là bạn không thể vô tình thực hiện các chuyển đổi không an toàn. Điểm bất lợi là bạn phải liệt kê tất cả các loại "an toàn" một cách rõ ràng.
Bạn cũng có thể triển khai giao thức cho các loại khác yêu cầu chuyển đổi không nhỏ, chẳng hạn như:
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)
}
}
hoặc triển khai các phương thức chuyển đổi trong các loại của riêng bạn để làm bất cứ điều gì cần thiết để tuần tự hóa và giải mã hóa một giá trị.
Byte đặt hàng
Không có chuyển đổi thứ tự byte nào được thực hiện trong các phương pháp trên, dữ liệu luôn theo thứ tự byte chủ. Đối với biểu diễn độc lập với nền tảng (ví dụ: thứ tự byte “big endian” hay còn gọi là “mạng”), hãy sử dụng các thuộc tính số nguyên tương ứng. khởi tạo. Ví dụ:
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
}
Tất nhiên việc chuyển đổi này cũng có thể được thực hiện nói chung, theo phương pháp chuyển đổi chung.