Parsing Hex Strings in Swift
August 12th, 2017I want to turn the string "0x02 0x03 0x04"
into an array of bytes ([UInt8]
). Seems simple enough right?
let hex = "0x02 0x03 0x04"
let components = hex.components(separatedBy: " ")
let array = components.map { UInt8($0) }
But the UInt8
constructor that takes a string doesn't understand the "0x" prefix to indicate hex strings. There's a handy UInt8("3F" radix:16)
constructor that does understand hex but we have to remove the prefix first.
Swift doesn't have an obvious way to remove the first 2 characters from a string.
This works:
let hexStr = "0x3F"
let hexValue = UInt8(hexStr.replacingOccurrences(of: "0x", with: ""), radix: 16)
But this will scan the entire string (admittedly only two more bytes in this case). But is there an easy way to just skip the first two bytes?
String has substring(from:String.Index)
which seems like what we want, but "0x3F".substring(from:2)
doesn't compile. The String.Index
is a type that's managed by the String, to account for Unicode glyphs that are longer than one byte.
hexStr.substring(from: hexStr.index(hexStr.startIndex, offsetBy: 2))
will give us the correct substring but this is a mouthful - it's hard to tell from scanning that line that all we want to do is remove the first two characters.
So we have these two options to do this with Swift string methods:
let hex = "0x3F"
let hex1 = hex.replacingOccurrences(of: "0x", with: "")
let hex2 = hex.substring(from: hex.index(hex.startIndex, offsetBy: 2))
As expected, a quick benchmark shows the second version is about 4 times faster than the first version.
There's also Scanner
, which can take the hex string with the 0x
prefix and return a hex value, but it works with UInt32
and UInt64
types so there's some extra gymnastics to get the result into a UInt8
. Scanner
benchmarks to be almost as fast as using substring, but using a separate class for this operation feels like more overhead than I want.
One of the great things about Swift is we can drop down to C code, and for this particular operation, there's a C function that fits the bill exactly.
let hex3 = UInt8(strtoul(hex, nil, 16))
The C strtoul
function converts a string given a radix, and for radix 16, ignores the prefix. And it's 3x faster than the Swift substring version above.
For readability let's wrap this up into a UInt8 extension:
extension UInt8 {
static func from(hexString: String) -> UInt8 {
return UInt8(strtoul(hex, nil, 16))
}
}
With that in place we finally have:
let hex4 = UInt8.from(hexString: "0x3F")
Since making it easy to apply to other int sizes veers off into generics, I'll leave that as an exercise for the reader.