Cómo convertir datos a cadenas hexadecimales en Swift
Quiero la representación hexadecimal de un valor de datos en Swift.
Al final me gustaría usarlo así:
let data = Data(base64Encoded: "aGVsbG8gd29ybGQ=")!
print(data.hexString)
Una implementación simple (tomada de ¿Cómo aplicar hash a NSString con SHA1 en Swift?, con una opción adicional para salida en mayúsculas) sería
extension Data {
struct HexEncodingOptions: OptionSet {
let rawValue: Int
static let upperCase = HexEncodingOptions(rawValue: 1 << 0)
}
func hexEncodedString(options: HexEncodingOptions = []) -> String {
let format = options.contains(.upperCase) ? "%02hhX" : "%02hhx"
return self.map { String(format: format, $0) }.joined()
}
}
Elegí un hexEncodedString(options:)
método al estilo del método existente base64EncodedString(options:)
.
Data
se ajusta al Collection
protocolo, por lo tanto, se puede utilizar
map()
para asignar cada byte a la cadena hexadecimal correspondiente. El %02x
formato imprime el argumento en base 16, completado hasta dos dígitos con un cero a la izquierda si es necesario. El hh
modificador hace que el argumento (que se pasa como un número entero en la pila) se trate como una cantidad de un byte. Se podría omitir el modificador aquí porque $0
es un
número sin signoUInt8
( ) y no se producirá ninguna extensión de signo, pero no hace daño dejarlo ahí.
Luego, el resultado se une a una sola cadena.
Ejemplo:
let data = Data([0, 1, 127, 128, 255])
// For Swift < 4.2 use:
// let data = Data(bytes: [0, 1, 127, 128, 255])
print(data.hexEncodedString()) // 00017f80ff
print(data.hexEncodedString(options: .upperCase)) // 00017F80FF
La siguiente implementación es más rápida en un factor de aproximadamente 50 (probada con 1000 bytes aleatorios). Está inspirado en
la solución de RenniePet
y la solución de Nick Moore , pero aprovecha la
String(unsafeUninitializedCapacity:initializingUTF8With:)
cual se introdujo con Swift 5.3/Xcode 12 y está disponible en macOS 11 y iOS 14 o posterior.
Este método permite crear una cadena Swift a partir de unidades UTF-8 de manera eficiente, sin copias ni reasignaciones innecesarias.
También se proporciona una implementación alternativa para versiones anteriores de macOS/iOS.
extension Data {
struct HexEncodingOptions: OptionSet {
let rawValue: Int
static let upperCase = HexEncodingOptions(rawValue: 1 << 0)
}
func hexEncodedString(options: HexEncodingOptions = []) -> String {
let hexDigits = options.contains(.upperCase) ? "0123456789ABCDEF" : "0123456789abcdef"
if #available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *) {
let utf8Digits = Array(hexDigits.utf8)
return String(unsafeUninitializedCapacity: 2 * self.count) { (ptr) -> Int in
var p = ptr.baseAddress!
for byte in self {
p[0] = utf8Digits[Int(byte / 16)]
p[1] = utf8Digits[Int(byte % 16)]
p += 2
}
return 2 * self.count
}
} else {
let utf16Digits = Array(hexDigits.utf16)
var chars: [unichar] = []
chars.reserveCapacity(2 * self.count)
for byte in self {
chars.append(utf16Digits[Int(byte / 16)])
chars.append(utf16Digits[Int(byte % 16)])
}
return String(utf16CodeUnits: chars, count: chars.count)
}
}
}
Este código amplía el Data
tipo con una propiedad calculada. Itera a través de los bytes de datos y concatena la representación hexadecimal del byte con el resultado:
extension Data {
var hexDescription: String {
return reduce("") {$0 + String(format: "%02x", $1)}
}
}
Mi version. Es aproximadamente 10 veces más rápido que la respuesta [original] aceptada por Martin R.
public extension Data {
private static let hexAlphabet = Array("0123456789abcdef".unicodeScalars)
func hexStringEncoded() -> String {
String(reduce(into: "".unicodeScalars) { result, value in
result.append(Self.hexAlphabet[Int(value / 0x10)])
result.append(Self.hexAlphabet[Int(value % 0x10)])
})
}
}
Solución rápida y compatible con versiones anteriores:
extension Data {
/// Fast convert to hex by reserving memory (instead of mapping and join).
public func toHex(uppercase: Bool = false) -> String {
// Constants (Hex has 2 characters for each Byte).
let size = self.count * 2;
let degitToCharMap = Array((
uppercase ? "0123456789ABCDEF" : "0123456789abcdef"
).utf16);
// Reserve dynamic memory (plus one for null termination).
let buffer = UnsafeMutablePointer<unichar>.allocate(capacity: size + 1);
// Convert each byte.
var index = 0
for byte in self {
buffer[index] = degitToCharMap[Int(byte / 16)];
index += 1;
buffer[index] = degitToCharMap[Int(byte % 16)];
index += 1;
}
// Set Null termination.
buffer[index] = 0;
// Casts to string (without any copying).
return String(utf16CodeUnitsNoCopy: buffer,
count: size, freeWhenDone: true)
}
}
Tenga en cuenta que lo anterior transfiere la propiedad al objeto
buffer
devueltoString
.También sepa que, debido a que los datos internos de Swift
String
sonUTF16
(pero pueden serloUTF8
desde Swift 5), todas las soluciones proporcionadas en la respuesta aceptada hacen una copia completa (y son más lentas), al menos si NO#available(macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0, *)
;-)Como mencioné en mi perfil, el uso bajo
Apache 2.0
licencia también está permitido (sin necesidad de atribución).