LoginSignup
9
4

More than 5 years have passed since last update.

SwiftのStringを行儀よく使ったHex文字列byte配列変換

Posted at

SwiftのStringは生半可な知識で挑むといろいろ面食らいます。
度々「そもそも文字とは何か」という概念的な問いまで遡っては、「今はそんなことを考えたいわけではないんだ!」と我に帰ったりしがちです。(Swiftあるある)

練習としてStringの流儀に沿った形での16進数文字列 -> Data変換を考えてみました。

ポイントは

  • 全体的にUnicodeScalarViewを扱う
    • Swiftの文字列の内部表現に一番近い(低コスト)
    • スペースや改行のスキップ判定にCharacterSetを使いやすい
  • 全体の文字数をカウントしたり、毎回先頭からのオフセットをとるなどコストの高い計算を避ける
  • 末尾再帰による最適化(を期待)
  • 1byte16進数のパースはUInt8(_:string, radix:int)におまかせしている
    • さらに最適化をめざすならここを自作してstringを経由しない形にしてもいいかも

String+hex.swift

import Foundation

//    "".hexData.hexString                //
//    "01".hexData.hexString              // 01
//    "ab".hexData.hexString              // ab
//    "abff 99".hexData.hexString         // abff99
//    "abff\n99".hexData.hexString        // abff99
//    "abzff 99".hexData.hexString        // ab
//    "abf ff 99".hexData.hexString       // ab
//    "abf".hexData.hexString             // ab

fileprivate func convertHex(_ s: String.UnicodeScalarView, i: String.UnicodeScalarIndex, appendTo d: [UInt8]) -> [UInt8] {

    let skipChars = CharacterSet.whitespacesAndNewlines

    guard i != s.endIndex else { return d }

    let next1 = s.index(after: i)

    if skipChars.contains(s[i]) {
        return convertHex(s, i: next1, appendTo: d)
    } else {
        guard next1 != s.endIndex else { return d }
        let next2 = s.index(after: next1)

        let sub = String(s[i..<next2])

        guard let v = UInt8(sub, radix: 16) else { return d }

        return convertHex(s, i: next2, appendTo: d + [ v ])
    }
}

extension String {

    /// Convert Hexadecimal String to Array<UInt>
    ///     "0123".hex                // [1, 35]
    ///     "aabbccdd 00112233".hex   // 170, 187, 204, 221, 0, 17, 34, 51]
    var hex : [UInt8] {
        return convertHex(self.unicodeScalars, i: self.unicodeScalars.startIndex, appendTo: [])
    }

    /// Convert Hexadecimal String to Data
    ///     "0123".hexData                    /// 0123
    ///     "aa bb cc dd 00 11 22 33".hexData /// aabbccdd 00112233
    var hexData : Data {
        return Data(convertHex(self.unicodeScalars, i: self.unicodeScalars.startIndex, appendTo: []))
    }
}

extension Data {
    var hexString : String {
        return self.reduce("") { (a : String, v : UInt8) -> String in
            return a + String(format: "%02x", v)
        }
    }
}

参考

なぜSwiftの文字列APIは難しいのか | プログラミング | POSTD
http://postd.cc/why-is-swifts-string-api-so-hard/

String - Swift Standard Library | Apple Developer Documentation
https://developer.apple.com/reference/swift/string

9
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
4