типы номеров Swift туда и обратно в / из данных
С Swift 3 наклоняясь к Data вместо [UInt8], Я пытаюсь выяснить, какой наиболее эффективный / идиоматический способ кодирования / декодирования swifts различных типов чисел (UInt8, Double, Float, Int64 и т. д.) В качестве объектов данных.
есть этот ответ для использования [UInt8], но, похоже, он использует различные API указателей, которые я не могу найти на данных.
Я хотел бы в основном некоторые пользовательские расширения, которые выглядят примерно так:
let input = 42.13 // implicit Double
let bytes = input.data
let roundtrip = bytes.to(Double) // --> 42.13
часть, которая действительно ускользает от меня, я просмотрел кучу документов, как я могу получить какую-то указательную вещь (OpaquePointer или BufferPointer или UnsafePointer?) из любой базовой структуры (которой являются все числа). В C, я бы просто ударил амперсанд перед ним, и там вы идете.
3 ответов:
как создать
DataС
struct Dataимеет инициализаторpublic init(bytes: UnsafeRawPointer, count: Int), которые могут быть использованы аналогичным образом, как и в различных ответах на вопрос как преобразовать double в массив байтов в swift? что вы связали с:
let input = 42.13 var value = input let data = withUnsafePointer(to: &value) { Data(bytes: UnsafePointer(), count: MemoryLayout.size(ofValue: input)) } print(data as NSData) // <713d0ad7 a3104540>как уже сказал @zneak, вы можете взять адрес только a переменная, поэтому переменная копия создается с помощью
var value = value. В более ранних версиях Swift вы могли бы добиться этого, сделав параметр функции сам по себе переменная, это больше не поддерживается.однако, проще использовать инициализатор
:public init<SourceType>(buffer: UnsafeBufferPointer<SourceType>)let input = 42.13 var value = input let data = Data(buffer: UnsafeBufferPointer(start: &value, count: 1)) print(data as NSData) // <713d0ad7 a3104540>обратите внимание, что общий заполнитель
SourceTypeавтоматически выводится из контекст.как получить значение из
Data
NSDataуbytesсвойство для получения доступа к базовому хранилищу.struct Dataесть универсальныйpublic func withUnsafeBytes<ResultType, ContentType>(_ body: @noescape (UnsafePointer<ContentType>) throws -> ResultType) rethrows -> ResultTypeвместо этого, который может быть использован такой:
let data = Data(bytes: [0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40]) let value = data.withUnsafeBytes { (ptr: UnsafePointer<Double>) -> Double in return ptr.pointee } print(value) // 42.13если
ContentTypeможно вывести из контекста, тогда его не нужно указывать в закрытии, так что это может быть упрощено доlet data = Data(bytes: [0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40]) let value: Double = data.withUnsafeBytes { .pointee } print(value) // 42.13общее решение #1
вышеуказанные преобразования теперь могут быть легко реализованы как общие методы
struct Data:extension Data { init<T>(from value: T) { var value = value self.init(buffer: UnsafeBufferPointer(start: &value, count: 1)) } func to<T>(type: T.Type) -> T { return self.withUnsafeBytes { .pointee } } }пример:
let input = 42.13 // implicit Double let data = Data(from: input) print(data as NSData) // <713d0ad7 a3104540> let roundtrip = data.to(type: Double.self) print(roundtrip) // 42.13аналогично, вы можете конвертировать массивы до
Dataи снова:extension Data { init<T>(fromArray values: [T]) { var values = values self.init(buffer: UnsafeBufferPointer(start: &values, count: values.count)) } func toArray<T>(type: T.Type) -> [T] { return self.withUnsafeBytes { [T](UnsafeBufferPointer(start: , count: self.count/MemoryLayout<T>.stride)) } } }пример:
let input: [Int16] = [1, Int16.max, Int16.min] let data = Data(fromArray: input) print(data as NSData) // <0100ff7f 0080> let roundtrip = data.toArray(type: Int16.self) print(roundtrip) // [1, 32767, -32768]общее решение #2
вышеуказанный подход имеет один недостаток: как в как преобразовать double в массив байтов в swift?, это на самом деле работает только с "простой" типы, такие как целые числа и с плавающей запятой. "Сложные" типы, такие как
ArrayиStringимеют (скрытые) указатели на базовое хранилище и не могут быть передавали, просто копируя сама структура. Он также не будет работать с ссылочные типы, которые являются просто указателями на реальное хранилище объектов.так что решить эту проблему можно
определите протокол, который определяет методы преобразования в
Dataи снова:protocol DataConvertible { init?(data: Data) var data: Data { get } }реализовать преобразования в качестве методов по умолчанию в расширении протокола:
extension DataConvertible { init?(data: Data) { guard data.count == MemoryLayout<Self>.size else { return nil } self = data.withUnsafeBytes { .pointee } } var data: Data { var value = self return Data(buffer: UnsafeBufferPointer(start: &value, count: 1)) } }я выбрал a failable инициализатор здесь проверяет, что количество предоставленных байтов соответствует размеру типа.
и, наконец, объявить соответствие всем типам, которые могут быть безопасно преобразованы в
Dataи снова:extension Int : DataConvertible { } extension Float : DataConvertible { } extension Double : DataConvertible { } // add more types here ...это делает преобразование еще более элегантным:
let input = 42.13 let data = input.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 self.data(using: .utf8)! } }или реализовать методы преобразования в ваших собственных типов, чтобы сделать все, что есть надо так сериализовать и десериализовать значение.
Вы можете получить небезопасный указатель на mutable объекты с помощью
withUnsafePointer:withUnsafePointer(&input) { /* is your pointer */ }Я не знаю способа получить его для неизменяемых объектов, потому что оператор inout работает только с изменяемыми объектами.
это показано в ответе, который вы связали.
в моем случае, Мартин Рответ помог, но результат был перевернут. Поэтому я сделал небольшое изменение в его коде:
extension UInt16 : DataConvertible { init?(data: Data) { guard data.count == MemoryLayout<UInt16>.size else { return nil } self = data.withUnsafeBytes { .pointee } } var data: Data { var value = CFSwapInt16HostToBig(self)//Acho que o padrao do IOS 'e LittleEndian, pois os bytes estavao ao contrario return Data(buffer: UnsafeBufferPointer(start: &value, count: 1)) } }проблема связана с LittleEndian и BigEndian.
Comments