@@ -41,49 +41,58 @@ public protocol CacheableValue {
41
41
var cost : Int { get }
42
42
}
43
43
44
+ // NSCache insert, remove, and lookup operations are documented as thread safe: https://developer.apple.com/documentation/foundation/nscache
45
+ fileprivate final class UnsafeNSCacheSendableWrapper < Key: Hashable , Value> {
46
+ init ( value: NSCache < KeyWrapper < Key > , ValueWrapper < Value > > ) {
47
+ self . value = value
48
+ }
49
+ let value : NSCache < KeyWrapper < Key > , ValueWrapper < Value > >
50
+ }
51
+ extension UnsafeNSCacheSendableWrapper : @unchecked Sendable where Key: Sendable , Value: Sendable { }
52
+
44
53
/// A thread-safe cache container.
45
54
///
46
55
/// This container will automatically evict entries when the system is under memory pressure.
47
56
public final class Cache < Key: Hashable , Value> : NSObject , KeyValueStorage , NSCacheDelegate {
48
57
/// The underlying cache implementation.
49
- private let cache : NSCache < KeyWrapper < Key > , ValueWrapper < Value > >
58
+ private let cache : UnsafeNSCacheSendableWrapper < Key , Value >
50
59
private let willEvictCallback : ( @Sendable ( Value ) -> Void ) ?
51
60
52
61
public init ( willEvictCallback: ( @Sendable ( Value ) -> Void ) ? = nil , totalCostLimit: Int ? = nil ) {
53
- self . cache = NSCache ( )
62
+ self . cache = . init ( value : NSCache ( ) )
54
63
self . willEvictCallback = willEvictCallback
55
64
super. init ( )
56
65
if let totalCostLimit {
57
- self . cache. totalCostLimit = totalCostLimit
66
+ self . cache. value . totalCostLimit = totalCostLimit
58
67
}
59
- self . cache. delegate = self
68
+ self . cache. value . delegate = self
60
69
}
61
70
62
71
/// Remove all objects in the cache.
63
72
public func removeAll( ) {
64
- cache. removeAllObjects ( )
73
+ cache. value . removeAllObjects ( )
65
74
}
66
75
67
76
/// Remove the entry for a given `key`.
68
77
public func remove( _ key: Key ) {
69
- cache. removeObject ( forKey: KeyWrapper ( key) )
78
+ cache. value . removeObject ( forKey: KeyWrapper ( key) )
70
79
}
71
80
72
81
/// Subscript access to the cache.
73
82
public subscript( _ key: Key ) -> Value ? {
74
83
get {
75
- if let wrappedValue = cache. object ( forKey: KeyWrapper ( key) ) {
84
+ if let wrappedValue = cache. value . object ( forKey: KeyWrapper ( key) ) {
76
85
return wrappedValue. value
77
86
}
78
87
return nil
79
88
}
80
89
set {
81
90
if let newValue, let cacheableValue = newValue as? ( any CacheableValue ) {
82
- cache. setObject ( ValueWrapper ( newValue) , forKey: KeyWrapper ( key) , cost: cacheableValue. cost)
91
+ cache. value . setObject ( ValueWrapper ( newValue) , forKey: KeyWrapper ( key) , cost: cacheableValue. cost)
83
92
} else if let newValue = newValue {
84
- cache. setObject ( ValueWrapper ( newValue) , forKey: KeyWrapper ( key) )
93
+ cache. value . setObject ( ValueWrapper ( newValue) , forKey: KeyWrapper ( key) )
85
94
} else {
86
- cache. removeObject ( forKey: KeyWrapper ( key) )
95
+ cache. value . removeObject ( forKey: KeyWrapper ( key) )
87
96
}
88
97
}
89
98
}
@@ -98,16 +107,16 @@ public final class Cache<Key: Hashable, Value>: NSObject, KeyValueStorage, NSCac
98
107
/// NOTE: The cache *does not* ensure that the block to compute a value is only called once for a given key. In race conditions, it may be called multiple times, even though only the last computed result will be used.
99
108
public func getOrInsert( _ key: Key , _ body: ( ) throws -> Value ) rethrows -> Value {
100
109
let wrappedKey = KeyWrapper ( key)
101
- if let wrappedValue = cache. object ( forKey: wrappedKey) {
110
+ if let wrappedValue = cache. value . object ( forKey: wrappedKey) {
102
111
return wrappedValue. value
103
112
}
104
113
105
114
let value = try body ( )
106
115
if let cacheableValue = value as? ( any CacheableValue ) {
107
- cache. setObject ( ValueWrapper ( value) , forKey: wrappedKey, cost: cacheableValue. cost)
116
+ cache. value . setObject ( ValueWrapper ( value) , forKey: wrappedKey, cost: cacheableValue. cost)
108
117
109
118
} else {
110
- cache. setObject ( ValueWrapper ( value) , forKey: wrappedKey)
119
+ cache. value . setObject ( ValueWrapper ( value) , forKey: wrappedKey)
111
120
}
112
121
return value
113
122
}
0 commit comments