20

My application uses a somewhat complex inmutable data structure that is encoded in a binary file. I need to have access to it at the byte level, avoiding any copying. Normally, I would use C or C++ pointer arithmetic and typecasts, to access and interpret the raw byte values. I would like to do the same with Swift.

I have found that the following works:

class RawData {
    var data: NSData!

    init(rawData: NSData) {
      data = rawData
    }

    func read<T>(byteLocation: Int) -> T {
      let bytes = data.subdataWithRange(NSMakeRange(byteLocation, sizeof(T))).bytes
      return UnsafePointer<T>(bytes).memory
    }

    func example_ReadAnIntAtByteLocation5() -> Int {
      return read(5) as Int
    }
}

However, I am not sure how efficient it is. Do data.subdataWithRange and NSMakeRange allocate objects every time I call them, or are they just syntactic sugar for dealing with pointers?

Is there a better way to do this in Swift?

EDIT:

I have created a small Objective-C class that just encapsulates a function to offset a pointer by a given number of bytes:

@implementation RawDataOffsetPointer

inline void* offsetPointer(void* ptr, int bytes){
    return (char*)ptr + bytes;
}

@end

If I include this class in the bridging header, then I can change my read method to

func read<T>(byteLocation: Int) -> T {
  let ptr = offsetPointer(data.bytes, CInt(byteLocation))
  return UnsafePointer<T>(ptr).memory
}

which will not copy data from my buffer, or allocate other objects.

However, it would still be nice to do some pointer arithmetic from Swift, if it were possible.

Eduardo
  • 8,362
  • 6
  • 38
  • 72
  • My gut says that if the subdata is immutable, swift wouldn't bother to copy it. – Fernando Mazzon Jun 05 '14 at 18:54
  • 1
    Just on this point- is there a way in Swift to read a stream of `UInt8`s from an NSData in a completely safe way? Or from a file even? I'm using this NSData/subdata dance in a binary file parser dingus I'm writing now, I really hate looking at that "unsafe" nonsense but it seems like the only way I can get the raw bytes out of the file. – iluvcapra Jun 06 '14 at 04:18

3 Answers3

17

If you just want to do it directly, UnsafePointer<T> can be manipulated arithmetically:

   let oldPointer = UnsafePointer<()>
   let newPointer = oldPointer + 10

You can also cast a pointer like so (UnsafePointer<()> is equivalent to void *)

   let castPointer = UnsafePointer<MyStruct>(oldPointer)
russbishop
  • 16,587
  • 7
  • 61
  • 74
  • `let oldPointer = UnsafePointer<()>` gives me an error `'>' is not a postfix unary operator`. Am I missing something? – Robert Aug 19 '16 at 08:57
15

I would recommend looking into NSInputStream, which allows you to read NSData as a series of bytes (UInt8 in Swift).

Here is a little sample I put together in the playground:

func generateRandomData(count:Int) -> NSData
{
    var array = Array<UInt8>(count: count, repeatedValue: 0)

    arc4random_buf(&array, UInt(count))
    return NSData(bytes: array, length: count)
}

let randomData = generateRandomData(256 * 1024)

let stream = NSInputStream(data: randomData)
stream.open() // IMPORTANT

var readBuffer = Array<UInt8>(count: 16 * 1024, repeatedValue: 0)

var totalBytesRead = 0

while (totalBytesRead < randomData.length)
{
    let numberOfBytesRead = stream.read(&readBuffer, maxLength: readBuffer.count)

    // Do something with the data

    totalBytesRead += numberOfBytesRead
}

You can create an extension to read primitive types like so:

extension NSInputStream
{
    func readInt32() -> Int
    {
        var readBuffer = Array<UInt8>(count:sizeof(Int32), repeatedValue: 0)

        var numberOfBytesRead = self.read(&readBuffer, maxLength: readBuffer.count)

        return Int(readBuffer[0]) << 24 |
            Int(readBuffer[1]) << 16 |
            Int(readBuffer[2]) << 8 |
            Int(readBuffer[3])
    }
}
Erik
  • 12,730
  • 5
  • 36
  • 42
11

I would recommend the simple way to use UnsafeArray.

let data = NSData(contentsOfFile: filename)
let ptr = UnsafePointer<UInt8>(data.bytes)
let bytes = UnsafeBufferPointer<UInt8>(start:ptr, count:data.length)
Jeef
  • 26,861
  • 21
  • 78
  • 156
Shintaro Abe
  • 163
  • 6
  • When I try this I get a EXC_BAD_ACCESS when it gets to the bytes line. – Brian Jun 15 '14 at 04:00
  • With the current Swift version, the last line should be `let bytes = UnsafeBufferPointer(start:ptr, count:data.length)` – Martin R Jan 29 '15 at 17:50