Получение данных из NSData с помощью Swift
Я нахожу Swift
и NSData
быть нечестивым браком разочарования. Я нахожу, что чувствую, что вся предполагаемая новая найденная быстрая безопасность выходит из окна каждый раз, когда я имею дело с этой вещью. Количество сбоев (с бесполезными следами) не помогает.
Итак, я узнал, что могу избежать страшного UnsafeMutablePointer
вещи, делая такие вещи, как следующее:
var bytes = [UInt8](count: 15, repeatedValue: 0)
anNSData.getBytes(&bytes, length=15)
Я также обнаружил, что я могу извлечь прямо на единственном значения:
var u32:UInt32 = 0
anNSData.getBytes(&u32, length=4)
это приводит к двум промежуточным вопросам:
1) Есть ли что-то, что я могу использовать, что более надежно, чем жестко закодированные константы. Если бы это был C, я бы просто использовал sizeof
. Но я думаю, что я читал, что, возможно, я должен использовать strideof
вместо sizeof
? И это не сработает на [UInt8]
' s, не так ли?
2) документы (для Swift) говорят, что этот параметр должен быть _ buffer: UnsafeMutablePointer<Void>
. Так как же это работает? Мне просто везет? Почему? хотел бы я сделать это вместо более родной/управляемой конструкции [Uint8]?? Мне было интересно, если UnsafeMutablePointer
был протокол, но это структура.
ободренный чтением значений напрямую (а не как массив), я подумал, что, возможно, я мог бы попробовать другой вид структуры. У меня есть 6-байтовая структура, которая выглядит так:
struct TreeDescription : Hashable {
var id:UInt32 = 0x00000000
var channel:UInt8 = 0x00
var rssi:UInt8 = 0x00
var hashValue:Int {
return Int(self.id)
}
}
который на самом деле работает (подумав, что это не так, но в конечном итоге делает чистоту, которая заставила некоторые сбои уйти)!
var tree = TreeDescription()
anNSData.getBytes(&newTree, length: 6)
но это приводит меня к беспокойству о деталях упаковки структуры? Почему это работает? О чем мне беспокоиться?
все это кажется мне очень C-ish. Я думал, что Свифт взял C из ObjectiveC.
2 ответов
вы можете ознакомиться сырых данных что действительно ново, и этот парень просто немного экспериментировал с этой идеей, поэтому не думайте, что она хорошо протестирована или что-то еще, некоторые функции еще даже не реализованы. Это в основном оболочка Swift-y вокруг (вы догадались) необработанных данных, серия байтов.
используя это расширение, вы можете инициализировать его с помощью NSData
например:
extension RawData {
convenience init(data: NSData) {
self.init(UnsafeMutableBufferPointer(start: UnsafeMutablePointer(data.bytes), count: data.length))
}
}
вы будете называть это как это:
let data = "Hello, data!".dataUsingEncoding(NSASCIIStringEncoding)!
let rawData = RawData(data: data)
EDIT: чтобы ответить на ваши вопросы:
дело в том, что данные могут быть большие, очень большие. Вы обычно не хотите копировать большие вещи, так как пространство ценно. Разница между массивом [UInt8]
значения и NSData
экземпляр заключается в том, что массив копируется каждый раз, вы даете его функции -> новая копия, вы делаете назначение -> новая копия. Это не очень желательно с большими данными.
1) Если вы хотите самый родной, безопасный способ, без каких-либо сторонних библиотек, как упоминалось, вы можете сделать это:
let data = UnsafeMutableBufferPointer(start: UnsafeMutablePointer(data.bytes), count: data.length)
(я знаю, это звучит не очень безопасно, но поверьте это так). Вы можете использовать это почти как обычный массив:
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()
и он не копирует данные, когда вы передаете их.
2) UnsafeMutablePointer<Void>
действительно очень похож на C, в этом контексте он представляет начальное значение (также называемое base) в последовательности указателей. The Void
тип приходит от C также, это означает, что указатель не знает, какое значение он хранит. Вы можете бросить все виды указателей на тип, который вы ожидаете, как это:UnsafeMutablePointer<Int>(yourVoidPointer)
(это не катастрофа). Как упоминалось ранее, вы можете использовать UnsafeMutableBufferPointer
использовать его как коллекцию вашего типа. UnsafeMutableBufferPointer
- это просто обертка вокруг вашего базового указателя и длины (это объясняет инициализатор, который я использовал).
ваш метод декодирования данных непосредственно в вашу структуру действительно работает, свойства структуры находятся в правильном порядок, даже после времени компиляции, и размер структуры-это именно сумма ее сохраненных свойств. Для таких простых данных, как ваша, это совершенно нормально. Существует альтернатива: использовать NSCoding
протокол. Преимущество: безопаснее. Недостаток: вы должны подкласс NSObject. Я думаю, тебе следует придерживаться того, как ты делаешь это сейчас. Одна вещь, которую я бы изменил, - это поместить декодирование вашей структуры внутри самой структуры и использовать sizeof
. Пусть будет так:
struct TreeDescription {
var id:UInt32 = 0x00000000
var channel:UInt8 = 0x00
var rssi:UInt8 = 0x00
init(data: NSData) {
data.getBytes(&self, length: sizeof(TreeDescription))
}
}
другой EDIT: вы всегда можете получить базовые данные из Unsafe(Mutable)Pointer<T>
методом memory
чей тип возврата составляет T
. Если вам нужно, вы всегда можете сдвинуть указатели (чтобы получить следующее значение, например), просто добавив/вычитая Int
s к нему.
изменить ответ на ваш комментарий: вы используете &
передать inout
переменная, которая затем может быть изменена в пределах функции. Потому что inout
переменная в основном такая же, как передача указателя, разработчики Swift решили сделать это возможно пройти &value
для аргумента, который ожидает UnsafeMutablePointer
. Демонстрация:
func inoutArray(inout array: [Int]) {}
func pointerArray(array: UnsafeMutablePointer<Int>) {}
var array = [1, 2, 3]
inoutArray(&array)
pointerArray(&array)
это также работает для structs
(и, возможно, некоторые другие вещи)
вот как вы это делаете.
UInt8* unsafePointer = (UInt8*)data.bytes;
небезопасные указатели-это вещь C. Вы не должны нуждаться в них, если вы не взаимодействуете с кодом Obj-C, поэтому просто сделайте это прямо перед вызовом метода Obj-C, который ожидает эти небезопасные типы данных.