Asked  6 Months ago    Answers:  5   Viewed   198 times

I want the hexadecimal representation of a Data value in Swift.

Eventually I'd want to use it like this:

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

 Answers

87

A simple implementation (taken from How to hash NSString with SHA1 in Swift?, with an additional option for uppercase output) would be

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

I chose a hexEncodedString(options:) method in the style of the existing method base64EncodedString(options:).

Data conforms to the Collection protocol, therefore one can use map() to map each byte to the corresponding hex string. The %02x format prints the argument in base 16, filled up to two digits with a leading zero if necessary. The hh modifier causes the argument (which is passed as an integer on the stack) to be treated as a one byte quantity. One could omit the modifier here because $0 is an unsigned number (UInt8) and no sign-extension will occur, but it does no harm leaving it in.

The result is then joined to a single string.

Example:

let data = Data(bytes: [0, 1, 127, 128, 255])
print(data.hexEncodedString()) // 00017f80ff
print(data.hexEncodedString(options: .upperCase)) // 00017F80FF

The following implementation is faster by a factor about 50 (tested with 1000 random bytes). It is inspired to RenniePet's solution and Nick Moore's solution, but takes advantage of String(unsafeUninitializedCapacity:initializingUTF8With:) which was introduced with Swift 5.3/Xcode 12 and is available on macOS 11 and iOS 14 or newer.

This method allows to create a Swift string from UTF-8 units efficiently, without unnecessary copying or reallocations.

An alternative implementation for older macOS/iOS versions is also provided.

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)
        }
    }
}
Tuesday, June 1, 2021
 
daniel__
answered 6 Months ago
94

edit/update:

Xcode 11 • Swift 5.1 or later

extension StringProtocol {
    func distance(of element: Element) -> Int? { firstIndex(of: element)?.distance(in: self) }
    func distance<S: StringProtocol>(of string: S) -> Int? { range(of: string)?.lowerBound.distance(in: self) }
}

extension Collection {
    func distance(to index: Index) -> Int { distance(from: startIndex, to: index) }
}

extension String.Index {
    func distance<S: StringProtocol>(in string: S) -> Int { string.distance(to: self) }
}

Playground testing

let letters = "abcdefg"

let char: Character = "c"
if let distance = letters.distance(of: char) {
    print("character (char) was found at position #(distance)")   // "character c was found at position #2n"
} else {
    print("character (char) was not found")
}

let string = "cde"
if let distance = letters.distance(of: string) {
    print("string (string) was found at position #(distance)")   // "string cde was found at position #2n"
} else {
    print("string (string) was not found")
}
Wednesday, June 23, 2021
 
skrilled
answered 6 Months ago
93

WARNING, This is deprecated in iOS 8, see below for latest

DispatchQueue.global expects the DispatchQueue.GlobalQueuePriority enum, which is:

  • high
  • default
  • low
  • background

So in your case, you just write:

DispatchQueue.global(priority: .background).async(execute: { () -> Void in

})

If you want the lowest priority.

A quick check reveals, that DispatchQueue.global(priority:_) is deprecated in iOS 8.

Latest solution:

DispatchQueue.global(qos: .background).async {
            
}

Which gives you even more options to choose from:

  • background
  • utility
  • default
  • userInitiated
  • userInteractive
  • unspecified
Friday, August 13, 2021
 
uiroshan
answered 4 Months ago
64

In Swift 3 there are four Range structures:

  • "x" ..< "y"Range<T>
  • "x" ... "y"ClosedRange<T>
  • 1 ..< 5CountableRange<T>
  • 1 ... 5CountableClosedRange<T>

(The operators ..< and ... are overloaded so that if the elements are stridable (random-access iterators e.g. numbers and pointers), a Countable Range will be returned. But these operators can still return plain Ranges to satisfy the type checker.)

Since Range and ClosedRange are different structures, you cannot implicitly convert a them with each other, and thus the error.

If you want Rand to accept a ClosedRange as well as Range, you must overload it:

// accepts Rand(0 ..< 5)
func Rand(_ range: Range<UInt32>) -> Int {
    return Int(range.lowerBound + arc4random_uniform(range.upperBound - range.lowerBound))
}

// accepts Rand(1 ... 5)
func Rand(_ range: ClosedRange<UInt32>) -> Int {
    return Int(range.lowerBound + arc4random_uniform(range.upperBound + 1 - range.lowerBound))
}
Saturday, August 21, 2021
 
Sergey Kalinichenko
answered 4 Months ago
76

You can still use the Java conversion by calling the static function on java.lang.Integer:

val hexString = java.lang.Integer.toHexString(i)

And, starting with Kotlin 1.1, there is a function in the Kotlin standard library that does the conversion, too:

fun Int.toString(radix: Int): String

Returns a string representation of this Int value in the specified radix.

Note, however, that this will still be different from Integer.toHexString(), because the latter performs the unsigned conversion:

println((-50).toString(16)) // -32
println(Integer.toHexString(-50)) // ffffffce

But with experimental Kotlin unsigned types, it is now possible to get the same result from negative number unsigned conversion as with Integer.toHexString(-50):

println((-50).toUInt().toString(16)) // ffffffce
Friday, September 10, 2021
 
Thomas
answered 3 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :  
Share