Reading Data into a Struct in Swift

前端 未结 3 1875
忘掉有多难
忘掉有多难 2020-12-06 07:46

I\'m trying to read bare Data into a Swift 4 struct using the withUnsafeBytes method. The problem

The network UDP packet has t

相关标签:
3条回答
  • 2020-12-06 08:18

    Following on Leo Dabus answer, I created a slightly more readable constructor:

    extension Data {
        func object<T>(at index: Index) -> T {
            subdata(in: index ..< index.advanced(by: MemoryLayout<T>.size))
                .withUnsafeBytes { $0.load(as: T.self) }
        }
    }
    

    struct XPLBeacon {
        var majorVersion: UInt8
        var minorVersion: UInt8
        var applicationHostId: UInt32
        var versionNumber: UInt32
    
        init(data: Data) {
            var index = data.startIndex
            majorVersion = data.object(at: index)
    
            index += MemoryLayout.size(ofValue: majorVersion)
            minorVersion = data.object(at: index)
    
            index += MemoryLayout.size(ofValue: minorVersion)
            applicationHostId = data.object(at: index)
    
            index += MemoryLayout.size(ofValue: applicationHostId)
            versionNumber = data.object(at: index)
        }
    }
    

    What is not part of this is of course the checking for the correctness of the data. As other mentioned in comments, this could be done either by having a failable init method or by throwing an Error.

    0 讨论(0)
  • 2020-12-06 08:29

    Reading the entire structure from the data does not work because the struct members are padded to their natural boundary. The memory layout of struct XPLBeacon is

     A B x x C C C C D D D D
    

    where

     offset    member
      0        A       - majorVersion (UInt8)
      1        B       - minorVersion (UInt8)
      2        x x     - padding
      4        C C C C - applicationHostId (UInt32)
      8        D D D D - versionNumber (UInt32)
    

    and the padding is inserted so that the UInt32 members are aligned to memory addresses which are a multiple of their size. This is also confirmed by

    print(MemoryLayout<XPLBeacon>.size) // 12
    

    (For more information about alignment in Swift, see Type Layout).

    If you read the entire data into the struct then the bytes are assigned as follows

     01 02 0A 00 00 00 0B 00 00 00
     A  B  x  x  C  C  C  C  D  D  D  D
    

    which explains why major/minorVersion are correct, but applicationHostId and versionNumber are wrong. Reading all members separately from the data is the correct solution.

    0 讨论(0)
  • 2020-12-06 08:38

    Since Swift 3 Data conforms to RandomAccessCollection, MutableCollection, RangeReplaceableCollection. So you can simply create a custom initializer to initialise your struct properties as follow:

    struct XPLBeacon {
        let majorVersion, minorVersion: UInt8             // 1 + 1 = 2 Bytes
        let applicationHostId, versionNumber: UInt32      // 4 + 4 = 8 Bytes
        init(data: Data) {
            self.majorVersion = data[0]
            self.minorVersion = data[1]
            self.applicationHostId = data
                .subdata(in: 2..<6)
                .withUnsafeBytes { $0.load(as: UInt32.self) }
            self.versionNumber = data
                .subdata(in: 6..<10)
                .withUnsafeBytes { $0.load(as: UInt32.self) }
        }
    }
    

    var data = Data([0x01,0x02, 0x0A, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00,0x00])
    print(data as NSData)     // "{length = 10, bytes = 0x01020a0000000b000000}\n" <01020a00 00000b00 0000>
    
    let beacon = XPLBeacon(data: data)
    beacon.majorVersion       // 1
    beacon.minorVersion       // 2
    beacon.applicationHostId  // 10
    beacon.versionNumber      // 11
    
    0 讨论(0)
提交回复
热议问题