1

I am doing some Swift 5 conversion on code that I don't quite understand, legacy code from a previous developer. I get:

'withUnsafeBytes' is deprecated: use withUnsafeBytes(_: (UnsafeRawBufferPointer) throws -> R) rethrows -> R` instead

for:

func toArray<T>(type: T.Type) -> [T] {
    return self.withUnsafeBytes {
        [T](UnsafeBufferPointer(start: $0, count: self.count/MemoryLayout<T>.stride))
    }
}

I want to replace it with this but I am unsure if it does the same thing:

func toArray<T>(type: T.Type) -> [T] where T: ExpressibleByIntegerLiteral {
    var array = [T](repeating: 0, count: self.count/MemoryLayout<T>.stride)
    _ = array.withUnsafeMutableBytes { copyBytes(to: $0) }
    return array
}

Used in the context of these two fxs:

static func extractPacketSizeFromIV(iv: Data) -> Int32? {
    let array = iv.toArray(type: Int32.self)
    guard array.count == 4 else { return nil }

    let r0 = array[0].bigEndian
    let r1 = array[1].bigEndian
    let r2 = array[2].bigEndian

    return r2 ^ r1 ^ r0
}

static func extractGuidFromIV(iv: Data) -> Int32? {
    let array = iv.toArray(type: Int32.self)
    guard array.count == 4 else { return nil }

    let r0 = array[0].bigEndian
    let r1 = array[1].bigEndian
    let r2 = array[2].bigEndian
    let r3 = array[3].bigEndian

    return r3 ^ r2 ^ r1 ^ r0
}
Joshua Hart
  • 772
  • 1
  • 21
  • 31
  • Thanks @Rob I added more info for how they are used. – Joshua Hart May 10 '19 at 00:54
  • Your code looks like the Swift 4 and Swift 5 versions from [round trip Swift number types to/from Data](https://stackoverflow.com/a/38024025/1187415) – you could have left a comment and ask for clarification :) – Martin R May 10 '19 at 04:47

2 Answers2

1

First of all, your toArray is defined in an extension of Data, right?

(Please clarify such things when you write questions.)

Your code would work in the same way as the code from a previous developer in your use case, but I would write the equivalent in Swift 5 like this:

func toArray<T>(type: T.Type) -> [T] {
    return self.withUnsafeBytes {
        [T]($0.bindMemory(to: type))
    }
}

bindMemory(to: type) creates an UnsafeBufferPointer<T> (as in the original code) from the parameter passed from new withUnsafeBytes - which is UnsafeRawBufferPointer.

OOPer
  • 47,149
  • 6
  • 107
  • 142
  • 1
    The potential problem with bindMemory is that it requires the memory to be properly *aligned* for the bound type, and it is generally difficult to tell how the memory of a given data value is aligned. That's why I suggested copyMemory in https://stackoverflow.com/a/38024025/1187415. – Martin R May 10 '19 at 04:49
  • Yes, of course, but this question made it clear that the `Data` consisted of these three/four `Int32` values only, so I wouldn’t go about complicating this unnecessarily with `copyMemory` or what have you (esp if the buffer was large, homogenous collection). Sure, if you had a more complicated `Data` with heterogenous data types of different sizes, you’d take it to the next level, but not here, IMHO. – Rob May 10 '19 at 06:09
1

I would use the suggested replacement, namely withUnsafeBytes(_:), whose first parameter is a UnsafeRawBufferPointer, directly, not building an array or copying buffers unnecessarily, e.g.:

static func extractPacketSizeFromIV(iv: Data) -> Int32? {
    return iv.withUnsafeBytes { rawBuffer -> Int32 in
        let buffer = rawBuffer.bindMemory(to: Int32.self)
        let r0 = buffer[0].bigEndian
        let r1 = buffer[1].bigEndian
        let r2 = buffer[2].bigEndian

        return r2 ^ r1 ^ r0
    }
}

Obviously, if your Data was more complicated than this (e.g. a heterogenous payload with lots of different types of different sizes), different approaches might be called for, but if this is a simple buffer with just a simple collection of Int32, the above is an efficient way to retrieve the necessary values.

Rob
  • 415,655
  • 72
  • 787
  • 1,044