Указатели, арифметика указателей и необработанные данные в Swift

Мое приложение использует несколько сложную неинтегрируемую структуру данных, которая закодирована в двоичном файле. Мне нужно иметь доступ к нему на уровне байта, избегая копирования. Обычно я должен использовать арифметику указателей C или С++ и приемы типов, чтобы получать и интерпретировать значения исходного байта. Я хотел бы сделать то же самое с Swift.

Я обнаружил, что следующие работы:

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

Однако я не уверен, насколько он эффективен. Делать data.subdataWithRange и NSMakeRange распределять объекты каждый раз, когда я их вызываю, или они просто синтаксический сахар для работы с указателями?

Есть ли лучший способ сделать это в Swift?

EDIT:

Я создал небольшой класс Objective-C, который просто инкапсулирует функцию для смещения указателя на заданное количество байтов:

@implementation RawDataOffsetPointer

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

@end

Если я включу этот класс в заголовок моста, то я могу изменить свой метод read на

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

который не будет копировать данные из моего буфера или выделять другие объекты.

Однако было бы неплохо сделать некоторую арифметику указателя из Swift, если это было возможно.

Ответы

Ответ 1

Если вы просто хотите сделать это напрямую, UnsafePointer<T> можно обрабатывать арифметически:

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

Вы также можете сделать указатель таким образом (UnsafePointer<()> эквивалентно void *)

   let castPointer = UnsafePointer<MyStruct>(oldPointer)

Ответ 2

Я бы рекомендовал посмотреть NSInputStream, который позволяет читать NSData как последовательность байтов (UInt8 в Swift).

Вот небольшой пример, который я собрал на игровой площадке:

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
}

Вы можете создать extension для чтения таких примитивных типов, как:

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

Ответ 3

Я бы порекомендовал простой способ использования UnsafeArray.

let data = NSData(contentsOfFile: filename)
let ptr = UnsafePointer<UInt8>(data.bytes)
let bytes = UnsafeBufferPointer<UInt8>(start:ptr, count:data.length)