/
LockIsolated.swift
117 lines (108 loc) · 3.35 KB
/
LockIsolated.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import Foundation
/// A generic wrapper for isolating a mutable value with a lock.
///
/// To asynchronously isolate a value on an actor, see ``ActorIsolated``. If you trust the
/// sendability of the underlying value, consider using ``UncheckedSendable``, instead.
@dynamicMemberLookup
public final class LockIsolated<Value>: @unchecked Sendable {
private var _value: Value
private let lock = NSRecursiveLock()
/// Initializes lock-isolated state around a value.
///
/// - Parameter value: A value to isolate with a lock.
public init(_ value: @autoclosure @Sendable () throws -> Value) rethrows {
self._value = try value()
}
public subscript<Subject: Sendable>(dynamicMember keyPath: KeyPath<Value, Subject>) -> Subject {
self.lock.sync {
self._value[keyPath: keyPath]
}
}
/// Perform an operation with isolated access to the underlying value.
///
/// Useful for modifying a value in a single transaction.
///
/// ```swift
/// // Isolate an integer for concurrent read/write access:
/// var count = LockIsolated(0)
///
/// func increment() {
/// // Safely increment it:
/// self.count.withValue { $0 += 1 }
/// }
/// ```
///
/// - Parameter operation: An operation to be performed on the the underlying value with a lock.
/// - Returns: The result of the operation.
public func withValue<T: Sendable>(
_ operation: @Sendable (inout Value) throws -> T
) rethrows -> T {
try self.lock.sync {
var value = self._value
defer { self._value = value }
return try operation(&value)
}
}
/// Overwrite the isolated value with a new value.
///
/// ```swift
/// // Isolate an integer for concurrent read/write access:
/// var count = LockIsolated(0)
///
/// func reset() {
/// // Reset it:
/// self.count.setValue(0)
/// }
/// ```
///
/// > Tip: Use ``withValue(_:)`` instead of ``setValue(_:)`` if the value being set is derived
/// > from the current value. That is, do this:
/// >
/// > ```swift
/// > self.count.withValue { $0 += 1 }
/// > ```
/// >
/// > ...and not this:
/// >
/// > ```swift
/// > self.count.setValue(self.count + 1)
/// > ```
/// >
/// > ``withValue(_:)`` isolates the entire transaction and avoids data races between reading and
/// > writing the value.
///
/// - Parameter newValue: The value to replace the current isolated value with.
public func setValue(_ newValue: @autoclosure @Sendable () throws -> Value) rethrows {
try self.lock.sync {
self._value = try newValue()
}
}
}
extension LockIsolated where Value: Sendable {
/// The lock-isolated value.
public var value: Value {
self.lock.sync {
self._value
}
}
}
@available(*, deprecated, message: "Lock isolated values should not be equatable")
extension LockIsolated: Equatable where Value: Equatable {
public static func == (lhs: LockIsolated, rhs: LockIsolated) -> Bool {
lhs.value == rhs.value
}
}
@available(*, deprecated, message: "Lock isolated values should not be hashable")
extension LockIsolated: Hashable where Value: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(self.value)
}
}
extension NSRecursiveLock {
@inlinable @discardableResult
@_spi(Internals) public func sync<R>(work: () throws -> R) rethrows -> R {
self.lock()
defer { self.unlock() }
return try work()
}
}