Swift Structs to Nsdata and Back

Swift structs to NSData and back

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
}
}

How to Covert struct with an Array of string to NSData and vice versa Swift

Based on this answer:
Swift structs to NSData and back
I wrote this solution:

struct MessageRandomWords {
let message = MessageType.kMessageTypeRandomWords
var data : NSData?
var name: String

struct ArchivedPacket {
let message = MessageType.kMessageTypeRandomWords
var dataLength : Int64
var nameLength : Int64
}

func archive() -> NSData {
var archivedPack = ArchivedPacket(dataLength: Int64(self.data!.length), nameLength: Int64(self.name.lengthOfBytesUsingEncoding(NSUTF8StringEncoding)))
var metaData = NSData(bytes: &archivedPack, length: sizeof(ArchivedPacket))
let archiveData = NSMutableData(data: metaData)
archiveData.appendData(name.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!)
archiveData.appendData(data!)
return archiveData
}

static func unarchive(data : NSData!) -> MessageRandomWords {
var archivedPacket = ArchivedPacket(dataLength: 0, nameLength: 0)
let archivedStructLength = sizeof(ArchivedPacket) //lenght of the struct

//Get the data tha will form our archived Packet
let archivedData = data.subdataWithRange(NSMakeRange(0, archivedStructLength))
//save the data taht form the archivedPacket inside the archivedPacket
archivedData.getBytes(&archivedPacket, length: archivedStructLength)
//get the range of data that contains the name
let nameRange = NSMakeRange(archivedStructLength, Int(archivedPacket.nameLength))
//get the range of the data that contains the data
let dataRange = NSMakeRange(archivedStructLength + Int(archivedPacket.nameLength), Int(archivedPacket.dataLength))
//get the data that rappresent the name
let nameData = data.subdataWithRange(nameRange)
//Get the name frome the data
let name = NSString(data: nameData, encoding: NSUTF8StringEncoding) as! String
// Geth the data
let theData = data.subdataWithRange(dataRange)

//Create the struct
let messageRndm = MessageRandomWords(data: theData, name: name)
return messageRndm
}

}

If you create the struct as showed, you can send your array of string by encode it as NSData and then decode it when received.

You can find the full working example on GitHub

If you have some better solutions please leave some feedback

Structs to NSData to Structs?

// make a NSData object
NSData *myData = [NSData dataWithBytes:&myPacketJoin length:sizeof(myPacketJoin)];

// make a new PacketJoin
PacketJoin newJoin;
[myData getBytes:&newJoin length:sizeof(newJoin)];

How to encode and decode struct to NSData in swift?

@Gwendal Roué : you are right, but I have to build another class according to each struct. I used the following method, it is ugly, but it works. Can you help me to improve it?

init(data: NSData) {
let dictionary = NSKeyedUnarchiver.unarchiveObjectWithData(data) as! NSDictionary
fid = (dictionary["fid"] as! NSNumber).integerValue
date = dictionary["date"] as! NSDate
forumName = dictionary["forumName"] as! String
typeid = (dictionary["typeid"] as! NSNumber).integerValue
page = (dictionary["page"] as! NSNumber).integerValue
totalPageNumber = (dictionary["totalPageNumber"] as? NSNumber)?.integerValue
let threadDataList = dictionary["threadDataList"] as! [NSData]
threadList = threadDataList.map { Thread(data: $0) }
}
extension ThreadManager {
func encode() -> NSData {
let dictionary = NSMutableDictionary()
dictionary.setObject(NSNumber(integer: fid), forKey: "fid")
dictionary.setObject(date, forKey: "date")
dictionary.setObject(forumName, forKey: "forumName")
dictionary.setObject(NSNumber(integer: typeid), forKey: "typeid")
dictionary.setObject(NSNumber(integer: page), forKey: "page")
if totalPageNumber != nil {
dictionary.setObject(NSNumber(integer: totalPageNumber!), forKey: "totalPageNumber")
}
let threadDataList: [NSData] = threadList.map { $0.encode() }
dictionary.setObject(threadDataList, forKey: "threadDataList")

return NSKeyedArchiver.archivedDataWithRootObject(dictionary)
}
}

Getting data out of NSData with Swift

You may want to check out RawData which is really new and this guy just experimented a bit with this idea, so don't think that it's tested well or anything, some function aren't even implemented yet. It's basically a Swift-y wrapper around (you guessed it) raw data, a series of bytes.

Using this extension, you can initialise it with an NSData instance:

extension RawData {
convenience init(data: NSData) {
self.init(UnsafeMutableBufferPointer(start: UnsafeMutablePointer(data.bytes), count: data.length))
}
}

You'de be calling it like this:

let data = "Hello, data!".dataUsingEncoding(NSASCIIStringEncoding)!

let rawData = RawData(data: data)

EDIT: To answer your questions:

The thing is that data can be large, very large. You generally don't want to copy large stuff, as space is valuable. The difference between an array of [UInt8] values and an NSData instance is that the array gets copied every time, you give it to a function -> new copy, you do an assignment -> new copy. That's not very desirable with large data.

1) If you want the most native, safe way, without any 3rd party libraries as the one mentioned, you can do this:

let data = UnsafeMutableBufferPointer(start: UnsafeMutablePointer(data.bytes), count: data.length)

(I know it doesn't sound very safe, but believe me it is). You can use this almost like an ordinary array:

let data = "Hello, data!".dataUsingEncoding(NSASCIIStringEncoding)!

let bytes = UnsafeMutableBufferPointer(start: UnsafeMutablePointer<UInt8>(data.bytes), count: data.length)

for byte in bytes {}
bytes.indexOf(0)
bytes.maxElement()

and it doesn't copy the data as you pass it along.

2) UnsafeMutablePointer<Void> is indeed very C-like, in this context it represents the starting value (also called base) in a sequence of pointers. The Void type comes from C as well, it means that the pointer doesn't know what kind of value it's storing. You can cast all kinds of pointers to the type you're expecting like this: UnsafeMutablePointer<Int>(yourVoidPointer) (This shouldn't crash). As mentioned before, you can use UnsafeMutableBufferPointer to use it as a collection of your type. UnsafeMutableBufferPointer is just a wrapper around your base pointer and the length (this explains the initialiser I used).

Your method of decoding the data directly into your struct does indeed work, the properties of a struct are in the right order, even after compile time, and the size of a struct is exactly the sum of it's stored properties. For simple data like yours that's totally fine. There is an alternative: To use the NSCoding protocol. Advantage: safer. Disadvantage: You have to subclass NSObject. I think you should be sticking to the way you do it now. One thing I would change though is to put the decoding of your struct inside the struct itself and use sizeof. Have it like this:

struct TreeDescription {
var id:UInt32 = 0x00000000
var channel:UInt8 = 0x00
var rssi:UInt8 = 0x00

init(data: NSData) {
data.getBytes(&self, length: sizeof(TreeDescription))
}
}

Another EDIT: You can always get the underlying data from an Unsafe(Mutable)Pointer<T> with the method memory whose return type is T. If you need to you can always shift pointers (to get the next value e.g.) by just adding/subtracting Ints to it.

EDIT answering your comment: You use the & to pass an inout variable, which can then be modified within the function. Because an inout variable is basically the same as passing the pointer, the Swift devs decided to make it possible to pass &value for an argument that is expecting an UnsafeMutablePointer. Demonstration:

func inoutArray(inout array: [Int]) {}

func pointerArray(array: UnsafeMutablePointer<Int>) {}

var array = [1, 2, 3]

inoutArray(&array)
pointerArray(&array)

This also works for structs (and maybe some other things)

Using Structs (includes byte Array) with SWIFT - Struct to NSData and NSData to Struct

var Message: [Byte] declares a variable of the type struct Array:

struct Array<T> : MutableCollectionType, Sliceable {

/// The type of element stored by this `Array`
typealias Element = T

/// Always zero, which is the index of the first element when non-empty.
var startIndex: Int { get }

/// A "past-the-end" element index; the successor of the last valid
/// subscript argument.
var endIndex: Int { get }
subscript (index: Int) -> T

// ... and much more ...
}

so this is not just a "C array" of bytes. The actual storage is opaque and only
accessible through methods and properties.

You can define a tuple of fixed size:

struct exampleStruct {
var ModelNumber: Byte
var MajorVersion: Byte
var MinorVersion: Byte
var Revision: Byte
var Message: (Byte, Byte, Byte, Byte, Byte)
}

var myStruct = exampleStruct (
ModelNumber: 1,
MajorVersion: 2,
MinorVersion: 3,
Revision: 4,
Message: (0x48, 0x45, 0x4C, 0x4C, 0x4F) // HELLO
)
var data = NSData(
bytes: &myStruct,
length: sizeof(exampleStruct)
)

println(data) // <01020304 48454c4c 4f>

However, I don't think that Swift makes any guarantees about the binary representation
of its structures, so this may break in the future.

Extract struct from NSData in Swift

Assuming you meant “decode it back from NSData”:

func match(match: GKMatch!, didReceiveData data: NSData!, fromRemotePlayer player: GKPlayer!) {
if data.length == sizeof(MovePacket) {
let packet = UnsafePointer<MovePacket>(data.bytes).memory
println(packet)
} else {
// error: data size is incorrect
}
}

How to convert NSData to struct accurately

How the compiler lays out the individual fields of a struct in memory is implementation dependent. Usually the compiler has to add padding to properly align the fields and it might even reorder them (by grouping fields of the same size) to reduce the required padding and the overall size of the struct.

You can turn this behavior off using __attribute__((packed)):

typedef struct __attribute__((packed)) {
UInt8 cmd;
UInt16 index;
UInt32 timeStamp;
UInt16 steps;// 步数
UInt16 calories;// 卡路里
UInt16 distance;// 距离,单位m
UInt16 sleep;// 睡眠
UInt16 duration;// 运动时长,单位minute
} D2MHistoryDataPort;


Related Topics



Leave a reply



Submit