query - string to data swift 4
ida y vuelta Tipos de números Swift a/desde Datos (3)
En mi caso, la respuesta de ayudó pero el resultado fue invertido. Entonces hice un pequeño cambio en su código:
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))
}
}
El problema está relacionado con LittleEndian y BigEndian.
Con Swift 3 inclinado hacia
Data
lugar de
[UInt8]
, estoy tratando de descubrir cuál es la forma más eficiente / idiomática de codificar / decodificar swift varios tipos de números (UInt8, Double, Float, Int64, etc.) como objetos de datos.
Existe esta respuesta para usar [UInt8] , pero parece estar usando varias API de puntero que no puedo encontrar en los datos.
Básicamente, me gustaría tener algunas extensiones personalizadas que se parecen a:
let input = 42.13 // implicit Double
let bytes = input.data
let roundtrip = bytes.to(Double) // --> 42.13
La parte que realmente me elude, he examinado un montón de documentos, es cómo puedo obtener algún tipo de puntero (OpaquePointer o BufferPointer o UnsafePointer?) Desde cualquier estructura básica (que son todos los números). En C, simplemente abofetearía un ampersand delante de él, y ahí lo tienes.
Puede obtener un puntero inseguro a objetos
mutables
usando
withUnsafePointer
:
withUnsafePointer(&input) { /* $0 is your pointer */ }
No conozco una forma de obtener uno para objetos inmutables, porque el operador inout solo funciona en objetos mutables.
Esto se demuestra en la respuesta a la que te has vinculado.
Nota: El código se ha actualizado para Swift 5 (Xcode 10.2) ahora. (Las versiones Swift 3 y Swift 4.2 se pueden encontrar en el historial de edición). También es posible que ahora los datos no alineados se manejen correctamente.
Cómo crear
Data
partir de un valor
A partir de Swift 4.2, los datos se pueden crear a partir de un valor simplemente con
let value = 42.13
let data = withUnsafeBytes(of: value) { Data($0) }
print(data as NSData) // <713d0ad7 a3104540>
Explicación:
-
withUnsafeBytes(of: value)
invoca el cierre con un puntero de búfer que cubre los bytes sin formato del valor. -
Un puntero de búfer sin procesar es una secuencia de bytes, por lo tanto, se pueden usar
Data($0)
para crear los datos.
Cómo recuperar un valor de
Data
A partir de Swift 5,
withUnsafeBytes(_:)
de
Data
invoca el cierre con un
UnsafeMutableRawBufferPointer
"sin tipo" a los bytes.
El método
load(fromByteOffset:as:)
lee el valor de la memoria:
let data = Data([0x71, 0x3d, 0x0a, 0xd7, 0xa3, 0x10, 0x45, 0x40])
let value = data.withUnsafeBytes {
$0.load(as: Double.self)
}
print(value) // 42.13
Hay un problema con este enfoque: requiere que la memoria esté
alineada
con la propiedad para el tipo (aquí: alineada con una dirección de 8 bytes).
Pero eso no está garantizado, por ejemplo, si los datos se obtuvieron como una porción de otro valor de
Data
.
Por lo tanto, es más seguro copiar los bytes al valor:
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
Explicación:
-
withUnsafeMutableBytes(of:_:)
invoca el cierre con un puntero de búfer mutable que cubre los bytes sin formato del valor. -
El
copyBytes(to:)
deDataProtocol
(al que se ajustan losData
) copia bytes de los datos a ese búfer.
El valor de retorno de
copyBytes()
es el número de bytes copiados.
Es igual al tamaño del búfer de destino, o menor si los datos no contienen suficientes bytes.
Solución genérica n. ° 1
Las conversiones anteriores ahora se pueden implementar fácilmente como métodos genéricos de
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
}
}
La restricción
T: ExpressibleByIntegerLiteral
se agrega aquí para que podamos inicializar fácilmente el valor a "cero"; eso no es realmente una restricción porque este método se puede usar con los tipos "trival" (entero y punto flotante) de todos modos, ver a continuación.
Ejemplo:
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")
}
Del mismo modo, puede convertir
matrices
a
Data
y viceversa:
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
}
}
Ejemplo:
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]
Solución genérica # 2
El enfoque anterior tiene una desventaja: en realidad solo funciona con tipos "triviales" como los enteros y los tipos de coma flotante.
Los tipos "complejos" como
Array
y
String
tienen punteros (ocultos) al almacenamiento subyacente y no se pueden pasar simplemente copiando la estructura misma.
Tampoco funcionaría con tipos de referencia que son solo punteros al almacenamiento de objetos reales.
Así que resuelve ese problema, uno puede
-
Defina un protocolo que defina los métodos para convertir a
Data
y viceversa:protocol DataConvertible { init?(data: Data) var data: Data { get } }
-
Implemente las conversiones como métodos predeterminados en una extensión de protocolo:
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) } } }
He elegido un inicializador que no está disponible aquí que verifica que el número de bytes proporcionados coincida con el tamaño del tipo.
-
Y finalmente declara conformidad con todos los tipos que se pueden convertir de forma segura a
Data
y viceversa:extension Int : DataConvertible { } extension Float : DataConvertible { } extension Double : DataConvertible { } // add more types here ...
Esto hace que la conversión sea aún más elegante:
let value = 42.13
let data = value.data
print(data as NSData) // <713d0ad7 a3104540>
if let roundtrip = Double(data: data) {
print(roundtrip) // 42.13
}
La ventaja del segundo enfoque es que no puede realizar conversiones inseguras sin darse cuenta. La desventaja es que debe enumerar todos los tipos "seguros" explícitamente.
También puede implementar el protocolo para otros tipos que requieren una conversión no trivial, como:
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)
}
}
o implemente los métodos de conversión en sus propios tipos para hacer lo que sea necesario para serializar y deserializar un valor.
Orden de bytes
No se realiza la conversión del orden de bytes en los métodos anteriores, los datos siempre están en el orden de bytes del host. Para una representación independiente de la plataforma (por ejemplo, "big endian", también conocido como orden de bytes "red"), use las propiedades de enteros correspondientes resp. inicializadores Por ejemplo:
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
}
Por supuesto, esta conversión también se puede hacer generalmente, en el método de conversión genérico.