Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 25 additions & 30 deletions Sources/c/c.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,21 @@ import Foundation.NSLock

public protocol Cacheable: AnyObject {
associatedtype Key: Hashable
associatedtype Value

/// Returns a Dictionary containing all the key value pairs of the cache
var allValues: [Key: Any] { get }
var allValues: [Key: Value] { get }

init(initialValues: [Key: Any])
init(initialValues: [Key: Value])

/// Get the value in the `cache` using the `key`. This returns an optional value. If the value is `nil`, that means either the value doesn't exist or the value is not able to be casted as `Value`.
func get<Value>(_ key: Key, as: Value.Type) -> Value?
func get<Output>(_ key: Key, as: Output.Type) -> Output?

/// Resolve the value in the `cache` using the `key`. This should only be used when you know the value is always in the `cache`.
func resolve<Value>(_ key: Key, as: Value.Type) throws -> Value
func resolve<Output>(_ key: Key, as: Output.Type) throws -> Output

/// Set the value in the `cache` using the `key`. This function will replace anything in the `cache` that has the same `key`.
func set<Value>(value: Value, forKey key: Key)
func set(value: Value, forKey key: Key)

/// Remove the value in the `cache` using the `key`.
func remove(_ key: Key)
Expand All @@ -30,14 +31,14 @@ public protocol Cacheable: AnyObject {
func require(_ key: Key) throws -> Self

/// Returns a Dictionary containing only the key value pairs where the value is the same type as the generic type `Value`
func valuesInCache<Value>(
ofType: Value.Type
) -> [Key: Value]
func valuesInCache<Output>(
ofType: Output.Type
) -> [Key: Output]
}

public extension Cacheable {
var allValues: [Key: Any] {
valuesInCache(ofType: Any.self)
var allValues: [Key: Value] {
valuesInCache(ofType: Value.self)
}
}

Expand Down Expand Up @@ -82,19 +83,19 @@ public enum c {
}
}

open class KeyedCache<Key: Hashable>: Cacheable {
open class Cache<Key: Hashable, Value>: Cacheable {
fileprivate var lock: NSLock
fileprivate var cache: [Key: Any]
fileprivate var cache: [Key: Value]

required public init(initialValues: [Key: Any] = [:]) {
required public init(initialValues: [Key: Value] = [:]) {
lock = NSLock()
cache = initialValues
}

open func get<Value>(_ key: Key, as: Value.Type = Value.self) -> Value? {
open func get<Output>(_ key: Key, as: Output.Type = Output.self) -> Output? {
lock.lock()
defer { lock.unlock() }
guard let value = cache[key] as? Value else {
guard let value = cache[key] as? Output else {
return nil
}

Expand All @@ -110,26 +111,26 @@ public enum c {

guard let (_, unwrappedValue) = mirror.children.first else { return nil }

guard let value = unwrappedValue as? Value else {
guard let value = unwrappedValue as? Output else {
return nil
}

return value
}

open func resolve<Value>(_ key: Key, as: Value.Type = Value.self) throws -> Value {
open func resolve<Output>(_ key: Key, as: Output.Type = Output.self) throws -> Output {
guard contains(key) else {
throw MissingRequiredKeysError(keys: [key])
}

guard let value: Value = get(key) else {
throw InvalidTypeError(expectedType: Value.self, actualValue: get(key))
guard let value: Output = get(key) else {
throw InvalidTypeError(expectedType: Output.self, actualValue: get(key))
}

return value
}

open func set<Value>(value: Value, forKey key: Key) {
open func set(value: Value, forKey key: Key) {
lock.lock()
cache[key] = value
lock.unlock()
Expand Down Expand Up @@ -160,16 +161,10 @@ public enum c {
try require(keys: [key])
}

open func valuesInCache<Value>(
ofType: Value.Type = Value.self
) -> [Key: Value] {
cache.compactMapValues { $0 as? Value }
}
}

public class Cache: KeyedCache<AnyHashable> {
required public init(initialValues: [Key: Any] = [:]) {
super.init(initialValues: initialValues)
open func valuesInCache<Output>(
ofType: Output.Type = Output.self
) -> [Key: Output] {
cache.compactMapValues { $0 as? Output }
}
}

Expand Down
24 changes: 12 additions & 12 deletions Tests/cTests/cTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ final class cTests: XCTestCase {
}

try t.expect("that a cache works") {
let cache = c.Cache()
let cache = c.Cache<AnyHashable, Any>()

cache.set(value: Double.pi, forKey: "🥧")

Expand Down Expand Up @@ -50,11 +50,11 @@ final class cTests: XCTestCase {
forKey: "cache"
)

try t.assert(isNotNil: c.get("cache", as: c.Cache.self))
try t.assert(isNotNil: c.get("cache", as: c.Cache<AnyHashable, Any>.self))
}

try t.expect("that we can update a Cache and add a new one") {
guard let cache: c.Cache = c.get("cache") else { throw t.error(description: "Could not find Cache.") }
guard let cache: c.Cache<AnyHashable, Any> = c.get("cache") else { throw t.error(description: "Could not find Cache.") }

cache.set(value: "Hello, World!", forKey: "hello")

Expand All @@ -66,15 +66,15 @@ final class cTests: XCTestCase {
c.set(value: cache, forKey: "cache")

c.set(
value: c.Cache(initialValues: ["hello": "Hi!"]),
value: c.Cache<String, String>(initialValues: ["hello": "Hi!"]),
forKey: "cache2"
)
}

try t.expect("that we can get both Caches") {
guard
let cache: c.Cache = c.get("cache"),
let cache2: c.Cache = c.get("cache2")
let cache: c.Cache<AnyHashable, Any> = c.get("cache"),
let cache2: c.Cache<String, String> = c.get("cache2")
else { throw t.error(description: "Missing a Cache.") }

try t.assert(cache.get("hello"), isEqualTo: "Hello, World!")
Expand All @@ -95,7 +95,7 @@ final class cTests: XCTestCase {
enum EnvironmentKey: Hashable {
case appID

static var appCache: c.KeyedCache<EnvironmentKey> = c.KeyedCache(
static var appCache: c.Cache<EnvironmentKey, Any> = c.Cache(
initialValues: [
.appID: "APP-ID"
]
Expand All @@ -113,7 +113,7 @@ final class cTests: XCTestCase {
let l_appID: String = try! EnvironmentKey.appCache.resolve(EnvironmentKey.appID)

// Globally Accessed Cache
let g_appID: String = try! c.resolve("\(EnvironmentKey.self)", as: c.KeyedCache<EnvironmentKey>.self).resolve(.appID)
let g_appID: String = try! c.resolve("\(EnvironmentKey.self)", as: c.Cache<EnvironmentKey, Any>.self).resolve(.appID)
}

XCTAssert(
Expand All @@ -129,12 +129,12 @@ final class cTests: XCTestCase {

try t.expect {
try t.assert(
c.resolve("someCache", as: c.Cache.self).resolve(EnvironmentKey.appID),
c.resolve("someCache", as: c.Cache<EnvironmentKey, String>.self).resolve(EnvironmentKey.appID),
isEqualTo: "!!!"
)

try t.assert(
c.resolve("\(EnvironmentKey.self)", as: c.KeyedCache<EnvironmentKey>.self).resolve(.appID),
c.resolve("\(EnvironmentKey.self)", as: c.Cache<EnvironmentKey, Any>.self).resolve(.appID),
isEqualTo: "???"
)

Expand All @@ -143,10 +143,10 @@ final class cTests: XCTestCase {
isEqualTo: "???"
)

try c.resolve("\(EnvironmentKey.self)", as: c.KeyedCache<EnvironmentKey>.self).set(value: "😎", forKey: .appID)
try c.resolve("\(EnvironmentKey.self)", as: c.Cache<EnvironmentKey, Any>.self).set(value: "😎", forKey: .appID)

try t.assert(
c.resolve("\(EnvironmentKey.self)", as: c.KeyedCache<EnvironmentKey>.self).resolve(.appID),
c.resolve("\(EnvironmentKey.self)", as: c.Cache<EnvironmentKey, Any>.self).resolve(.appID),
isEqualTo: "😎"
)

Expand Down