1. ホーム
  2. swift

[解決済み] swiftでデータを16進文字列に変換する方法

2023-02-06 14:21:45

質問

SwiftでDataの値を16進数で表示したいのですが。

最終的にはこんな風に使いたい。

let data = Data(base64Encoded: "aGVsbG8gd29ybGQ=")!
print(data.hexString)

どのように解決するのですか?

簡単な実装( SwiftでSHA1を使ってNSStringをハッシュする方法は? を参考にし、大文字出力のオプションを追加したもの) は次のようになります。

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()
    }
}

を選んだのは hexEncodedString(options:) メソッドを既存のメソッドのスタイルで base64EncodedString(options:) .

Data に準拠し Collection プロトコルに準拠しているので map() を使って各バイトを対応する16進文字列に対応付けることができます。 そのため %02x 形式は,引数を16進数で表示し,2桁まで埋める。 に,必要ならば先頭の0を加えて表示する。また hh 修飾子を使うと,引数 (スタック上で整数として渡される)は1バイトの量として扱われる。 量として扱われます。ここで修飾子を省略することができるのは $0 符号なし の数である ( UInt8 ) で、符号拡張は行われませんが、そのままでも問題ありません。 を残しておいても害はありません。

その結果、1つの文字列に結合されます。

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


以下の実装は、50倍ほど高速です。 (1000ランダムバイトでテスト)。この実装は RenniePetの解決策 Nick Mooreの解答 を利用していますが String(unsafeUninitializedCapacity:initializingUTF8With:) は Swift 5.3/Xcode 12 で導入され、macOS 11 および iOS 14 以降で利用可能です。

このメソッドは、不要なコピーや再割り当てを行わずに、UTF-8ユニットからSwift文字列を効率的に作成することができます。

古い 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)
        }
    }
}