Last active
May 2, 2018 19:00
-
-
Save benasher44/b6c908b465cb8f5c157123b91a10fd9c to your computer and use it in GitHub Desktop.
Fast Swift implementation of UUID(uuidString:)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
extension UUID { | |
/// Ranges within a UUID string that contain non-hyphen characters | |
private static let charRanges = [ | |
(0..<8), | |
(9..<13), | |
(14..<18), | |
(19..<23), | |
(24..<36), | |
] | |
/// - Returns: Returns the UUID byte for a given UUID string character | |
private static func uuidByte(for char: CChar) -> UInt8? { | |
switch char { | |
case _ where char >= 65 && char < 71: // A-F | |
return UInt8(char).unsafeSubtracting(55) | |
case _ where char >= 97 && char < 103: // a-f | |
return UInt8(char).unsafeSubtracting(87) | |
case _ where char >= 48 && char < 58: // 0-9 | |
return UInt8(char).unsafeSubtracting(48) | |
default: | |
return nil | |
} | |
} | |
/// Translates a UUID UTF-8 string into UUID bytes | |
private static func uuid(from cStringBuffer: UnsafeBufferPointer<CChar>) -> uuid_t? { | |
// Check that hyphens are in the right places | |
guard cStringBuffer[8] == 45, | |
cStringBuffer[13] == 45, | |
cStringBuffer[18] == 45, | |
cStringBuffer[23] == 45 | |
else { return nil } | |
// Build the uuid_t | |
var uuid: uuid_t = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) | |
let success = withUnsafeMutableBytes(of: &uuid) { (uuidBytes) -> Bool in | |
var offset = 0 | |
for range in UUID.charRanges { | |
// For each sequence of non-hyphen UUID characters, construct a byte from each | |
// consecutive pair of UUID characters | |
var i = range.lowerBound | |
while i < range.upperBound { | |
guard let firstHalf = UUID.uuidByte(for: cStringBuffer[i]), | |
let secondHalf = UUID.uuidByte(for: cStringBuffer[i + 1]) else { return false } | |
uuidBytes[offset] = (firstHalf &<< 4) | secondHalf | |
i += 2 | |
offset += 1 | |
} | |
} | |
return true | |
} | |
guard success else { return nil } | |
return uuid | |
} | |
/// Initializes a `UUID` from a UTF8 C-String | |
/// | |
/// - Parameters: | |
/// - cString: Pointer the start of the C string | |
/// - validateLength: If true, we use `strlen` to validate the length of the string; otherwise | |
/// we assume the length is already the expected 36 characters | |
public init?(from cString: UnsafePointer<CChar>, validateLength: Bool) { | |
// skip validating the length or check that the length is the 36 characters in a UUID string | |
guard !validateLength || strlen(cString) == 36 else { return nil } | |
guard let uuid = UUID.uuid(from: UnsafeBufferPointer(start: cString, count: 36)) else { | |
return nil | |
} | |
self.init(uuid: uuid) | |
} | |
/// Initializes a `UUID` from a `String` | |
/// | |
/// - Parameter uuidString: The `UUID` string | |
/// - Note: This uses native `UUID` parsing | |
public init?(from uuidString: String) { | |
let cString = uuidString.utf8CString | |
// 36 characters + null terminating byte | |
guard cString.count == 37 else { return nil } | |
guard let uuid = cString.withUnsafeBufferPointer(UUID.uuid(from:)) else { return nil } | |
self.init(uuid: uuid) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
thanks for posting this.
perhaps
uuidByte(for:)
might be a little cleaner with the following, which based on my tests (compiled with -O flag, test shown below) seems to be same speed:testing code: