【问题标题】:Converting Hex String to NSData in Swift在 Swift 中将十六进制字符串转换为 NSData
【发布时间】:2014-12-17 12:55:15
【问题描述】:

我得到了在 Objective-C 中将 String 转换为 HEX-String 的代码:

- (NSString *) CreateDataWithHexString:(NSString*)inputString {
    NSUInteger inLength = [inputString length];    
      
    unichar *inCharacters = alloca(sizeof(unichar) * inLength);
    [inputString getCharacters:inCharacters range:NSMakeRange(0, inLength)];
    
    UInt8 *outBytes = malloc(sizeof(UInt8) * ((inLength / 2) + 1));
    
    NSInteger i, o = 0;
    UInt8 outByte = 0;

    for (i = 0; i < inLength; i++) {
        UInt8 c = inCharacters[i];
        SInt8 value = -1;
        
        if      (c >= '0' && c <= '9') value =      (c - '0');
        else if (c >= 'A' && c <= 'F') value = 10 + (c - 'A');
        else if (c >= 'a' && c <= 'f') value = 10 + (c - 'a');
        
        if (value >= 0) {
            if (i % 2 == 1) {
                outBytes[o++] = (outByte << 4) | value;
                outByte = 0;
            } else {
                outByte = value;
            }
            
        } else {
            if (o != 0) break;
        }
    }
    
    NSData *a = [[NSData alloc] initWithBytesNoCopy:outBytes length:o freeWhenDone:YES];
    NSString* newStr = [NSString stringWithUTF8String:[a bytes]];
    return newStr;
}

我希望在 Swift 中也一样。任何人都可以用 Swift 翻译这段代码吗,或者有什么简单的方法可以在 Swift 中做到这一点?

【问题讨论】:

    标签: ios objective-c swift


    【解决方案1】:

    这是我到Data 例程的十六进制字符串:

    extension String {
        
        /// Create `Data` from hexadecimal string representation
        ///
        /// This creates a `Data` object from hex string. Note, if the string has any spaces or non-hex characters (e.g. starts with '<' and with a '>'), those are ignored and only hex characters are processed.
        ///
        /// - returns: Data represented by this hexadecimal string.
        
        var hexadecimal: Data? {
            var data = Data(capacity: count / 2)
            
            let regex = try! NSRegularExpression(pattern: "[0-9a-f]{1,2}", options: .caseInsensitive)
            regex.enumerateMatches(in: self, range: NSRange(startIndex..., in: self)) { match, _, _ in
                let byteString = (self as NSString).substring(with: match!.range)
                let num = UInt8(byteString, radix: 16)!
                data.append(num)
            }
            
            guard data.count > 0 else { return nil }
            
            return data
        }
        
    }
    

    为了完整起见,这是我的Data 到十六进制字符串例程:

    extension Data {
        
        /// Hexadecimal string representation of `Data` object.
        
        var hexadecimal: String {
            return map { String(format: "%02x", $0) }
                .joined()
        }
    }
    

    注意,如上所示,我通常只在十六进制表示和NSData 实例之间进行转换(因为如果信息可以表示为字符串,您可能一开始就不会创建十六进制表示) .但是您最初的问题想要在十六进制表示和 String 对象之间进行转换,这可能看起来像这样:

    extension String {
        
        /// Create `String` representation of `Data` created from hexadecimal string representation
        ///
        /// This takes a hexadecimal representation and creates a String object from that. Note, if the string has any spaces, those are removed. Also if the string started with a `<` or ended with a `>`, those are removed, too.
        ///
        /// For example,
        ///
        ///     String(hexadecimal: "<666f6f>")
        ///
        /// is
        ///
        ///     Optional("foo")
        ///
        /// - returns: `String` represented by this hexadecimal string.
        
        init?(hexadecimal string: String, encoding: String.Encoding = .utf8) {
            guard let data = string.hexadecimal() else {
                return nil
            }
            
            self.init(data: data, encoding: encoding)
        }
                
        /// Create hexadecimal string representation of `String` object.
        ///
        /// For example,
        ///
        ///     "foo".hexadecimalString()
        ///
        /// is
        ///
        ///     Optional("666f6f")
        ///
        /// - parameter encoding: The `String.Encoding` that indicates how the string should be converted to `Data` before performing the hexadecimal conversion.
        ///
        /// - returns: `String` representation of this String object.
        
        func hexadecimalString(encoding: String.Encoding = .utf8) -> String? {
            return data(using: encoding)?
                .hexadecimal
        }
        
    }
    

    然后你可以像这样使用上面的:

    let hexString = "68656c6c 6f2c2077 6f726c64"
    print(String(hexadecimal: hexString))
    

    或者,

    let originalString = "hello, world"
    print(originalString.hexadecimalString())
    

    有关早期 Swift 版本的上述排列,请参阅此问题的修订历史记录。

    【讨论】:

    • extension NSData { /// Create hexadecimal string representation of NSData object. /// /// :returns: String representation of this NSData object. func hexadecimalString() -&gt; String { var string = NSMutableString(capacity: length * 2) var byte: Byte? for i in 0 ..&lt; length { getBytes(&amp;byte, range: NSMakeRange(i, 1)) string.appendFormat("%02x", byte!) } return string } } 这似乎在 Swift 1.2 中崩溃了......试图找出原因。
    • @Rob 感谢更新线程。我只使用函数 dataFromHexadecimalString(),而不是 AppDelegate.swift 中的整个扩展,并且我收到此错误“AppDelegate”没有名为“stringByTrimmingCharactersInSet”的成员
    • @QadirHussain - 上面的代码在 Swift 4 中运行良好。我刚刚再次测试了它。我怀疑您的项目中存在其他问题。
    • 它适用于我,例如let string = "Hello السلام عليكم"let hex = string.hexadecimalString()!let string2 = String(hexadecimal: hex)!。它工作正常。您需要向我们展示一个不起作用的示例。我建议您发布您自己的问题,向我们展示一个可以正常工作的示例。我敢打赌你正在处理一个不是有效 UTF8 表示的十六进制字符串,但我不能说没有看到它。我们不应该在这里使用 cmets 来处理这种问答,所以请发布您自己的问题。
    • 我刚刚将 hexadecimal() 函数与下面的 Rok Gregorič 答案进行了基准测试,Rob 的速度几乎正好是其两倍(在我的 Mac 上)
    【解决方案2】:

    将十六进制字符串转换为数据和字符串:

    Swift1

    func dataWithHexString(hex: String) -> NSData {
        var hex = hex
        let data = NSMutableData()
        while(countElements(hex) > 0) {
            var c: String = hex.substringToIndex(advance(hex.startIndex, 2))
            hex = hex.substringFromIndex(advance(hex.startIndex, 2))
            var ch: UInt32 = 0
            NSScanner(string: c).scanHexInt(&ch)
            data.appendBytes(&ch, length: 1)
        }
        return data
    }
    

    使用:

    let data = dataWithHexString("68656c6c6f2c20776f726c64") // <68656c6c 6f2c2077 6f726c64>
    if let string = NSString(data: data, encoding: 1) {
        print(string) // hello, world
    }
    

    Swift2

    func dataWithHexString(hex: String) -> NSData {
        var hex = hex
        let data = NSMutableData()
        while(hex.characters.count > 0) {
            let c: String = hex.substringToIndex(hex.startIndex.advancedBy(2))
            hex = hex.substringFromIndex(hex.startIndex.advancedBy(2))
            var ch: UInt32 = 0
            NSScanner(string: c).scanHexInt(&ch)
            data.appendBytes(&ch, length: 1)
        }
        return data
    }
    

    使用:

    let data = dataWithHexString("68656c6c6f2c20776f726c64") // <68656c6c 6f2c2077 6f726c64>
    if let string = String(data: data, encoding: NSUTF8StringEncoding) {
        print(string) //"hello, world"
    }
    

    Swift3

    func dataWithHexString(hex: String) -> Data {
        var hex = hex
        var data = Data()
        while(hex.characters.count > 0) {
            let c: String = hex.substring(to: hex.index(hex.startIndex, offsetBy: 2))
            hex = hex.substring(from: hex.index(hex.startIndex, offsetBy: 2))
            var ch: UInt32 = 0
            Scanner(string: c).scanHexInt32(&ch)
            var char = UInt8(ch)
            data.append(&char, count: 1)
        }
        return data
    }
    

    使用:

    let data = dataWithHexString(hex: "68656c6c6f2c20776f726c64") // <68656c6c 6f2c2077 6f726c64>
    let string = String(data: data, encoding: .utf8) // "hello, world"
    

    Swift4

    func dataWithHexString(hex: String) -> Data {
        var hex = hex
        var data = Data()
        while(hex.count > 0) {
            let subIndex = hex.index(hex.startIndex, offsetBy: 2)
            let c = String(hex[..<subIndex])
            hex = String(hex[subIndex...])
            var ch: UInt32 = 0
            Scanner(string: c).scanHexInt32(&ch)
            var char = UInt8(ch)
            data.append(&char, count: 1)
        }
        return data
    }
    

    使用:

    let data = dataWithHexString(hex: "68656c6c6f2c20776f726c64") // <68656c6c 6f2c2077 6f726c64>
    let string = String(data: data, encoding: .utf8) // "hello, world"
    

    【讨论】:

      【解决方案3】:

      Swift 4 和 Swift 5 实现:

      init?(hexString: String) {
        let len = hexString.count / 2
        var data = Data(capacity: len)
        var i = hexString.startIndex
        for _ in 0..<len {
          let j = hexString.index(i, offsetBy: 2)
          let bytes = hexString[i..<j]
          if var num = UInt8(bytes, radix: 16) {
            data.append(&num, count: 1)
          } else {
            return nil
          }
          i = j
        }
        self = data
      }
      

      用法:

      let data = Data(hexString: "0a1b3c4d")
      

      【讨论】:

      • 这在 Swift 4 中不起作用。hexString[j..&lt;j+2] 没有本机下标。您可能在某处或扩展名中有一个下标
      • @Kalzem 感谢您指出这一点。是的,我有一个字符串下标的扩展名。我修改了代码以在没有它们的情况下工作。
      • 我喜欢这个解决方案。如果您通过循环记住 currentIndex 而不是总是将字符串从 .startIndex '遍历'到偏移量,它可能会更有效率。
      【解决方案4】:

      这是我的 Swifty 5 方法:

      • 确实处理“0x”前缀
      • 使用下标代替分配的 Array(),也没有 C 风格 [i+1]
      • .hexadecimal 添加到 String.data(using encoding:) -&gt; Data?

      .

      字符串扩展:

          extension String {
              enum ExtendedEncoding {
                  case hexadecimal
              }
      
              func data(using encoding:ExtendedEncoding) -> Data? {
                  let hexStr = self.dropFirst(self.hasPrefix("0x") ? 2 : 0)
      
                  guard hexStr.count % 2 == 0 else { return nil }
      
                  var newData = Data(capacity: hexStr.count/2)
      
                  var indexIsEven = true
                  for i in hexStr.indices {
                      if indexIsEven {
                          let byteRange = i...hexStr.index(after: i)
                          guard let byte = UInt8(hexStr[byteRange], radix: 16) else { return nil }
                          newData.append(byte)
                      }
                      indexIsEven.toggle()
                  }
                  return newData
              }
          }
      

      用法:

          "5413".data(using: .hexadecimal)
          "0x1234FF".data(using: .hexadecimal)
      

      测试:

          extension Data {
              var bytes:[UInt8] { // fancy pretty call: myData.bytes -> [UInt8]
                  return [UInt8](self)
              }
      
              // Could make a more optimized one~
              func hexa(prefixed isPrefixed:Bool = true) -> String {
                  return self.bytes.reduce(isPrefixed ? "0x" : "") { $0 + String(format: "%02X", $1) }
              }
          }
      
          print("000204ff5400".data(using: .hexadecimal)?.hexa() ?? "failed") // OK
          print("0x000204ff5400".data(using: .hexadecimal)?.hexa() ?? "failed") // OK
          print("541".data(using: .hexadecimal)?.hexa() ?? "failed") // fails
          print("5413".data(using: .hexadecimal)?.hexa() ?? "failed") // OK
      

      【讨论】:

        【解决方案5】:

        斯威夫特 5

        extension Data {
            init?(hex: String) {
                guard hex.count.isMultiple(of: 2) else {
                    return nil
                }
                
                let chars = hex.map { $0 }
                let bytes = stride(from: 0, to: chars.count, by: 2)
                    .map { String(chars[$0]) + String(chars[$0 + 1]) }
                    .compactMap { UInt8($0, radix: 16) }
                
                guard hex.count / bytes.count == 2 else { return nil }
                self.init(bytes)
            }
        }
        

        【讨论】:

          【解决方案6】:

          这是我确定的一个简单解决方案:

          extension NSData {
              public convenience init(hexString: String) {
                  var index = hexString.startIndex
                  var bytes: [UInt8] = []
                  repeat {
                      bytes.append(hexString[index...index.advancedBy(1)].withCString {
                          return UInt8(strtoul($0, nil, 16))
                      })
          
                      index = index.advancedBy(2)
                  } while index.distanceTo(hexString.endIndex) != 0
          
                  self.init(bytes: &bytes, length: bytes.count)
              }
          }
          

          用法:

          let data = NSData(hexString: "b8dfb080bc33fb564249e34252bf143d88fc018f")
          

          输出:

          print(data)
          >>> <b8dfb080 bc33fb56 4249e342 52bf143d 88fc018f>
          

          2016 年 6 月 29 日更新

          我更新了初始化程序以处理格式错误的数据(即无效字符或奇数个字符)。

          public convenience init?(hexString: String, force: Bool) {
              let characterSet = NSCharacterSet(charactersInString: "0123456789abcdefABCDEF")
              for scalar in hexString.unicodeScalars {
                  if characterSet.characterIsMember(UInt16(scalar.value)) {
                      hexString.append(scalar)
                  }
                  else if !force {
                      return nil
                  }
              }
          
              if hexString.characters.count % 2 == 1 {
                  if force {
                      hexString = "0" + hexString
                  }
                  else {
                      return nil
                  }
              }
          
              var index = hexString.startIndex
              var bytes: [UInt8] = []
              repeat {
                  bytes.append(hexString[index...index.advancedBy(1)].withCString {
                      return UInt8(strtoul($0, nil, 16))
                      })
          
                  index = index.advancedBy(2)
              } while index.distanceTo(hexString.endIndex) != 0
          
              self.init(bytes: &bytes, length: bytes.count)
          }
          

          【讨论】:

            【解决方案7】:

            这是我对使用 Swift 4 将十六进制字符串转换为数据的看法:

            extension Data {
                private static let hexRegex = try! NSRegularExpression(pattern: "^([a-fA-F0-9][a-fA-F0-9])*$", options: [])
            
                init?(hexString: String) {
                    if Data.hexRegex.matches(in: hexString, range: NSMakeRange(0, hexString.count)).isEmpty {
                        return nil // does not look like a hexadecimal string
                    }
            
                    let chars = Array(hexString)
            
                    let bytes: [UInt8] = 
                        stride(from: 0, to: chars.count, by: 2)
                            .map {UInt8(String([chars[$0], chars[$0+1]]), radix: 16)}
                            .compactMap{$0}
            
                    self = Data(bytes)
                }
            
                var hexString: String {
                    return map { String(format: "%02hhx", $0) }.joined()
                }
            }
            

            (我添加了一个小功能来转换back to hex string I found in this answer

            下面是你将如何使用它:

                let data = Data(hexString: "cafecafe")
            
                print(data?.hexString) // will print Optional("cafecafe")
            

            【讨论】:

              【解决方案8】:

              另一个易于遵循并利用 swifts 内置十六进制解析的解决方案

              func convertHexToBytes(_ str: String) -> Data? {
                  let values = str.compactMap { $0.hexDigitValue } // map char to value of 0-15 or nil
                  if values.count == str.count && values.count % 2 == 0 {
                      var data = Data()
                      for x in stride(from: 0, to: values.count, by: 2) {
                        let byte = (values[x] << 4) + values[x+1] // concat high and low bits
                        data.append(UInt8(byte))
                      }
                      return data
                  }
                  return nil
              }
              
              let good = "e01AFd"
              let bad = "e0671"
              let ugly = "GT40"
              print("\(convertHexToBytes(good))") // Optional(3 bytes)
              print("\(convertHexToBytes(bad))") // nil
              print("\(convertHexToBytes(ugly))") // nil
              

              【讨论】:

                【解决方案9】:

                代码在 Swift 3.0.2 中为我工作。

                extension String {
                    /// Expanded encoding
                    ///
                    /// - bytesHexLiteral: Hex string of bytes
                    /// - base64: Base64 string
                    enum ExpandedEncoding {
                        /// Hex string of bytes
                        case bytesHexLiteral
                        /// Base64 string
                        case base64
                    }
                
                    /// Convert to `Data` with expanded encoding
                    ///
                    /// - Parameter encoding: Expanded encoding
                    /// - Returns: data
                    func data(using encoding: ExpandedEncoding) -> Data? {
                        switch encoding {
                        case .bytesHexLiteral:
                            guard self.characters.count % 2 == 0 else { return nil }
                            var data = Data()
                            var byteLiteral = ""
                            for (index, character) in self.characters.enumerated() {
                                if index % 2 == 0 {
                                    byteLiteral = String(character)
                                } else {
                                    byteLiteral.append(character)
                                    guard let byte = UInt8(byteLiteral, radix: 16) else { return nil }
                                    data.append(byte)
                                }
                            }
                            return data
                        case .base64:
                            return Data(base64Encoded: self)
                        }
                    }
                }
                

                【讨论】:

                  【解决方案10】:

                  斯威夫特 5

                  支持 iOS 13iOS2...iOS12

                  extension String {
                    var hex: Data? {
                      var value = self
                      var data = Data()
                      
                      while value.count > 0 {
                        let subIndex = value.index(value.startIndex, offsetBy: 2)
                        let c = String(value[..<subIndex])
                        value = String(value[subIndex...])
                        
                        var char: UInt8
                        if #available(iOS 13.0, *) {
                          guard let int = Scanner(string: c).scanInt32(representation: .hexadecimal) else { return nil }
                          char = UInt8(int)
                        } else {
                          var int: UInt32 = 0
                          Scanner(string: c).scanHexInt32(&int)
                          char = UInt8(int)
                        }
                        
                        data.append(&char, count: 1)
                      }
                      
                      return data
                    }
                  }
                  

                  【讨论】:

                    【解决方案11】:

                    假设你的字符串是偶数大小,你可以用它来转换为十六进制并保存到数据中:

                    Swift 5.2

                    func hex(from string: String) -> Data {
                        .init(stride(from: 0, to: string.count, by: 2).map {
                            string[string.index(string.startIndex, offsetBy: $0) ... string.index(string.startIndex, offsetBy: $0 + 1)]
                        }.map {
                            UInt8($0, radix: 16)!
                        })
                    }
                    

                    【讨论】:

                    • 这将在每次迭代时不必要地从 startIndex 偏移。 stackoverflow.com/a/43360864/2303865
                    • 上述代码在执行偏移时崩溃,请参阅上面来自@LeoDabus 的堆栈链接,这是正确且稳定的答案。
                    猜你喜欢
                    • 2013-05-07
                    • 2013-12-01
                    • 2011-11-23
                    • 2012-01-06
                    • 1970-01-01
                    • 2018-01-31
                    • 2018-01-22
                    • 1970-01-01
                    相关资源
                    最近更新 更多