From ad460a47813fc877a4f9ff10f77f501ac0324a45 Mon Sep 17 00:00:00 2001 From: swiftty <62803132+swiftty@users.noreply.github.com> Date: Sun, 5 Jan 2025 13:23:20 +0900 Subject: [PATCH 1/3] Separate LRUCache target --- Package.swift | 17 ++++++++-------- Sources/DataCacheKit/MemoryCache.swift | 1 + .../Utils => LRUCache}/LRUCache.swift | 20 ++++++++++--------- .../LRUCacheTests.swift | 2 +- 4 files changed, 22 insertions(+), 18 deletions(-) rename Sources/{DataCacheKit/Utils => LRUCache}/LRUCache.swift (88%) rename Tests/{DataCacheKitTests => LRUCacheTests}/LRUCacheTests.swift (99%) diff --git a/Package.swift b/Package.swift index 302b327..47b6113 100644 --- a/Package.swift +++ b/Package.swift @@ -13,23 +13,24 @@ let package = Package( .macOS(.v13) ], products: [ - // Products define the executables and libraries a package produces, and make them visible to other packages. .library( name: "DataCacheKit", targets: ["DataCacheKit"]), ], - dependencies: [ - // Dependencies declare other packages that this package depends on. - // .package(url: /* package url */, from: "1.0.0"), - ], + dependencies: [], targets: [ - // Targets are the basic building blocks of a package. A target can define a module or a test suite. - // Targets can depend on other targets in this package, and on products in packages this package depends on. .target( name: "DataCacheKit", - dependencies: []), + dependencies: ["LRUCache"]), .testTarget( name: "DataCacheKitTests", dependencies: ["DataCacheKit"]), + + .target( + name: "LRUCache", + dependencies: []), + .testTarget( + name: "LRUCacheTests", + dependencies: ["LRUCache"]), ] ) diff --git a/Sources/DataCacheKit/MemoryCache.swift b/Sources/DataCacheKit/MemoryCache.swift index 3303462..36b538a 100644 --- a/Sources/DataCacheKit/MemoryCache.swift +++ b/Sources/DataCacheKit/MemoryCache.swift @@ -1,5 +1,6 @@ import Foundation import OSLog +import LRUCache public actor MemoryCache: Caching { public nonisolated let options: Options diff --git a/Sources/DataCacheKit/Utils/LRUCache.swift b/Sources/LRUCache/LRUCache.swift similarity index 88% rename from Sources/DataCacheKit/Utils/LRUCache.swift rename to Sources/LRUCache/LRUCache.swift index e324d7b..5f4733c 100644 --- a/Sources/DataCacheKit/Utils/LRUCache.swift +++ b/Sources/LRUCache/LRUCache.swift @@ -1,36 +1,38 @@ // https://github.com/swiftlang/swift-corelibs-foundation/blob/25d044f2c4ceb635d9f714f588673fd7a29790c1/Sources/Foundation/NSCache.swift import os -struct LRUCache: ~Copyable, Sendable { - var totalCostLimit: Int { +public struct LRUCache: ~Copyable, Sendable { + public var totalCostLimit: Int { get { entries.withLock { $0.totalCostLimit } } nonmutating set { entries.withLock { $0.totalCostLimit = newValue } } } - var countLimit: Int { + public var countLimit: Int { get { entries.withLock { $0.countLimit } } nonmutating set { entries.withLock { $0.countLimit = newValue } } } private let entries = OSAllocatedUnfairLock(initialState: .init()) - func value(forKey key: Key) -> Value? { + public init() {} + + public func value(forKey key: Key) -> Value? { entries.withLock { entries in entries.values[key]?.value } } - func setValue(_ value: Value, forKey key: Key) { + public func setValue(_ value: Value, forKey key: Key) { setValue(value, forKey: key, cost: 0) } - func setValue(_ value: Value, forKey key: Key, cost: Int) { + public func setValue(_ value: Value, forKey key: Key, cost: Int) { entries.withLock { entries in entries.set(value, forKey: key, cost: cost) } } - func removeValue(forKey key: Key) { + public func removeValue(forKey key: Key) { entries.withLock { entries in if let entry = entries.values.removeValue(forKey: key) { entries.totalCost -= entry.cost @@ -39,7 +41,7 @@ struct LRUCache: ~Copyable, Sendable } } - func removeAllValues() { + public func removeAllValues() { entries.withLock { entiries in entiries.removeAll() } @@ -47,7 +49,7 @@ struct LRUCache: ~Copyable, Sendable } extension LRUCache { - subscript (_ key: Key, cost cost: Int = 0) -> Value? { + public subscript (_ key: Key, cost cost: Int = 0) -> Value? { get { value(forKey: key) } diff --git a/Tests/DataCacheKitTests/LRUCacheTests.swift b/Tests/LRUCacheTests/LRUCacheTests.swift similarity index 99% rename from Tests/DataCacheKitTests/LRUCacheTests.swift rename to Tests/LRUCacheTests/LRUCacheTests.swift index 0c8f9df..217ae7a 100644 --- a/Tests/DataCacheKitTests/LRUCacheTests.swift +++ b/Tests/LRUCacheTests/LRUCacheTests.swift @@ -1,5 +1,5 @@ import Testing -@testable import DataCacheKit +@testable import LRUCache import Foundation struct LRUCacheTests { From 0606232bc45c205f238554bd8015976198c93336 Mon Sep 17 00:00:00 2001 From: swiftty <62803132+swiftty@users.noreply.github.com> Date: Sun, 5 Jan 2025 13:24:16 +0900 Subject: [PATCH 2/3] Clear relation when removed --- Sources/LRUCache/LRUCache.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Sources/LRUCache/LRUCache.swift b/Sources/LRUCache/LRUCache.swift index 5f4733c..2f19e84 100644 --- a/Sources/LRUCache/LRUCache.swift +++ b/Sources/LRUCache/LRUCache.swift @@ -157,6 +157,9 @@ private extension LRUCache { if entry === tail { tail = oldPrev } + + entry.next = nil + entry.prev = nil } mutating func removeAll() { From 09df273d5acec8f3de7522ababf9faa7b0dd0766 Mon Sep 17 00:00:00 2001 From: swiftty <62803132+swiftty@users.noreply.github.com> Date: Sun, 5 Jan 2025 13:29:47 +0900 Subject: [PATCH 3/3] Update entry list when accessing value --- Sources/LRUCache/LRUCache.swift | 5 +++- Tests/LRUCacheTests/LRUCacheTests.swift | 40 +++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/Sources/LRUCache/LRUCache.swift b/Sources/LRUCache/LRUCache.swift index 2f19e84..87af5ee 100644 --- a/Sources/LRUCache/LRUCache.swift +++ b/Sources/LRUCache/LRUCache.swift @@ -18,7 +18,10 @@ public struct LRUCache: ~Copyable, Se public func value(forKey key: Key) -> Value? { entries.withLock { entries in - entries.values[key]?.value + guard let entry = entries.values[key] else { return nil } + entries.remove(entry) + entries.insert(entry) + return entry.value } } diff --git a/Tests/LRUCacheTests/LRUCacheTests.swift b/Tests/LRUCacheTests/LRUCacheTests.swift index 217ae7a..c423a67 100644 --- a/Tests/LRUCacheTests/LRUCacheTests.swift +++ b/Tests/LRUCacheTests/LRUCacheTests.swift @@ -75,6 +75,46 @@ struct LRUCacheTests { #expect(cache[3] == 3) } + @Test + func testCountLimitAccess() { + let cache = LRUCache() + + // Given + cache.countLimit = 2 + + // When + cache[1] = 1 + cache[2] = 2 + + _ = cache[1] + cache[3] = 3 + + // Then + #expect(cache[1] == 1) + #expect(cache[2] == nil) + #expect(cache[3] == 3) + } + + @Test + func testCountLimitAccessNSCache() { + let cache = NSCacheWrapper() + + // Given + cache.countLimit = 2 + + // When + cache[1] = 1 + cache[2] = 2 + + _ = cache[1] + cache[3] = 3 + + // Then + #expect(cache[1] == 1) + #expect(cache[2] == nil) + #expect(cache[3] == 3) + } + @Test func testCountLimitWithCost1() { let cache = LRUCache()