-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add RequiredKeysCache and tests * Add global caches * Add property wrappers and tests
- Loading branch information
Showing
11 changed files
with
617 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
extension RequiredKeysCache { | ||
/** | ||
Accesses the value associated with the given required key for reading and writing, optionally using a default value if the key is missing. | ||
|
||
- Parameters: | ||
- requiredKey: The required key to retrieve the value for. | ||
- default: The default value to be returned if the key is missing. | ||
- Returns: The value stored in the cache for the given key, or the default value if it doesn't exist. | ||
*/ | ||
public subscript(requiredKey key: Key) -> Value { | ||
get { | ||
resolve(requiredKey: key, as: Value.self) | ||
} | ||
set(newValue) { | ||
set(value: newValue, forKey: key) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
/// The `RequiredKeysCache` class is a subclass of `Cache` that allows you to define a set of required keys. This cache ensures that the required keys are always present and throws an error if any of them are missing. | ||
public class RequiredKeysCache<Key: Hashable, Value>: Cache<Key, Value> { | ||
|
||
/// The set of keys that must always be present in the cache. | ||
public var requiredKeys: Set<Key> { | ||
didSet { | ||
for key in requiredKeys { | ||
_ = resolve(requiredKey: key) | ||
} | ||
} | ||
} | ||
|
||
/** | ||
Initializes a new instance of `RequiredKeysCache` with the specified required keys and initial values. | ||
|
||
- Parameters: | ||
- requiredKeys: A set of keys that must always be present in the cache. | ||
- initialValues: A dictionary of initial key-value pairs to populate the cache. | ||
*/ | ||
public init( | ||
requiredKeys: Set<Key>, | ||
initialValues: [Key: Value] | ||
) { | ||
self.requiredKeys = requiredKeys | ||
|
||
super.init(initialValues: initialValues) | ||
|
||
do { | ||
_ = try require(keys: requiredKeys) | ||
} | ||
|
||
catch { fatalError(error.localizedDescription) } | ||
} | ||
|
||
/** | ||
Initializes a new instance of `RequiredKeysCache` with the specified initial values. The required keys are automatically inferred from the initial values. | ||
|
||
- Parameters: | ||
- initialValues: A dictionary of initial key-value pairs to populate the cache. The keys are considered as required keys. | ||
*/ | ||
public required convenience init(initialValues: [Key: Value] = [:]) { | ||
self.init(requiredKeys: Set(initialValues.keys), initialValues: initialValues) | ||
} | ||
|
||
/** | ||
Removes a key-value pair from the cache. If the key is one of the required keys, it is not removed. | ||
|
||
- Parameters: | ||
- key: The key of the value to remove. | ||
*/ | ||
public override func remove(_ key: Key) { | ||
guard requiredKeys.contains(key) == false else { return } | ||
|
||
super.remove(key) | ||
} | ||
|
||
/** | ||
Resolves a required key from the cache, ensuring its presence and returning its value. | ||
|
||
- Parameters: | ||
- requiredKey: The required key to resolve. | ||
- as: The type to expect as the value of the key. The default is `Output.self`. | ||
|
||
- Returns: The resolved value for the required key. | ||
- Throws: A runtime error if the required key is not present in the cache or if the expected value type is incorrect. | ||
*/ | ||
public func resolve<Output>(requiredKey: Key, as: Output.Type = Output.self) -> Output { | ||
guard | ||
requiredKeys.contains(requiredKey) | ||
else { fatalError("The key '\(requiredKey)' is not a Required Key.") } | ||
|
||
guard | ||
contains(requiredKey) | ||
else { fatalError("Required Key Missing: '\(requiredKey)'") } | ||
|
||
do { | ||
return try resolve(requiredKey, as: Output.self) | ||
} | ||
|
||
catch { fatalError(error.localizedDescription) } | ||
} | ||
|
||
/** | ||
Resolves a required key from the cache, ensuring its presence and returning its value. | ||
|
||
- Parameter requiredKey: The required key to resolve. | ||
|
||
- Returns: The resolved value for the required key. | ||
*/ | ||
public func resolve(requiredKey: Key) -> Value { | ||
resolve(requiredKey: requiredKey, as: Value.self) | ||
} | ||
|
||
/** | ||
Updates the value of a required key in the cache using a closure. | ||
|
||
- Parameters: | ||
- requiredKey: The required key to update. | ||
- as: The type to expect as the value of the key. The default is `CacheValue.self`. | ||
- block: A closure that takes the current value of the required key and returns the new value. | ||
|
||
- Returns: The updated value for the required key. | ||
*/ | ||
@discardableResult | ||
public func update<CacheValue>( | ||
requiredKey key: Key, | ||
as: CacheValue.Type = CacheValue.self, | ||
block: (CacheValue) -> Value | ||
) -> Value { | ||
let newValue = block(resolve(requiredKey: key, as: CacheValue.self)) | ||
|
||
set(value: newValue, forKey: key) | ||
|
||
return newValue | ||
} | ||
|
||
/** | ||
Updates the value of a required key in the cache using a closure. | ||
|
||
- Parameters: | ||
- requiredKey: The required key to update. | ||
- block: A closure that takes the current value of the required key and returns the new value. | ||
|
||
- Returns: The updated value for the required key. | ||
*/ | ||
@discardableResult | ||
public func update( | ||
requiredKey key: Key, | ||
block: (Value) -> Value | ||
) -> Value { | ||
update( | ||
requiredKey: key, | ||
as: Value.self, | ||
block: block | ||
) | ||
} | ||
|
||
/** | ||
Uses the value of a required key from the cache in a closure and returns a result. | ||
|
||
- Parameters: | ||
- requiredKey: The required key to use. | ||
- as: The type to expect as the value of the key. The default is `CacheValue.self`. | ||
- block: A closure that takes the value of the required key and returns a result. | ||
|
||
- Returns: The result of the closure evaluation. | ||
*/ | ||
@discardableResult | ||
public func use<CacheValue, Output>( | ||
requiredKey key: Key, | ||
as: CacheValue.Type = CacheValue.self, | ||
block: (CacheValue) -> Output? | ||
) -> Output? { | ||
block(resolve(requiredKey: key, as: CacheValue.self)) | ||
} | ||
|
||
/** | ||
Uses the value of a required key from the cache in a closure. | ||
|
||
- Parameters: | ||
- requiredKey: The required key to use. | ||
- as: The type to expect as the value of the key. The default is `CacheValue.self`. | ||
- block: A closure that takes the value of the required key. | ||
|
||
*/ | ||
public func use<CacheValue>( | ||
requiredKey key: Key, | ||
as: CacheValue.Type = CacheValue.self, | ||
block: (CacheValue) -> Void | ||
) { | ||
block(resolve(requiredKey: key, as: CacheValue.self)) | ||
} | ||
|
||
/** | ||
Uses the value of a required key from the cache in a closure and returns a result. | ||
|
||
- Parameters: | ||
- requiredKey: The required key to use. | ||
- block: A closure that takes the value of the required key and returns a result. | ||
|
||
- Returns: The result of the closure evaluation. | ||
*/ | ||
@discardableResult | ||
public func use<Output>( | ||
requiredKey key: Key, | ||
block: (Value) -> Output? | ||
) -> Output? { | ||
use( | ||
requiredKey: key, | ||
as: Value.self, | ||
block: block | ||
) | ||
} | ||
|
||
/** | ||
Uses the value of a required key from the cache in a closure. | ||
|
||
- Parameters: | ||
- requiredKey: The required key to use. | ||
- block: A closure that takes the value of the required key. | ||
|
||
*/ | ||
public func use( | ||
requiredKey key: Key, | ||
block: (Value) -> Void | ||
) { | ||
use( | ||
requiredKey: key, | ||
as: Value.self, | ||
block: block | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
extension Global { | ||
/// The global cache for storing values. | ||
public static var cache: Cache<AnyHashable, Any> = Cache() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
extension Global { | ||
/// The global cache for storing required dependencies. | ||
public static var dependencies: RequiredKeysCache<AnyHashable, Any> = RequiredKeysCache() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
#if canImport(SwiftUI) | ||
import SwiftUI | ||
|
||
extension Global { | ||
#if os(macOS) | ||
/// A typealias for `NSImage`. | ||
public typealias CacheImage = NSImage | ||
#else | ||
/// A typealias for `UIImage`. | ||
public typealias CacheImage = UIImage | ||
#endif | ||
|
||
/// The global cache for storing images. | ||
public static var images: Cache<URL, CacheImage> = Cache() | ||
} | ||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/// The `Global` enum is a container for various global properties or caches used within the application. | ||
public enum Global { } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/** | ||
The `Cached` property wrapper provides a convenient way to access values from a cache. It allows you to specify a key, cache instance, and a default value. The property wrapper ensures that the value is always retrieved from the cache and provides type safety for accessing the value. | ||
|
||
Usage: | ||
```swift | ||
@Cached(key: "myKey", cache: myCache, defaultValue: 0) | ||
var myValue: Int | ||
|
||
// Accessing the value | ||
let currentValue = myValue | ||
|
||
// Updating the value | ||
myValue = 42 | ||
``` | ||
|
||
- Parameters: | ||
- key: The key associated with the value in the cache. | ||
- cache: The cache instance to retrieve the value from. | ||
- defaultValue: The default value to be used if the value is not present in the cache. | ||
|
||
The property wrapper provides a `wrappedValue` that can be accessed and mutated like a regular property. When accessed, the `wrappedValue` retrieves the value from the cache based on the specified key. If the value is not present in the cache, the `defaultValue` is used. When mutated, the `wrappedValue` sets the new value into the cache using the specified key. | ||
|
||
- Note: The `Cached` property wrapper relies on a cache instance that conforms to the `Cache` protocol, in order to retrieve and store the values efficiently. | ||
*/ | ||
@propertyWrapper public struct Cached<Key: Hashable, Value> { | ||
/// The key associated with the value in the cache. | ||
public let key: Key | ||
|
||
/// The cache instance to retrieve the value from. | ||
public let cache: Cache<Key, Any> | ||
|
||
/// The default value to be used if the value is not present in the cache. | ||
public let defaultValue: Value | ||
|
||
/// The wrapped value that can be accessed and mutated by the property wrapper. | ||
public var wrappedValue: Value { | ||
get { | ||
cache.get(key, as: Value.self) ?? defaultValue | ||
} | ||
set { | ||
cache.set(value: newValue, forKey: key) | ||
} | ||
} | ||
|
||
/** | ||
Initializes a new instance of the `Cached` property wrapper. | ||
|
||
- Parameters: | ||
- key: The key associated with the value in the cache. | ||
- cache: The cache instance to retrieve the value from. The default is `Global.cache`. | ||
- defaultValue: The default value to be used if the value is not present in the cache. | ||
*/ | ||
public init( | ||
key: Key, | ||
using cache: Cache<Key, Any> = Global.cache, | ||
defaultValue: Value | ||
) { | ||
self.key = key | ||
self.cache = cache | ||
self.defaultValue = defaultValue | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
/** | ||
The `OptionallyCached` property wrapper provides a convenient way to optionally access values from a cache. It allows you to specify a key and a cache instance. The property wrapper ensures that the value is retrieved from the cache if present, and provides type safety for accessing the value. | ||
|
||
Usage: | ||
```swift | ||
@OptionallyCached(key: "myKey", cache: myCache) | ||
var myValue: Int? | ||
|
||
// Accessing the value | ||
let currentValue = myValue | ||
|
||
// Setting the value | ||
myValue = 42 | ||
|
||
// Removing the value from the cache | ||
myValue = nil | ||
``` | ||
|
||
- Parameters: | ||
- key: The key associated with the value in the cache. | ||
- cache: The cache instance to retrieve the value from. | ||
|
||
The property wrapper provides a `wrappedValue` that can be accessed and mutated like a regular optional property. When accessed, the `wrappedValue` retrieves the value from the cache based on the specified key. If the value is not present in the cache, `nil` is returned. When mutated, the `wrappedValue` sets the new value into the cache using the specified key. If the new value is `nil`, the key-value pair is removed from the cache. | ||
|
||
- Note: The `OptionallyCached` property wrapper relies on a cache instance that conforms to the `Cache` protocol, in order to retrieve and store the values efficiently. | ||
|
||
*/ | ||
@propertyWrapper public struct OptionallyCached<Key: Hashable, Value> { | ||
/// The key associated with the value in the cache. | ||
public let key: Key | ||
|
||
/// The cache instance to retrieve the value from. | ||
public let cache: Cache<Key, Any> | ||
|
||
/// The wrapped value that can be accessed and mutated by the property wrapper. | ||
public var wrappedValue: Value? { | ||
get { | ||
cache.get(key, as: Value.self) | ||
} | ||
set { | ||
guard let newValue else { | ||
return cache.remove(key) | ||
} | ||
|
||
cache.set(value: newValue, forKey: key) | ||
} | ||
} | ||
|
||
/** | ||
Initializes a new instance of the `OptionallyCached` property wrapper. | ||
|
||
- Parameters: | ||
- key: The key associated with the value in the cache. | ||
- cache: The cache instance to retrieve the value from. The default is `Global.cache`. | ||
*/ | ||
public init( | ||
key: Key, | ||
using cache: Cache<Key, Any> = Global.cache | ||
) { | ||
self.key = key | ||
self.cache = cache | ||
} | ||
} |
Oops, something went wrong.