Skip to main content
2 of 5
added 350 characters in body
Martin R
  • 23.5k
  • 2
  • 36
  • 92

Instead of implementing consume() for each integer type separately, you can implement a single generic method:

mutating func consume<T: BinaryInteger>() throws -> T {
    let size = MemoryLayout<T>.size
    guard idx + size <= bytes.count else {
        throw Error.notEnoughBytes
    }
    defer { idx += size }
    return bytes[idx..<idx + size].enumerated().reduce(0) {
        $0 + T($1.element) << (8 * $1.offset)
    }
}

which can be used for all signed and unsigned integer types (U)Int, (U)Int8, ..., (U)Int64.


Instead of letting the compiler infer the return type from the context, one can alternatively pass it as a parameter:

mutating func consume<T: BinaryInteger>(_: T.Type) throws -> T { ... }

which is then – for example – called as

let crc32 = try buffer.consume(UInt32.self)

I would probably call the method get() or read() instead of consume().


Defining a local enum Error type which conforms to the (global) Error protocol is possible, but might be confusing to the reader. I would use a different name for the concrete error type, for example:

enum ReadError: Error {
    case notEnoughBytes
}

Now let's have a look how an error would be reported. The caller does not know the actual error type, so a typical calling sequence is:

do {
    let someData = Data(bytes: [1])
    var buffer = ConsumableByteArray(data: someData)
    let crc32: UInt32 = try buffer.consume()
    print(crc32)
} catch {
    print(error.localizedDescription)
}

This produces the output:

The operation couldn’t be completed. (MyProg.ConsumableByteArray.ReadError error 0.)

This can be improved by adopting the LocalizedError protocol (see for example How to provide a localized description with an Error type in Swift? on Stack Overflow):

enum ReadError: Error, LocalizedError {
    case notEnoughBytes
    
    public var errorDescription: String? {
        switch self {
        case .notEnoughBytes:
            return "Not enough bytes in buffer"
        }
    }
}

Now the error output of the above program becomes

Not enough bytes in buffer

You can even store additional information about the error in associated values:

enum ReadError: Error, LocalizedError {
    case notEnoughBytes(available: Int, needed: Int)
    
    public var errorDescription: String? {
        switch self {
        case .notEnoughBytes(let available, let needed):
            return "Not enough bytes in buffer (available: \(available), needed: \(needed))"
        }
    }
}

Then by throwing

 throw ReadError.notEnoughBytes(available: bytes.count - idx, needed: size)

an error message like

Not enough bytes in buffer (available: 1, needed: 4)

is produced.


Finally note that there is a ByteBuffer type as part of the SwiftNIO framework, which can do all this and more. Even if you decide not to use it, having a look at its documentation and interface might be instructive.

Martin R
  • 23.5k
  • 2
  • 36
  • 92