круглые поездки Типы данных Swift в / из данных

С Swift 3, склоняющимся к Data вместо [UInt8] , я пытаюсь выяснить, какой самый эффективный / идиоматический способ кодирования / декодирования swifts различных типов (UInt8, Double, Float, Int64 и т. Д.) В качестве объектов данных.

Вот этот ответ для использования [UInt8] , но, похоже, он использует различные API-интерфейсы указателей, которые я не могу найти в Data.

Я бы хотел, в основном, некоторые пользовательские расширения, которые выглядят примерно так:

 let input = 42.13 // implicit Double let bytes = input.data let roundtrip = bytes.to(Double) // --> 42.13 

Часть, которая действительно ускользает от меня, я просмотрел кучу документов, так это то, как я могу получить какую-то вещь-указатель (OpaquePointer или BufferPointer или UnsafePointer?) Из любой базовой структуры (которой все номера). В С, я бы просто ударил амперсанда перед ним, и я пойду.

Как создать 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($0), count: MemoryLayout.size(ofValue: input)) } print(data as NSData) // <713d0ad7 a3104540> 

Как уже сказал @zneak, вы можете взять адрес только переменной , поэтому переменная копия создается с помощью var value = value . В более ранних версиях Swift вы могли бы достичь этого, сделав параметр функции самой переменной, это больше не поддерживается.

Однако проще использовать инициализатор

 public init(buffer: UnsafeBufferPointer) 

вместо:

 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(_ body: @noescape (UnsafePointer) throws -> ResultType) rethrows -> ResultType 

вместо этого, который можно использовать следующим образом:

 let data = Data(bytes: [0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40]) let value = data.withUnsafeBytes { (ptr: UnsafePointer) -> 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 { $0.pointee } print(value) // 42.13 

Общее решение №1

Вышеперечисленные преобразования теперь могут быть легко реализованы как общие методы struct Data :

 extension Data { init(from value: T) { var value = value self.init(buffer: UnsafeBufferPointer(start: &value, count: 1)) } func to(type: T.Type) -> T { return self.withUnsafeBytes { $0.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(fromArray values: [T]) { var values = values self.init(buffer: UnsafeBufferPointer(start: &values, count: values.count)) } func toArray(type: T.Type) -> [T] { return self.withUnsafeBytes { [T](UnsafeBufferPointer(start: $0, count: self.count/MemoryLayout.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.size else { return nil } self = data.withUnsafeBytes { $0.pointee } } var data: Data { var value = self return Data(buffer: UnsafeBufferPointer(start: &value, count: 1)) } } 

    Я выбрал здесь неудачный инициализатор, который проверяет, соответствует ли количество байтов размеру этого типа.

  • И, наконец, объявить соответствие всем типам, которые можно безопасно преобразовать в 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)! } } 

или реализовать методы преобразования в ваших собственных типах, чтобы делать все необходимое, чтобы сериализовать и десериализовать значение.

Вы можете получить небезопасный указатель на изменяемые объекты, используя withUnsafePointer :

 withUnsafePointer(&input) { /* $0 is your pointer */ } 

Я не знаю способа получить один для неизменяемых объектов, потому что оператор inout работает только с изменяемыми объектами.

Это продемонстрировано в ответе, с которым вы связались.

В моем случае ответ Мартина R помог, но результат был инвертирован. Поэтому я сделал небольшое изменение в его коде:

 extension UInt16 : DataConvertible { init?(data: Data) { guard data.count == MemoryLayout.size else { return nil } self = data.withUnsafeBytes { $0.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.

  • Как преобразовать шестнадцатеричный номер в корзину в Swift?
  • В чем разница между использованием ARAnchor для вставки узла и непосредственной вставкой узла?
  • Запретить увольнение UIAlertController
  • Словарь неправильного порядка - JSON
  • Совместное использование данных между расширением общего доступа iOS 8 и основным приложением
  • Как создать локальные области в Swift?
  • dyld: библиотека не загружена: @ rpath / libswift_stdlib_core.dylib
  • Несколько функций с тем же именем
  • Почему я не могу создать пустой массив вложенного classа?
  • Правильно Разбор JSON в Swift 3
  • Как вы используете соответствующий class цветов для текущей платформы?
  • Давайте будем гением компьютера.