it-swarm.com.ru

туда и обратно Swift типы номеров в / из данных

Поскольку Swift 3 склоняется к Data вместо [UInt8], я пытаюсь выяснить, какой самый эффективный/идиоматичный способ кодирования/декодирования стрелок различных типов чисел (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?) Из любой базовой структуры (которой являются все числа). В Си, я бы просто ударил амперсанд перед ним, и вы идете.

80
Travis Griggs

Примечание: Код был обновлен для 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, чтобы мы могли легко инициализировать значение как "ноль" - это на самом деле не является ограничением, поскольку этот метод в любом случае можно использовать с типами "trival" (целое и с плавающей запятой), см. Ниже.

Пример:

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) }
        }
    }
    

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

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

Конечно, это преобразование может также быть сделано вообще, в общем методе преобразования.

203
Martin R

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

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

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

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

3
zneak

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

extension UInt16 : DataConvertible {

    init?(data: Data) {
        guard data.count == MemoryLayout<UInt16>.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.

2
Beto Caldas