【问题标题】:Swift structs to NSData and backSwift 结构到 NSData 并返回
【发布时间】:2015-05-09 02:32:16
【问题描述】:

我有一个结构,其中包含一个结构和一个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))

of 只给我名称和数据的指针。我正在探索NSKeyedArchiver,但我必须将 Packet 设为一个对象,并且我更愿意将其保留为结构。

干杯

尼克

【问题讨论】:

标签: swift serialization struct nsdata nskeyedarchiver


【解决方案1】:

没有真正得到任何反馈,这是我最终得到的解决方案:

  1. 为我的结构创建 encode()decode() 函数
  2. Int 更改为 Int64,以便 Int 在 32 位和 64 位平台上具有相同的大小
  3. 有一个中间结构(ArchivedPacket),它没有字符串或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】:

    斯威夫特 5

    如果您使用的是 Apple 平台,请立即使用 Codable。见documentation

    斯威夫特 3

    这是来自 Xcode 8.2.1 中 Playground 的未经更改的复制粘贴,可以正常工作。它比其他答案简单一些。

    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)")
    

    注意事项

    我无法创建 archiveunarchive 实例方法,因为 Data.init(bytes:​count:​)bytes 参数上发生变异?而且self 是不可变的,所以...这对我来说毫无意义。

    WhizzoKind 枚举在那里,因为这是我关心的事情。这对于示例并不重要。有人可能像我一样对枚举偏执。

    我不得不从其他 4 个 SO 问题/答案中拼凑出这个答案:

    还有这些文档: - http://swiftdoc.org/v3.1/type/UnsafePointer/

    然后沉思 Swift 闭包语法,直到我想尖叫。

    感谢其他 SO 提问者/作者。

    更新

    所以这将跨设备工作。例如,从 iPhone 7 发送到 Apple Watch。因为stride 不同。上面的例子在 iPhone 7 模拟器上是 80 字节,但在 Apple Watch Series 2 模拟器上是 40 字节。

    看起来@niklassaers 的方法(但不是语法)仍然是唯一可行的方法。我将把这个答案留在这里,因为它可能会帮助其他人了解围绕这个主题的所有新的 Swift 3 语法和 API 更改。

    我们唯一真正的希望是这个 Swift 提案:https://github.com/apple/swift-evolution/blob/master/proposals/0166-swift-archival-serialization.md

    【讨论】:

    • 这甚至不能在同一个设备上工作,我不认为。它只是在您的示例中偶然起作用,因为 name 字符串保存在内存中。您实际上并没有在此处序列化字符串。
    • @DagÅgren 我将通过在 Playground 之外和完整项目中进行不同的测试来调查您的声明。
    【解决方案3】:

    我使用 Jeff 的示例创建了以下结构:

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

    就像 Dag 提到的那样,整个事情有点脆弱。有时当名称包含空格或下划线/下划线时应用程序崩溃,有时它会无缘无故地崩溃。在所有情况下,未归档的名称看起来都类似于“4\200a\256”。令人惊讶的是,对于季节或剧集(如“第 2 季”),这不是问题。此处的空格不会强制应用崩溃。

    也许将字符串编码为 utf8 是一种替代方法,但我对归档/取消归档方法不够熟悉,无法适应这种情况。

    【讨论】:

    • 这几乎对我有用。我创建了一些结构,刷出应用程序,然后返回到那里,但是如果我再次刷出然后重新点击应用程序,它就会崩溃。有什么想法吗?
    • 嗨 Max,我停止研究这个主题,因为我读到使用 Swift 4 将结构(至少使用字符串)转换为 NSData 会容易得多。对于我的项目,我决定将带有 JSON 的数据转换为资产并将其存储在 iCloud 中。
    【解决方案4】:

    基本结构对象最简单的方法是PropertyListEncoder & PropertyListDecoder

    这是示例代码;

    斯威夫特 5

    struct Packet: Codable {
       var name: String
       var index: Int
       var numberOfPackets: Int
       var data: Data
    }
    
    func getDataFromPacket(packet: Packet) -> Data?{
      do{
        let data = try PropertyListEncoder.init().encode(packet)
        return data
      }catch let error as NSError{
        print(error.localizedDescription)
      }
        return nil
    }
    
    func getPacketFromData(data: Data) -> Packet?{
        do{
          let packet = try PropertyListDecoder.init().decode(Packet.self, from: data)
          return packet
        }catch let error as NSError{
          print(error.localizedDescription)
        }
        
        return nil
    }
    

    【讨论】:

      【解决方案5】:

      这似乎是最近才出现的,对我来说它看起来很可靠。还没试过...

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


      好吧,Swift 没有任何神奇的序列化方法,如果那是你所追求的。自从 C 语言的好日子以来,当你有一个带有指针的结构时,这是一个标志,你不能​​在不跟随指针并获取它们的数据的情况下序列化该结构实例的字节。同样适用于 Swift。

      根据您的序列化需求和限制,我会说使用 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
      

      为了扩展一点,我认为这很可怕,但可能。您可以写入元组的内存位置并从中读取using something like this

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-08-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-06-21
        相关资源
        最近更新 更多