Cómo convertir datos a cadenas hexadecimales en Swift

Resuelto marius asked hace 8 años • 8 respuestas

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)
marius avatar Aug 22 '16 15:08 marius
Aceptado

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:).

Datase ajusta al Collectionprotocolo, por lo tanto, se puede utilizar map()para asignar cada byte a la cadena hexadecimal correspondiente. El %02xformato imprime el argumento en base 16, completado hasta dos dígitos con un cero a la izquierda si es necesario. El hhmodificador 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 $0es 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)
        }
    }
}
Martin R avatar Oct 17 '2016 14:10 Martin R

Este código amplía el Datatipo 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)}
    }
}
marius avatar Aug 22 '2016 08:08 marius

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)])
        })
    }
}
Nick Moore avatar Nov 24 '2017 16:11 Nick Moore

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 bufferdevuelto String.

También sepa que, debido a que los datos internos de Swift Stringson UTF16(pero pueden serlo UTF8desde 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.0licencia también está permitido (sin necesidad de atribución).

Top-Master avatar Oct 07 '2021 07:10 Top-Master