Skip to content

Commit fab6910

Browse files
committed
Make ZippyJSONDecoder thread-safe and conform to Sendable
1 parent 70bce15 commit fab6910

File tree

1 file changed

+108
-27
lines changed

1 file changed

+108
-27
lines changed

Sources/ZippyJSON/ZippyJSONDecoder.swift

Lines changed: 108 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//Copyright (c) 2018 Michael Eisel. All rights reserved.
22

3+
import os
34
import Foundation
45
import ZippyJSONCFamily
56
import JJLISO8601DateFormatter
@@ -37,19 +38,15 @@ func isOnSimulator() -> Bool {
3738
#endif
3839
}
3940

40-
public final class ZippyJSONDecoder {
41+
public final class ZippyJSONDecoder: Sendable {
4142
@available(*, deprecated, message: "This flag is deprecated because full-precision parsing speed is now on par with imprecise, so it will just always use full-precision")
42-
public var zjd_fullPrecisionFloatParsing = true
43+
public let zjd_fullPrecisionFloatParsing = true
44+
45+
@Synchronized
4346
private static var _zjd_suppressWarnings: Bool = false
4447
public static var zjd_suppressWarnings: Bool {
45-
get {
46-
return _zjd_suppressWarnings
47-
}
48-
set {
49-
objc_sync_enter(self)
50-
defer { objc_sync_exit(self) }
51-
_zjd_suppressWarnings = newValue
52-
}
48+
get { _zjd_suppressWarnings }
49+
set { _zjd_suppressWarnings = newValue }
5350
}
5451

5552
private func createContext(string: UnsafePointer<Int8>, length: Int) -> ContextPointer {
@@ -167,42 +164,66 @@ public final class ZippyJSONDecoder {
167164
}
168165
}
169166

167+
@Synchronized
168+
private var _userInfo: [CodingUserInfoKey : Any] = [:]
169+
public var userInfo: [CodingUserInfoKey : Any] {
170+
get { _userInfo }
171+
set { _userInfo = newValue }
172+
}
170173

171-
public var userInfo: [CodingUserInfoKey : Any] = [:]
172-
173-
public var nonConformingFloatDecodingStrategy: NonConformingFloatDecodingStrategy
174+
@Synchronized
175+
private var _nonConformingFloatDecodingStrategy: NonConformingFloatDecodingStrategy
176+
public var nonConformingFloatDecodingStrategy: NonConformingFloatDecodingStrategy {
177+
get { _nonConformingFloatDecodingStrategy }
178+
set { _nonConformingFloatDecodingStrategy = newValue }
179+
}
174180

175-
public enum NonConformingFloatDecodingStrategy {
181+
public enum NonConformingFloatDecodingStrategy: Sendable {
176182
case `throw`
177183
case convertFromString(positiveInfinity: String, negativeInfinity: String, nan: String)
178184
}
179185

180-
public var dataDecodingStrategy: DataDecodingStrategy
186+
@Synchronized
187+
private var _dataDecodingStrategy: DataDecodingStrategy
188+
public var dataDecodingStrategy: DataDecodingStrategy {
189+
get { _dataDecodingStrategy }
190+
set { _dataDecodingStrategy = newValue }
191+
}
181192

182-
public enum DataDecodingStrategy {
193+
public enum DataDecodingStrategy: Sendable {
183194
case deferredToData
184195
case base64
185-
case custom((Decoder) throws -> Data)
196+
case custom(@Sendable (Decoder) throws -> Data)
186197
}
187198

188-
public enum KeyDecodingStrategy {
199+
public enum KeyDecodingStrategy: Sendable {
189200
case useDefaultKeys
190201
case convertFromSnakeCase
191-
case custom(([CodingKey]) -> CodingKey)
202+
case custom(@Sendable ([CodingKey]) -> CodingKey)
192203
}
193204

194-
public var keyDecodingStrategy: KeyDecodingStrategy
205+
@Synchronized
206+
private var _keyDecodingStrategy: KeyDecodingStrategy
207+
public var keyDecodingStrategy: KeyDecodingStrategy {
208+
get { _keyDecodingStrategy }
209+
set { _keyDecodingStrategy = newValue }
210+
}
195211

196-
public enum DateDecodingStrategy {
212+
public enum DateDecodingStrategy: Sendable {
197213
case deferredToDate
198214
case secondsSince1970
199215
case millisecondsSince1970
200216
case iso8601
201217
case formatted(DateFormatter)
202-
case custom((Decoder) throws -> Date)
218+
case custom(@Sendable (Decoder) throws -> Date)
203219
}
204220

205-
public var dateDecodingStrategy: DateDecodingStrategy
221+
@Synchronized
222+
private var _dateDecodingStrategy: DateDecodingStrategy
223+
public var dateDecodingStrategy: DateDecodingStrategy {
224+
get { _dateDecodingStrategy }
225+
set { _dateDecodingStrategy = newValue }
226+
}
206227

207228
var convertCase: Bool {
208229
get {
@@ -216,10 +237,10 @@ public final class ZippyJSONDecoder {
216237
}
217238

218239
public init() {
219-
keyDecodingStrategy = .useDefaultKeys
220-
dataDecodingStrategy = .base64
221-
dateDecodingStrategy = .deferredToDate
222-
nonConformingFloatDecodingStrategy = .throw
240+
_keyDecodingStrategy = .useDefaultKeys
241+
_dataDecodingStrategy = .base64
242+
_dateDecodingStrategy = .deferredToDate
243+
_nonConformingFloatDecodingStrategy = .throw
223244
}
224245
}
225246

@@ -1057,3 +1078,63 @@ extension __JSONDecoder : SingleValueDecodingContainer {
10571078

10581079
// End
10591080
}
1081+
1082+
@propertyWrapper
1083+
private final class Synchronized<Value>: @unchecked Sendable {
1084+
private let lock: LockProtocol
1085+
private var _wrappedValue: Value
1086+
1087+
init(wrappedValue: Value) {
1088+
self.lock = Lock()
1089+
_wrappedValue = wrappedValue
1090+
}
1091+
1092+
var wrappedValue: Value {
1093+
get {
1094+
lock.withLock {
1095+
_wrappedValue
1096+
}
1097+
}
1098+
set {
1099+
lock.withLock {
1100+
_wrappedValue = newValue
1101+
}
1102+
}
1103+
}
1104+
}
1105+
1106+
private final class Lock: LockProtocol {
1107+
let innerLock: LockProtocol
1108+
1109+
init() {
1110+
// Use the lighter-weight `OSAllocatedUnfairLock` if available
1111+
if #available(macOS 13.0, iOS 16.0, tvOS 16.0, watchOS 9.0, *) {
1112+
innerLock = OSAllocatedUnfairLock()
1113+
} else {
1114+
innerLock = NSLock()
1115+
}
1116+
}
1117+
1118+
func withLock<T>(_ body: () throws -> T) rethrows -> T {
1119+
return try innerLock.withLock(body)
1120+
}
1121+
}
1122+
1123+
private protocol LockProtocol {
1124+
func withLock<T>(_ body: () throws -> T) rethrows -> T
1125+
}
1126+
1127+
extension NSLock: LockProtocol {
1128+
func withLock<T>(_ body: () throws -> T) rethrows -> T {
1129+
lock()
1130+
defer { unlock() }
1131+
return try body()
1132+
}
1133+
}
1134+
1135+
@available(macOS 13.0, *)
1136+
extension OSAllocatedUnfairLock: LockProtocol {
1137+
func withLock<T>(_ body: () throws -> T) rethrows -> T {
1138+
try withLockUnchecked { _ in try body() }
1139+
}
1140+
}

0 commit comments

Comments
 (0)