Быстрые структуры в NSData и обратно

У меня есть структура, содержащая struct и NSObject, которые я хочу сериализовать в объект NSData:

struct Packet {
  var name: String
  var index: Int
  var numberOfPackets: Int
  var data: NSData
}

var thePacket = Packet(name: name, index: i, numberOfPackets: numberOfPackets, data: packetData)

Как лучше всего сериализовать пакет в NSData и как его лучше неэтериализировать?

Использование

var bufferData = NSData(bytes: & thePacket, length: sizeof(Packet))

только дает мне указатели на имя и данные. Я изучал NSKeyedArchiver, но тогда мне пришлось бы сделать Packet объектом, и я предпочел бы сохранить его структурой.

Приветствия

Ник

Ответы

Ответ 1

Не получив обратной связи, это решение, с которым я столкнулся:

  • Сделать функции encode() и decode() для моей структуры
  • Измените Int на Int64, чтобы Int имел одинаковый размер на 32-битных и 64-битных платформах.
  • Имейте промежуточную структуру (ArchivedPacket), у которой нет String или Data, но только Int64

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

public struct Packet {
    var name: String
    var index: Int64
    var numberOfPackets: Int64
    var data: NSData

    struct ArchivedPacket {
        var index : Int64
        var numberOfPackets : Int64
        var nameLength : Int64
        var dataLength : Int64
    }

    func archive() -> NSData {

        var archivedPacket = ArchivedPacket(index: Int64(self.index), numberOfPackets: Int64(self.numberOfPackets), nameLength: Int64(self.name.lengthOfBytesUsingEncoding(NSUTF8StringEncoding)), dataLength: Int64(self.data.length))

        var metadata = NSData(
            bytes: &archivedPacket,
            length: sizeof(ArchivedPacket)
        )

        let archivedData = NSMutableData(data: metadata)
        archivedData.appendData(name.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!)
        archivedData.appendData(data)

        return archivedData
    }

    func unarchive(data: NSData!) -> Packet {
        var archivedPacket = ArchivedPacket(index: 0, numberOfPackets: 0, nameLength: 0, dataLength: 0)
        let archivedStructLength = sizeof(ArchivedPacket)

        let archivedData = data.subdataWithRange(NSMakeRange(0, archivedStructLength))
        archivedData.getBytes(&archivedPacket)

        let nameRange = NSMakeRange(archivedStructLength, Int(archivedPacket.nameLength))
        let dataRange = NSMakeRange(archivedStructLength + Int(archivedPacket.nameLength), Int(archivedPacket.dataLength))

        let nameData = data.subdataWithRange(nameRange)
        let name = NSString(data: nameData, encoding: NSUTF8StringEncoding) as! String
        let theData = data.subdataWithRange(dataRange)

        let packet = Packet(name: name, index: archivedPacket.index, numberOfPackets: archivedPacket.numberOfPackets, data: theData)

        return packet
    }
}

Ответ 2

Swift 3

Это неизменная копия-копия с игровой площадки в Xcode 8.2.1, которая работает. Это немного проще, чем другие ответы.

import Foundation

enum WhizzoKind {
    case floom
    case bzzz
}

struct Whizzo {
    let name: String
    let num: Int
    let kind:WhizzoKind

    static func archive(w:Whizzo) -> Data {
        var fw = w
        return Data(bytes: &fw, count: MemoryLayout<Whizzo>.stride)
    }

    static func unarchive(d:Data) -> Whizzo {
        guard d.count == MemoryLayout<Whizzo>.stride else {
            fatalError("BOOM!")
        }

        var w:Whizzo?
        d.withUnsafeBytes({(bytes: UnsafePointer<Whizzo>)->Void in
            w = UnsafePointer<Whizzo>(bytes).pointee
        })
        return w!
    }
}

let thing = Whizzo(name:"Bob", num:77, kind:.bzzz)
print("thing = \(thing)")
let dataThing = Whizzo.archive(w: thing)
let convertedThing = Whizzo.unarchive(d: dataThing)
print("convertedThing = \(convertedThing)")

Примечания

Я не мог использовать методы экземпляра archive и unarchive, потому что Data.init(bytes:​count:​) мутирует по параметру bytes? И self не изменчив, поэтому... Это не имело для меня никакого смысла.

Перечисление WhizzoKind находится там, потому что это то, о чем я забочусь. Это не важно для примера. Кто-то может быть параноидальным по поводу перечислений, как я.

Мне пришлось сгладить этот ответ вместе с 4 другими вопросами и ответами SO:

И эти документы: - http://swiftdoc.org/v3.1/type/UnsafePointer/

И медитируя на синтаксисе закрытия Swift, пока я не захочу кричать.

Таким образом, благодаря этим другим SO-искателям/авторам.

Update

Итак, это не будет работать через устройства. Например, отправка с iPhone 7 на Apple Watch. Потому что stride отличается. Вышеприведенный пример - 80 байт на симуляторе iPhone 7, но 40 байтов на симуляторе Apple Watch Series 2.

Похоже, что подход (но не синтаксис) by @niklassaers по-прежнему остается единственным, который будет работать. Я собираюсь оставить этот ответ здесь, потому что он может помочь другим со всеми новыми синтаксисами Swift 3 и API, окружающими этот вопрос.

Наша единственная реальная надежда - это предложение Swift: https://github.com/apple/swift-evolution/blob/master/proposals/0166-swift-archival-serialization.md

Ответ 3

Я использовал пример Джеффа для создания следующей структуры:

struct Series {

var name: String?
var season: String?
var episode: String?

init(name: String?, season: String?, episode: String?) {
    self.name = name
    self.season = season
    self.episode = episode
}

static func archive(w: Series) -> Data {
    var fw = w
    return Data(bytes: &fw, count: MemoryLayout<Series>.stride)
}

static func unarchive(d: Data) -> Series {
    guard d.count == MemoryLayout<Series>.stride else {
        fatalError("Error!")
    }

    var w: Series?
    d.withUnsafeBytes({(bytes: UnsafePointer<Series>) -> Void in
        w = UnsafePointer<Series>(bytes).pointee
    })
    return w!
}

}

Как сказал Даг, все это немного хрупко. Иногда приложение вылетает, когда имя содержит пробел или подчеркивание/подчеркивание, а иногда оно сбой просто без причины. Во всех случаях имя, которое не имеет аналогов, похоже на это "4\200a\256". Удивительно, но это не проблема в случае сезона или эпизода (например, в "Сезон 2" ). Здесь пробел не приводит к сбою приложения.

Может быть, это альтернатива кодированию строк в utf8, но я недостаточно разбираюсь в методах архива/разборки, чтобы принять их для этого случая.

Ответ 4

Кажется, это появилось недавно, и для меня он выглядит солидным. Еще не пробовал...

https://github.com/a2/MessagePack.swift


Ну, у Swift нет магического метода сериализации, если это то, что вам нужно. Начиная с хороших дней C, когда у вас есть структура с указателем, это флаг, который вы не можете сериализовать байты этого экземпляра структуры без указания указателей и получения их данных. То же самое относится к Swift.

В зависимости от ваших потребностей и ограничений Serialization, я бы сказал, используя NSCoding или даже строки JSON будут приводить в порядок ваш код и сделать его более предсказуемым, чем текущее состояние. Конечно, вам нужно написать картограф, и есть накладные расходы. Все скажут вам следующее: "Сначала измерьте".

Теперь, вот интересная часть:

Если вы действительно хотите встроить свои данные в эту структуру и потоковое содержимое, не создавая пакет вокруг NSData, как вы делаете, вы можете зарезервировать байты с помощью Swift Tuples, которые работают так же, как резервировать байты в C с помощью char[CONST]:

struct what { 
    var x = 3 
}    

sizeof(what)

$R0: Int = 8

struct the { 
    var y = (3, 4, 5, 7, 8, 9, 33) 
}    

sizeof(the)

$R1: Int = 56

Чтобы немного развернуть это, я думаю, что это довольно ужасно, но возможно. Вы можете записать в ячейку памяти кортежа и прочитать из нее что-то вроде этого.