Swift construye NSData y vuelve

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 puedo serializar mejor el paquete en un NSData y cómo puedo deserializarlo mejor?

Utilizando

 var bufferData = NSData(bytes: & thePacket, length: sizeof(Packet)) 

de solo me da los indicadores de nombre y datos. Estaba explorando NSKeyedArchiver , pero luego tendría que convertir Packet en un objeto, y preferiría mantenerlo como una estructura.

Aclamaciones

Nik

Realmente no recibo ningún comentario, esta es la solución con la que terminé:

  1. Hacer funciones de encode() y decode() para mi estructura
  2. Cambie Int a Int64 para que el Int tenga el mismo tamaño en plataformas de 32 y 64 bits
  3. Tener una estructura intermedia (ArchivedPacket) que no tenga String o Data , pero solo Int64

Aquí está mi código, le estaría muy agradecido por sus comentarios, especialmente si hay formas menos engorrosas 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 } } 

Swift 3

Este es un copy-paste inalterado de un Playground 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.stride) } static func unarchive(d:Data) -> Whizzo { guard d.count == MemoryLayout.stride else { fatalError("BOOM!") } var w:Whizzo? d.withUnsafeBytes({(bytes: UnsafePointer)->Void in w = UnsafePointer(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 hacer archive y unarchive métodos de instancia porque Data.init(bytes:​count:​) está mutando en el parámetro bytes ? Y el self no es mutable, entonces … Esto no tenía sentido para mí.

El enum de WhizzoKind está ahí porque eso es algo que me importa. No es importante para el ejemplo. Alguien podría ser paranoico sobre enums como yo.

Tuve que improvisar esta respuesta a partir de otras 4 preguntas / respuestas SO:

  • Obteniendo datos de NSData con Swift
  • Extraer struct de NSData en Swift
  • ‘bytes’ no está disponible: use withUnsafeBytes en su lugar
  • Bytes inseguros en Swift 3

Y estos documentos: – http://swiftdoc.org/v3.1/type/UnsafePointer/

Y meditando sobre la syntax de cierre de Swift hasta que quise gritar.

Así que gracias a esos otros solicitantes / autores de SO.

Actualizar

Entonces esto no funcionará en todos los dispositivos . Por ejemplo, enviar desde iPhone 7 a Apple Watch. Porque el stride es diferente. El ejemplo anterior es de 80 bytes en el simulador de iPhone 7 pero de 40 bytes en el simulador de Apple Watch Series 2.

Parece que el enfoque (pero no la syntax) de @niklassaers sigue siendo el único que funcionará. Voy a dejar esta respuesta aquí porque podría ayudar a otros con toda la nueva syntax de Swift 3 y los cambios de API que rodean este tema.

Nuestra única esperanza real es esta propuesta de Swift: https://github.com/apple/swift-evolution/blob/master/proposals/0166-swift-archival-serialization.md

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.stride) } static func unarchive(d: Data) -> Series { guard d.count == MemoryLayout.stride else { fatalError("Error!") } var w: Series? d.withUnsafeBytes({(bytes: UnsafePointer) -> Void in w = UnsafePointer(bytes).pointee }) return w! } 

}

Como Dag mencionó, todo es un poco frágil. A veces, la aplicación falla cuando el nombre contiene un espacio en blanco o un subrayado / subrayado, y a veces se cuelga sin razón. En todos los casos, el nombre que se desarchiva es similar a este ‘4 \ 200a \ 256’. Sorprendentemente, esto no es un problema en caso de temporada o episodio (como en “Temporada 2”). Aquí el espacio en blanco no fuerza a la aplicación a bloquearse.

Tal vez sea una alternativa para codificar las cadenas para utf8 pero no estoy lo suficientemente familiarizado con los métodos de archivar / desarchivar para adoptarlas para este caso.

Parece que esto salió recientemente, y para mí se ve sólido. No lo intenté 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 buenos días de C, cuando tienes una estructura con un puntero, es una bandera que no puede serializar los bytes de la instancia de esa estructura sin seguir los punteros y obtener sus datos. Lo mismo aplica para 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 asignador, y hay una sobrecarga. Todos le dirán esto: “Mida primero”.

Ahora, aquí está la parte interesante:

Si realmente desea alinear sus datos en esa estructura, y transmitir los contenidos sin construir el paquete alrededor de NSData como lo hace, puede reservar bytes usando Swift Tuples , que funcionan de forma muy similar a cómo se reservan los 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 sobre esto, creo que es bastante horrible, pero posible. Puede escribir en la ubicación de la memoria de la tupla y leer de ella usando algo como esto .