Swift structs to NSData and back
Not really getting any feedback, this is the solution I ended up with:
- Make
encode()
anddecode()
functions for my struct - Change
Int
toInt64
so theInt
has the same size on 32-bit and 64-bit platforms - Have an intermediate struct (ArchivedPacket) that has no String or
Data
, but onlyInt64
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 Int
s 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
How to Set Layer Cornerradius For Only Bottom-Left, Bottom-Right, and Top-Left Corner
Getting Keyboard Size from Userinfo in Swift
How to Compare Two Dictionaries in Swift
How to Create Array of Unique Object List in Swift
Nsuserdefaults Not Working on Xcode Beta With Watch Os2
Binary Operator * Cannot Be Applied to Operands of Type Int and Double
Check Password String Strength Criteria in Swift
Choosing Coredata Entities from Form Picker
Swift: How to Use Preprocessor Flags (Like '#If Debug') to Implement API Keys
What Is _: in Swift Telling Me
How to Lock Orientation of One View Controller to Portrait Mode Only in Swift
How to Get the Current Date in Short Format in Swift
#Ifdef Replacement in the Swift Language
Examples of Delegates in Swift
How to Use String.Substringwithrange? (Or, How Do Ranges Work in Swift)
Deinit Method Is Never Called - Swift Playground
How to Save and Read Array of Array in Nsuserdefaults in Swift