1

I've actually googled this extensively, within stackoverflow and elsewhere. Most questions are about [UInt8] to String or [UInt8] to type_a (not array).

To clarify, I'd like to take an array of type_a. Get its pointer and tell swift to treat the next n iterations of type_b (size_of) as array of type_b.

I've tried variations of https://stackoverflow.com/a/26954091/5276890 which didn't work. A comment there led me to https://stackoverflow.com/a/42255468/5276890. withMemoryRebound seems like the right way but I couldn't find the right invocation.

Here's a sample code of what I'm doing instead to convert [UInt8] to [UInt32.bigEndian], both to clarify and in case it's useful (not likely)

    var intData = [UInt32]()
    let M = UInt32(256*256*256)
    var m = M
    var bigE:UInt32 = 0
    for i in 0..<data.count {
        bigE += UInt32(data[i]) * m
        if m == 1 {
            intData.append(bigE)
            bigE = 0
            m = M
        } else {
            m = m/256
        }
    }

<disclaimer+rant>
I have to admit I never could figure out the whole closures+withUnsafe* syntax and mostly used patterns online and modified them. I'd spend the time learning this, just as soon as the language authors decide and settle down on one specific syntax :(
</disclaimer+rant>

Community
  • 1
  • 1
Roy Falk
  • 1,605
  • 3
  • 17
  • 41

1 Answers1

3
  • Use withUnsafeBufferPointer to get a pointer to the element storage of the source array.
  • Use withMemoryRebound to "reinterpret" that pointer as pointing to elements of the target type.
  • Use Array(UnsafeBufferPointer(...) to create an array of the target type.

Example:

let source: [UInt16] = [1, 2, 3, 4]

let dest = source.withUnsafeBufferPointer {
    $0.baseAddress!.withMemoryRebound(to: UInt32.self, capacity: 2) {
        Array(UnsafeBufferPointer(start: $0, count: 2))
    }
}

print(dest) // [131073, 262147]

Or as a generic function:

func convertArray<S, T>(_ source: [S], to: T.Type) -> [T] {
    let count = source.count * MemoryLayout<S>.stride/MemoryLayout<T>.stride
    return source.withUnsafeBufferPointer {
        $0.baseAddress!.withMemoryRebound(to: T.self, capacity: count) {
            Array(UnsafeBufferPointer(start: $0, count: count))
        }
    }

}

Example:

let source: [UInt16] = [1, 2, 3, 4]
let dest = convertArray(source, to: UInt32.self)
print(dest) // [131073, 262147]

If you only need a (temporary) view on the array storage interpreted in another type then you can avoid the Array creation and use the UnsafeBufferPointer (which is a Collection and has array-like methods) without copying the data:

source.withUnsafeBufferPointer {
    $0.baseAddress!.withMemoryRebound(to: UInt32.self, capacity: 2) {
        let u32bufptr = UnsafeBufferPointer(start: $0, count: 2)

        // ... Operate on u32bufptr ...
        for elem in u32bufptr { print(elem) }
    }
}
Martin R
  • 488,667
  • 78
  • 1,132
  • 1,248
  • Works like a charm. Note that this doesn't do big endian. If you got here by googling something about big endian, use the bigEndian method of UInt32. – Roy Falk Apr 28 '17 at 08:23