@@ -41,49 +41,58 @@ public protocol CacheableValue {
4141 var cost : Int { get }
4242}
4343
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+
4453/// A thread-safe cache container.
4554///
4655/// This container will automatically evict entries when the system is under memory pressure.
4756public final class Cache < Key: Hashable , Value> : NSObject , KeyValueStorage , NSCacheDelegate {
4857 /// The underlying cache implementation.
49- private let cache : NSCache < KeyWrapper < Key > , ValueWrapper < Value > >
58+ private let cache : UnsafeNSCacheSendableWrapper < Key , Value >
5059 private let willEvictCallback : ( @Sendable ( Value ) -> Void ) ?
5160
5261 public init ( willEvictCallback: ( @Sendable ( Value ) -> Void ) ? = nil , totalCostLimit: Int ? = nil ) {
53- self . cache = NSCache ( )
62+ self . cache = . init ( value : NSCache ( ) )
5463 self . willEvictCallback = willEvictCallback
5564 super. init ( )
5665 if let totalCostLimit {
57- self . cache. totalCostLimit = totalCostLimit
66+ self . cache. value . totalCostLimit = totalCostLimit
5867 }
59- self . cache. delegate = self
68+ self . cache. value . delegate = self
6069 }
6170
6271 /// Remove all objects in the cache.
6372 public func removeAll( ) {
64- cache. removeAllObjects ( )
73+ cache. value . removeAllObjects ( )
6574 }
6675
6776 /// Remove the entry for a given `key`.
6877 public func remove( _ key: Key ) {
69- cache. removeObject ( forKey: KeyWrapper ( key) )
78+ cache. value . removeObject ( forKey: KeyWrapper ( key) )
7079 }
7180
7281 /// Subscript access to the cache.
7382 public subscript( _ key: Key ) -> Value ? {
7483 get {
75- if let wrappedValue = cache. object ( forKey: KeyWrapper ( key) ) {
84+ if let wrappedValue = cache. value . object ( forKey: KeyWrapper ( key) ) {
7685 return wrappedValue. value
7786 }
7887 return nil
7988 }
8089 set {
8190 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)
8392 } else if let newValue = newValue {
84- cache. setObject ( ValueWrapper ( newValue) , forKey: KeyWrapper ( key) )
93+ cache. value . setObject ( ValueWrapper ( newValue) , forKey: KeyWrapper ( key) )
8594 } else {
86- cache. removeObject ( forKey: KeyWrapper ( key) )
95+ cache. value . removeObject ( forKey: KeyWrapper ( key) )
8796 }
8897 }
8998 }
@@ -98,16 +107,16 @@ public final class Cache<Key: Hashable, Value>: NSObject, KeyValueStorage, NSCac
98107 /// 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.
99108 public func getOrInsert( _ key: Key , _ body: ( ) throws -> Value ) rethrows -> Value {
100109 let wrappedKey = KeyWrapper ( key)
101- if let wrappedValue = cache. object ( forKey: wrappedKey) {
110+ if let wrappedValue = cache. value . object ( forKey: wrappedKey) {
102111 return wrappedValue. value
103112 }
104113
105114 let value = try body ( )
106115 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)
108117
109118 } else {
110- cache. setObject ( ValueWrapper ( value) , forKey: wrappedKey)
119+ cache. value . setObject ( ValueWrapper ( value) , forKey: wrappedKey)
111120 }
112121 return value
113122 }
0 commit comments