array - Swift structs a NSData y de vuelta
structs swift (4)
Swift 3
Esto es una copia-pegada inalterada de un Patio de Recreo en Xcode 8.2.1 que funciona. Es un poco más simple que otras respuestas.
import Foundation
enum WhizzoKind {
case floom
case bzzz
}
struct Whizzo {
let name: String
let num: Int
let kind:WhizzoKind
static func archive(w:Whizzo) -> Data {
var fw = w
return Data(bytes: &fw, count: MemoryLayout<Whizzo>.stride)
}
static func unarchive(d:Data) -> Whizzo {
guard d.count == MemoryLayout<Whizzo>.stride else {
fatalError("BOOM!")
}
var w:Whizzo?
d.withUnsafeBytes({(bytes: UnsafePointer<Whizzo>)->Void in
w = UnsafePointer<Whizzo>(bytes).pointee
})
return w!
}
}
let thing = Whizzo(name:"Bob", num:77, kind:.bzzz)
print("thing = /(thing)")
let dataThing = Whizzo.archive(w: thing)
let convertedThing = Whizzo.unarchive(d: dataThing)
print("convertedThing = /(convertedThing)")
Notas
No pude crear métodos de instancia de archive
y unarchive
porque Data.init(bytes:count:)
está mutando en el parámetro bytes
. Y el self
no es mutable, así que ... Esto no tenía sentido para mí.
La enumeración de WhizzoKind
está ahí porque eso es algo que me importa. No es importante para el ejemplo. Alguien podría estar paranoico con los enums como yo.
Tuve que improvisar esta respuesta de otras 4 preguntas / respuestas de SO:
- Obtención de datos de NSData con Swift
- Extraer la estructura de NSData en Swift
- ''bytes'' no está disponible: use con UsnsBytes en su lugar
- Bytes inseguros en Swift 3
Y estos documentos: - http://swiftdoc.org/v3.1/type/UnsafePointer/
Y meditando en la sintaxis de cierre de Swift hasta que quise gritar.
Así que gracias a esos otros preguntantes / autores.
Actualizar
Así que esto no funcionará en todos los dispositivos . Por ejemplo, enviando desde iPhone 7 a Apple Watch. Porque la stride
es diferente. El ejemplo anterior es de 80 bytes en iPhone 7 Simulator pero 40 bytes en Apple Watch Series 2 Simulator.
Parece que el enfoque (pero no la sintaxis) de @niklassaers sigue siendo el único que funcionará. Voy a dejar esta respuesta aquí porque podría ayudar a otros con toda la nueva sintaxis Swift 3 y los cambios en la API que rodean este tema.
Nuestra única esperanza real es esta propuesta Swift: https://github.com/apple/swift-evolution/blob/master/proposals/0166-swift-archival-serialization.md
Tengo una estructura que contiene una estructura y un NSObject
que quiero serializar en un objeto NSData
:
struct Packet {
var name: String
var index: Int
var numberOfPackets: Int
var data: NSData
}
var thePacket = Packet(name: name, index: i, numberOfPackets: numberOfPackets, data: packetData)
¿Cómo mejor serializo el paquete en un NSData
, y cómo lo NSData
?
Utilizando
var bufferData = NSData(bytes: & thePacket, length: sizeof(Packet))
De solo me da los punteros de nombre y datos. Estaba explorando NSKeyedArchiver
, pero luego tendría que hacer de Packet un objeto, y preferiría mantenerlo como una estructura.
Aclamaciones
Nik
Parece que esto salió recientemente, y para mí se ve sólido. No lo probé todavía ...
https://github.com/a2/MessagePack.swift
Bueno, Swift no tiene ningún método de serialización mágico, si eso es lo que buscas. Desde los días buenos de C, cuando tiene una estructura con un puntero, es un indicador que no puede serializar los bytes de la instancia de esa estructura sin seguir los punteros y recuperar sus datos. Lo mismo se aplica a Swift.
Dependiendo de sus necesidades y restricciones de serialización, yo diría que usar NSCoding
o incluso cadenas JSON ordenará su código y lo hará más predecible que el estado actual. Claro, tendrás que escribir un mapeador, y hay una sobrecarga. Todos te dirán esto: "Mide primero".
Ahora, aquí está la parte interesante:
Si realmente desea alinear sus datos en esa estructura, y transmitir los contenidos sin crear el paquete alrededor de NSData
mientras lo hace, puede reservar bytes con Swift Tuples
, que funcionan de manera muy parecida a cómo reservaría bytes en C usando char[CONST]
:
struct what {
var x = 3
}
sizeof(what)
$R0: Int = 8
struct the {
var y = (3, 4, 5, 7, 8, 9, 33)
}
sizeof(the)
$R1: Int = 56
Para ampliar un poco esto, creo que es bastante horrible, pero posible. Puedes escribir en la ubicación de la memoria de la tupla y leer desde ella usando algo como esto .
Realmente no obteniendo ningún comentario, esta es la solución que terminé con:
- Hacer las funciones
encode()
ydecode()
para mi estructura - Cambie
Int
aInt64
para que elInt
tenga el mismo tamaño en plataformas de 32 y 64 bits. - Tener una estructura intermedia (paquete archivado) que no tiene cadena ni
Data
, pero soloInt64
Aquí está mi código, estaría muy agradecido por sus comentarios, especialmente si hay formas menos complicadas de hacer esto:
public struct Packet {
var name: String
var index: Int64
var numberOfPackets: Int64
var data: NSData
struct ArchivedPacket {
var index : Int64
var numberOfPackets : Int64
var nameLength : Int64
var dataLength : Int64
}
func archive() -> NSData {
var archivedPacket = ArchivedPacket(index: Int64(self.index), numberOfPackets: Int64(self.numberOfPackets), nameLength: Int64(self.name.lengthOfBytesUsingEncoding(NSUTF8StringEncoding)), dataLength: Int64(self.data.length))
var metadata = NSData(
bytes: &archivedPacket,
length: sizeof(ArchivedPacket)
)
let archivedData = NSMutableData(data: metadata)
archivedData.appendData(name.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!)
archivedData.appendData(data)
return archivedData
}
func unarchive(data: NSData!) -> Packet {
var archivedPacket = ArchivedPacket(index: 0, numberOfPackets: 0, nameLength: 0, dataLength: 0)
let archivedStructLength = sizeof(ArchivedPacket)
let archivedData = data.subdataWithRange(NSMakeRange(0, archivedStructLength))
archivedData.getBytes(&archivedPacket)
let nameRange = NSMakeRange(archivedStructLength, Int(archivedPacket.nameLength))
let dataRange = NSMakeRange(archivedStructLength + Int(archivedPacket.nameLength), Int(archivedPacket.dataLength))
let nameData = data.subdataWithRange(nameRange)
let name = NSString(data: nameData, encoding: NSUTF8StringEncoding) as! String
let theData = data.subdataWithRange(dataRange)
let packet = Packet(name: name, index: archivedPacket.index, numberOfPackets: archivedPacket.numberOfPackets, data: theData)
return packet
}
}
Usé el ejemplo de Jeff para crear la siguiente estructura:
struct Series {
var name: String?
var season: String?
var episode: String?
init(name: String?, season: String?, episode: String?) {
self.name = name
self.season = season
self.episode = episode
}
static func archive(w: Series) -> Data {
var fw = w
return Data(bytes: &fw, count: MemoryLayout<Series>.stride)
}
static func unarchive(d: Data) -> Series {
guard d.count == MemoryLayout<Series>.stride else {
fatalError("Error!")
}
var w: Series?
d.withUnsafeBytes({(bytes: UnsafePointer<Series>) -> Void in
w = UnsafePointer<Series>(bytes).pointee
})
return w!
}
}
Como Dag mencionó, todo es un poco frágil. A veces, la aplicación se bloquea cuando el nombre contiene un espacio en blanco o un subrayado / subrayado, y otras veces falla sin razón. En todos los casos, el nombre que no está archivado es similar a este ''4 / 200a / 256''. Sorprendentemente, esto no es un problema en caso de temporada o episodio (como en "Season 2"). Aquí el espacio en blanco no hace que la aplicación se bloquee.
Tal vez sea una alternativa para codificar las cadenas a utf8, pero no estoy lo suficientemente familiarizado con los métodos de archivar / desarchivar para adoptarlos en este caso.