Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift structs to NSData and back

Tags:

I have a struct containing a struct and an NSObject that I want to serialize into an NSData object:

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) 

How do I best serialize the Packet into an NSData, and how do I best deserialize it?

Using

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

of only gives me the pointers of name and data. I was exploring NSKeyedArchiver, but then I'd have to make Packet an object, and I'd prefer to keep it a struct.

Cheers

Nik

like image 273
niklassaers Avatar asked Mar 07 '15 15:03

niklassaers


People also ask

Can you inherit from a struct Swift?

Structs cannot have inheritance, so have only one type. If you point two variables at the same struct, they have their own independent copy of the data.

Can Swift structs have functions?

Function Inside Swift StructWe can also define a function inside a swift struct. A function defined inside a struct is called a method.

Are structs passed by value Swift?

All structures and enumerations are value types in Swift. This means that any structure and enumeration instances you create—and any value types they have as properties—are always copied when they're passed around in your code.

Can we use Swift struct in Objective C?

Swift enums and Objective-C IMPORTANT: Swift enums can't be used in Objective-C header files. But if your project must have Swift enums in header files, there are two possible solutions: Write Swift enum in Objective-C and use newly written enum in Swift files.


2 Answers

Not really getting any feedback, this is the solution I ended up with:

  1. Make encode() and decode() functions for my struct
  2. Change Int to Int64 so the Int has the same size on 32-bit and 64-bit platforms
  3. Have an intermediate struct (ArchivedPacket) that has no String or Data, but only Int64

Here is my code, I would be very grateful for your feedback, especially if there are less cumbersome ways to do this:

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     } } 
like image 151
niklassaers Avatar answered Sep 20 '22 19:09

niklassaers


Swift 5

If you are on Apple platforms, use Codable now. See documentation.

Swift 3

This is an unaltered copy-paste from a Playground in Xcode 8.2.1 that works. It is a bit simpler than other answers.

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

Notes

I couldn't make archive and unarchive instance methods because Data.init(bytes:​count:​) is mutating on the bytes parameter? And self isn't mutable, so... This made no sense to me.

The WhizzoKind enum is in there because that is something I care about. It's not important for the example. Someone might be paranoid about enums like I am.

I had to cobble this answer together from 4 other SO question/answers:

  • Getting data out of NSData with Swift
  • Extract struct from NSData in Swift
  • 'bytes' is unavailable: use withUnsafeBytes instead
  • Unsafe bytes in Swift 3

And these docs: - http://swiftdoc.org/v3.1/type/UnsafePointer/

And meditating on the Swift closure syntax until I wanted to scream.

So thanks to those other SO askers/authors.

Update

So this will not work across devices. For example, sending from iPhone 7 to Apple Watch. Because the stride is different. The above example is 80 bytes on iPhone 7 Simulator but 40 bytes on Apple Watch Series 2 Simulator.

It looks like the approach (but not syntax) by @niklassaers is still the only one that will work. I'm going to leave this answer here because it might help others with all the new Swift 3 syntax and API changes surrounding this topic.

Our only real hope is this Swift proposal: https://github.com/apple/swift-evolution/blob/master/proposals/0166-swift-archival-serialization.md

like image 36
Jeff Avatar answered Sep 20 '22 19:09

Jeff