@@ -74,59 +74,149 @@ extension StringProtocol {
7474
7575// MARK: - FIUUID
7676
77- /// A simple UUID v4 implementation without Foundation.
78- /// Generates a random 128-bit UUID compliant with RFC 4122.
77+ /// A lightweight pseudo- UUID,採用兩個 UInt64 儲存 128 位元識別值,以維持標準 UUID 字串格式且降低陣列開銷。
78+ @ frozen
7979public struct FIUUID : Hashable , Codable , Sendable {
8080 // MARK: Lifecycle
8181
8282 public init ( ) {
83- var rng = SystemRandomNumberGenerator ( )
84- self . bytes = Self . randomBytes ( count: 16 , using: & rng)
85- // Set version to 4
86- bytes [ 6 ] = ( bytes [ 6 ] & 0x0F ) | 0x40
87- // Set variant to 1 (RFC 4122)
88- bytes [ 8 ] = ( bytes [ 8 ] & 0x3F ) | 0x80
83+ self . highBits = UInt64 . random ( in: UInt64 . min ... UInt64 . max)
84+ self . lowBits = UInt64 . random ( in: UInt64 . min ... UInt64 . max)
85+ }
86+
87+ public init ( highBits: UInt64 , lowBits: UInt64 ) {
88+ self . highBits = highBits
89+ self . lowBits = lowBits
90+ }
91+
92+ /// 與先前 64 位元版本相容:會將值儲存在低位元,並將高位元清零。
93+ public init ( rawValue: UInt64 ) {
94+ self . highBits = 0
95+ self . lowBits = rawValue
96+ }
97+
98+ public init ( from decoder: any Decoder ) throws {
99+ let container = try decoder. singleValueContainer ( )
100+
101+ if let stringValue = try ? container. decode ( String . self) {
102+ guard let decoded = FIUUID ( uuidString: stringValue) else {
103+ throw DecodingError . dataCorruptedError (
104+ in: container,
105+ debugDescription: " Invalid UUID string for FIUUID. "
106+ )
107+ }
108+ self = decoded
109+ return
110+ }
111+
112+ if let pair = try ? container. decode ( [ UInt64 ] . self) , pair. count == 2 {
113+ self . init ( highBits: pair [ 0 ] , lowBits: pair [ 1 ] )
114+ return
115+ }
116+
117+ if let legacyRaw = try ? container. decode ( UInt64 . self) {
118+ self . init ( rawValue: legacyRaw)
119+ return
120+ }
121+
122+ throw DecodingError . dataCorruptedError (
123+ in: container,
124+ debugDescription: " Unsupported FIUUID encoding. "
125+ )
126+ }
127+
128+ /// 依照 UUID 字串初始化識別值,支援大小寫。
129+ public init ? ( uuidString: String ) {
130+ let filtered = uuidString. filter { $0 != " - " }
131+ guard filtered. count == 32 else { return nil }
132+
133+ var bytes : [ UInt8 ] = [ ]
134+ bytes. reserveCapacity ( 16 )
135+
136+ var index = filtered. startIndex
137+ for _ in 0 ..< 16 {
138+ let highChar = filtered [ index]
139+ index = filtered. index ( after: index)
140+ let lowChar = filtered [ index]
141+ index = filtered. index ( after: index)
142+
143+ guard let high = Self . hexValue ( of: highChar) ,
144+ let low = Self . hexValue ( of: lowChar)
145+ else { return nil }
146+
147+ bytes. append ( ( high << 4 ) | low)
148+ }
149+
150+ self . init ( bytes: bytes)
151+ }
152+
153+ private init ( bytes: [ UInt8 ] ) {
154+ self . highBits = Self . readBigEndian ( from: bytes, offset: 0 )
155+ self . lowBits = Self . readBigEndian ( from: bytes, offset: 8 )
89156 }
90157
91158 // MARK: Public
92159
93- /// Returns the UUID as a standard hyphenated string (e.g., "123e4567-e89b-12d3-a456-426614174000").
94- public func uuidString( ) -> String {
95- let hexDigits = bytes. map { byte in
96- let hex = String ( byte, radix: 16 )
97- return hex. count == 1 ? " 0 " + hex : hex
160+ /// 高 64 位元與低 64 位元的原始識別值。
161+ public let highBits : UInt64
162+ public let lowBits : UInt64
163+
164+ /// 以標準 UUID 字串形式返回識別值(預設為大寫)。
165+ public func uuidString( uppercase: Bool = true ) -> String {
166+ var bytes = [ UInt8] ( repeating: 0 , count: 16 )
167+ Self . write ( bigEndian: highBits, to: & bytes, offset: 0 )
168+ Self . write ( bigEndian: lowBits, to: & bytes, offset: 8 )
169+
170+ let hexDigits = uppercase ? Self . upperHexDigits : Self . lowerHexDigits
171+ var characters : [ Character ] = [ ]
172+ characters. reserveCapacity ( 36 )
173+
174+ for index in 0 ..< bytes. count {
175+ if index == 4 || index == 6 || index == 8 || index == 10 {
176+ characters. append ( " - " )
177+ }
178+ let byte = bytes [ index]
179+ characters. append ( hexDigits [ Int ( byte >> 4 ) ] )
180+ characters. append ( hexDigits [ Int ( byte & 0x0F ) ] )
98181 }
99- let hexString = hexDigits . joined ( )
100- let part1 = hexString . prefix ( 8 )
101- let part2 = hexString . dropFirst ( 8 ) . prefix ( 4 )
102- let part3 = hexString . dropFirst ( 12 ) . prefix ( 4 )
103- let part4 = hexString . dropFirst ( 16 ) . prefix ( 4 )
104- let part5 = hexString . suffix ( 12 )
105- return " \( part1 ) - \( part2 ) - \( part3 ) - \( part4 ) - \( part5 ) "
182+
183+ return String ( characters )
184+ }
185+
186+ public func encode ( to encoder : any Encoder ) throws {
187+ var container = encoder . singleValueContainer ( )
188+ try container . encode ( uuidString ( ) )
106189 }
107190
108191 // MARK: Private
109192
110- private var bytes : [ UInt8 ]
111-
112- private static func randomBytes(
113- count: Int ,
114- using rng: inout SystemRandomNumberGenerator
115- )
116- -> [ UInt8 ] {
117- var result = [ UInt8] ( repeating: 0 , count: count)
118- var offset = 0
119- while offset < count {
120- let randomValue = rng. next ( )
121- withUnsafeBytes ( of: randomValue) { buffer in
122- let bytesToCopy = min ( 8 , count - offset)
123- let byteCount = min ( bytesToCopy, buffer. count)
124- for i in 0 ..< byteCount {
125- result [ offset + i] = buffer [ i]
126- }
127- offset += byteCount
128- }
193+ private static let upperHexDigits : [ Character ] = Array ( " 0123456789ABCDEF " )
194+ private static let lowerHexDigits : [ Character ] = Array ( " 0123456789abcdef " )
195+
196+ private static func write( bigEndian value: UInt64 , to buffer: inout [ UInt8 ] , offset: Int ) {
197+ for index in 0 ..< 8 {
198+ buffer [ offset + index] = UInt8 ( truncatingIfNeeded: value >> ( 8 * ( 7 - index) ) )
199+ }
200+ }
201+
202+ private static func readBigEndian( from buffer: [ UInt8 ] , offset: Int ) -> UInt64 {
203+ var result : UInt64 = 0
204+ for index in 0 ..< 8 {
205+ result = ( result << 8 ) | UInt64 ( buffer [ offset + index] )
129206 }
130207 return result
131208 }
209+
210+ private static func hexValue( of character: Character ) -> UInt8 ? {
211+ switch character {
212+ case " 0 " ... " 9 " :
213+ return UInt8 ( character. unicodeScalars. first!. value - Unicode. Scalar ( " 0 " ) . value)
214+ case " a " ... " f " :
215+ return UInt8 ( character. unicodeScalars. first!. value - Unicode. Scalar ( " a " ) . value + 10 )
216+ case " A " ... " F " :
217+ return UInt8 ( character. unicodeScalars. first!. value - Unicode. Scalar ( " A " ) . value + 10 )
218+ default :
219+ return nil
220+ }
221+ }
132222}
0 commit comments