structs array swift serialization struct nsdata nskeyedarchiver

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:

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:

  1. Hacer las funciones 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 (paquete archivado) que no tiene cadena ni Data , pero solo Int64

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.