Указатели, арифметика указателей и необработанные данные в 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)