Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Binary file IO in Swift

Tags:

ios

swift

EDIT: TLDR In C family languages you can represent arbitrary data ( ints, floats, doubles, structs ) as byte streams via casting and pack them into streams or buffers. And you can do the reverse to get data back out. And of course you can byte swap for endianness correctness.

Is this possible in idiomatic swift?

Now the original question:

If I were writing in C/C++/ObjC I might cast a struct to unsigned char * and write its bytes to a FILE*, or memcpy them to a buffer. Same for ints, doubles, etc. I know there are endianness concerns to deal with, but this is for an iOS app, and I don't expect endianness to change any time soon for the platform. Swift's type system doesn't seem like it would allow this behavior ( casting arbitrary data to unsigned 8 bit ints and passing the address ), but I don't know.

I'm learning Swift, and would like an idiomatic way to write my data. Note that my highly numeric, and ultimately going to be sent over the wire so it needs to be compact, so textual formats like JSON are out.

I could use NSKeyedArchiver but I want to learn here. Also I don't want to write off an android client at some point in the future so a simple binary coding seems where it's at.

Any suggestions?

like image 487
TomorrowPlusX Avatar asked Aug 08 '14 17:08

TomorrowPlusX


Video Answer


2 Answers

As noted in Using Swift with Cocoa and Objective-C, you can pass/assign an array of a Swift type to a parameter/variable of pointer type, and vice versa, to get a binary representation. This even works if you define your own struct types, much like in C.

Here's an example -- I use code like this for packaging up 3D vertex data for the GPU (with SceneKit, OpenGL, etc.):

struct Float3 {
    var x, y, z: GLfloat
}
struct Vertex {
    var position, normal: Float3
}

var vertices: [Vertex] // initialization omitted for brevity
let data = NSData(bytes: vertices, length: vertices.count * sizeof(Vertex))

Inspect this data and you'll see a pattern of 32 * 3 * 2 bits of IEEE 754 floating-point numbers (just like you'd get from serializing a C struct through pointer access).

For going the other direction, you might sometimes need unsafeBitCast.

If you're using this for data persistence, be sure to check or enforce endianness.

like image 137
rickster Avatar answered Sep 21 '22 00:09

rickster


The kind of format you're discussing is well explored with MessagePack. There are a few early attempts at doing this in Swift:

  • https://github.com/briandw/SwiftPack
  • https://github.com/yageek/SwiftMsgPack

I'd probably start with the yageek version. In particular, look at how packing is done into [Byte] data structures. I'd say this is pretty idiomatic Swift, without losing endian management (which you shouldn't ignore; chips do change, and the numeric types give you via bigEndian):

extension Int32 : MsgPackMarshable{
  public func msgpack_marshal() -> Array<Byte>{

        let bigEndian: UInt32 = UInt32(self.bigEndian)
        return [0xce, Byte((bigEndian & 0xFF000000) >> 24), Byte((bigEndian & 0xFF0000) >> 16), Byte((bigEndian & 0xFF00) >> 8), Byte(bigEndian & 0x00FF)]
    }
}

This is also fairly similar to how you'd write it in C or C++ if you were managing byte order (which C and C++ should always do, so the fact that they could splat their bytes into memory doesn't make correct implementations trivial). I'd probably drop Byte (which comes from Foundation) and use UInt8 (which is defined in core Swift). But either is fine. And of course it's more idiomatic to say [UInt8] rather than Array<UInt8>.

That said, as Zaph notes, NSKeyedArchiver is idiomatic for Swift. But that doesn't mean MessagePack isn't a good format for this kind of problem, and it's very portable.

like image 34
Rob Napier Avatar answered Sep 20 '22 00:09

Rob Napier