yielding yielding - 7 months ago 52
Swift Question

swift 3: How to extend [UInt8] to add getUInt32BE function

I'm trying to extend

[UInt8]
with
getUInt32BE()
function like below code.

Here's the error that receives:


"Cannot subscript a value of type 'Self' with an index of type
'Range'"


Could you help me correct this error?

extension Collection where Iterator.Element == UInt8 {
public func getUInt32BE(at: Int = 0) -> UInt32 {
return self[at..<at+4].reduce(0) {
$0 << 8 + UInt32($1)
}
}
}


Thanks in advance :)

Answer

Alternative #1: use .pointee property of UnsafePointer

As described in the following Q&A

you can use, in Swift 2.2, e.g.

UnsafePointer<UInt16>(bytes).memory

to convert a 2-byte UInt8 array to a UInt16 value (host byte order).

Now, in Swift 3.0-dev .memory has been replaced by .pointee. Applying the above as well as this change, we can likewise use UnsafePointer<UInt32>(bytes).pointee to access the UInt32 representation of a 4-byte UInt8 array, however noting that the representation uses host byte order for conversion. To possible (if necessary) convert this to the big endian representation of the integer, we can use the .bigEndian property available to each integer type, as described in the following Q&A:

This is still valid in Swift 3.0-dev, and consequently, we can construct your getUInt32BE(...) method as follows

extension Collection where Iterator.Element == UInt8 {
    public func getUInt32BE(at: Index.Distance) -> UInt32? {
        let from = startIndex.advanced(by: at, limit: endIndex)
        let to = from.advanced(by: 4, limit: endIndex)

        guard case let bytes = Array(self[from..<to]) 
            where bytes.count == 4 else { return nil }

        return UnsafePointer<UInt32>(bytes).pointee.bigEndian
    }
}

Alternative #2: use bit shifting

Alternatively, using a modified version of the above with bit shifting (similar to the attempt of your question) rather than using UnsafePointer<..>:

extension Collection where Iterator.Element == UInt8 {

    public func getUInt32BE(at: Index.Distance) -> UInt32? {
        let from = startIndex.advanced(by: at, limit: endIndex)
        let to = from.advanced(by: 4, limit: endIndex)

        guard case let bytesSlice = self[from..<to] 
            where from.distance(to: to) == 4 else { return nil }

        return bytesSlice.reduce(0) { (tot, val) -> UInt32 in
            tot << 8 + UInt32(val as! UInt8)
        }
    }
}

Note that we need to help out the compiler by forcibly casting val from Iterator.Element to UInt8 (which is guaranteed to succeed due to the where clause of the extension; Iterator.Element == UInt8).


Example usage

Example usage for any of the two alternatives above:

/* example usage */
let bytes: [UInt8] = [
    0,   // 0b 0000 0000
    255, // 0b 1111 1111 
    0,   // 0b 0000 0000
    104, // 0b 0110 1000
    76]  // 0b 0100 1100

/*  byteArr[1..<5], big-endian:
        0b 1111 1111 0000 0000 0110 1000 0100 1100 */
let u32verify = 0b11111111000000000110100001001100 
print(u32verify) // 4278216780

if let u32val = bytes.getUInt32BE(1) {
    print(u32val) // 4278216780, OK
}

For details on the Collection protocol (the reason of your error above is not using the associated type Index.Distance of this protocol to construct your sub-array), see e.g.