Получение данных из 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. Если вам нужно, вы всегда можете сдвинуть указатели (чтобы получить следующее значение, например), просто добавив/вычитая Ints к нему.

изменить ответ на ваш комментарий: вы используете & передать 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, который ожидает эти небезопасные типы данных.