Skip to content

Commit da0b3f0

Browse files
committed
use NSCache for client-side object cache
1 parent fecc268 commit da0b3f0

File tree

3 files changed

+415
-2
lines changed

3 files changed

+415
-2
lines changed
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
//
2+
// Copyright (c) 2026 PADL Software Pty Ltd
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the License);
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an 'AS IS' BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
17+
@preconcurrency
18+
import Foundation
19+
20+
final class OcaObjectCache: NSObject, @unchecked Sendable, NSCacheDelegate {
21+
private let _cache = NSCache<NSNumber, OcaRoot>()
22+
private let _objectNumbers = Mutex(Set<OcaONo>())
23+
24+
override init() {
25+
super.init()
26+
_cache.delegate = self
27+
}
28+
29+
convenience init(countLimit: Int) {
30+
self.init()
31+
if countLimit > 0 {
32+
_cache.countLimit = countLimit
33+
}
34+
}
35+
36+
subscript(key: OcaONo) -> OcaRoot? {
37+
get {
38+
_cache.object(forKey: NSNumber(value: key))
39+
}
40+
set {
41+
if let newValue {
42+
_cache.setObject(newValue, forKey: NSNumber(value: key))
43+
_ = _objectNumbers.withLock { $0.insert(key) }
44+
} else {
45+
_cache.removeObject(forKey: NSNumber(value: key))
46+
_ = _objectNumbers.withLock { $0.remove(key) }
47+
}
48+
}
49+
}
50+
51+
var count: Int {
52+
_objectNumbers.withLock { $0.count }
53+
}
54+
55+
var keys: Set<OcaONo> {
56+
_objectNumbers.withLock { $0 }
57+
}
58+
59+
var values: [OcaRoot] {
60+
let keysCopy = _objectNumbers.withLock { $0 }
61+
return keysCopy.compactMap { _cache.object(forKey: NSNumber(value: $0)) }
62+
}
63+
64+
func removeAll() {
65+
_cache.removeAllObjects()
66+
_objectNumbers.withLock { $0.removeAll() }
67+
}
68+
69+
func removeValue(forKey key: OcaONo) {
70+
_cache.removeObject(forKey: NSNumber(value: key))
71+
_ = _objectNumbers.withLock { $0.remove(key) }
72+
}
73+
74+
fileprivate func removeKeyTracking(for key: OcaONo) {
75+
_ = _objectNumbers.withLock { $0.remove(key) }
76+
}
77+
78+
func cache(
79+
_ cache: NSCache<AnyObject, AnyObject>,
80+
willEvictObject obj: Any
81+
) {
82+
if let object = obj as? OcaRoot {
83+
removeKeyTracking(for: object.objectNumber)
84+
}
85+
}
86+
}
87+
88+
// MARK: - Sequence Conformance
89+
90+
extension OcaObjectCache: Sequence {
91+
func makeIterator() -> AnyIterator<(key: OcaONo, value: OcaRoot)> {
92+
let keysCopy = _objectNumbers.withLock { $0 }
93+
var iterator = keysCopy.makeIterator()
94+
return AnyIterator {
95+
guard let key = iterator.next(),
96+
let value = self[key]
97+
else {
98+
return nil
99+
}
100+
return (key, value)
101+
}
102+
}
103+
}

Sources/SwiftOCA/OCP.1/Ocp1Connection.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ open class Ocp1Connection: CustomStringConvertible {
257257
public let connectionState: AnyAsyncSequence<Ocp1ConnectionState>
258258

259259
/// Object interning
260-
var objects = [OcaONo: OcaRoot]()
260+
var objects = OcaObjectCache()
261261

262262
/// Root block, immutable
263263
public let rootBlock = OcaBlock(objectNumber: OcaRootBlockONo)
@@ -425,7 +425,7 @@ open class Ocp1Connection: CustomStringConvertible {
425425
open func connectDevice() async throws {}
426426

427427
public func clearObjectCache() async {
428-
objects = [:]
428+
objects.removeAll()
429429
}
430430

431431
open func disconnectDevice() async throws {}

0 commit comments

Comments
 (0)