Примечание. Сейчас код обновлен для 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>
Объяснение:
withUnsafeBytes(of: value)
вызывает закрытие с указателем буфера, покрывающим необработанные байты значения.
- Необработанный указатель буфера представляет собой последовательность байтов, поэтому можно использовать
Data($0)
для создания данных.
Как получить значение из Data
Начиная с Swift 5, withUnsafeBytes(_:)
из 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
У этого подхода есть одна проблема: он требует, чтобы память была выровнена по свойству для типа (здесь: выровнена по 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
Объяснение:
withUnsafeMutableBytes(of:_:)
вызывает закрытие с изменяемым указателем буфера, покрывающим необработанные байты значения.
- Метод
copyBytes(to:)
метода DataProtocol
(которому соответствует Data
) копирует байты из данные в этот буфер.
Возвращаемое значение 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
, чтобы мы могли легко инициализировать значение «нулем» - это не совсем ограничение, потому что этот метод в любом случае можно использовать с типами «тривины» (целые числа и числа с плавающей запятой), см. Ниже.
Пример:
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
Вышеупомянутый подход имеет один недостаток: на самом деле он работает только с «тривиальными» типами, такими как целые числа и типы с плавающей запятой. «Сложные» типы, такие как 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
}
Преимущество второго подхода в том, что вы не можете случайно выполнить небезопасные преобразования. Недостатком является то, что вам нужно явно перечислять все «безопасные» типы.
Вы также можете реализовать протокол для других типов, которые требуют нетривиального преобразования, например:
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
}
Конечно, это преобразование также может быть выполнено в общем методе преобразования.
person
Martin R
schedule
25.06.2016