Skip to content

Commit 8423946

Browse files
naitharparkera
authored andcommitted
[Cherry-Picked] [NSCache] [SR-3904] Fix for object storage. (#901)
* added nscache tests. count limit test is disabled. * Change default values for 'NSCache'. Added NSCacheKey class. new default value '0' of '.totalCostLimit' and '.countLimit' matches Darwin's Foundation version. NSCacheKey is new key class for 'NSCache' inner dictionary. It matches the Darwin's NSCache key behavior. * sort tests cases in aphabetical order. * Update TestNSCache.swift * [NSCache] Status update * NSCache 'isEqual' change based on review. * fix NSCache limit
1 parent 9e5e841 commit 8423946

File tree

5 files changed

+162
-12
lines changed

5 files changed

+162
-12
lines changed

Docs/Status.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ There is no _Complete_ status for test coverage because there are always additio
168168
| `NSMutableSet` | Mostly Complete | Incomplete | `init?(coder:)` remains unimplemented |
169169
| `NSCountedSet` | Mostly Complete | Incomplete | `init?(coder:)` remains unimplemented |
170170
| `NSCFSet` | N/A | N/A | For internal use only |
171-
| `NSCache` | Complete | None | |
171+
| `NSCache` | Complete | Incomplete | |
172172
| `NSSortDescriptor` | Unimplemented | None | |
173173

174174
* **RunLoop**: Timers, streams and run loops.

Foundation.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@
310310
6EB768281D18C12C00D4B719 /* UUID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB768271D18C12C00D4B719 /* UUID.swift */; };
311311
7900433B1CACD33E00ECCBF1 /* TestNSCompoundPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 790043391CACD33E00ECCBF1 /* TestNSCompoundPredicate.swift */; };
312312
7900433C1CACD33E00ECCBF1 /* TestNSPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7900433A1CACD33E00ECCBF1 /* TestNSPredicate.swift */; };
313+
90E645DF1E4C89A400D0D47C /* TestNSCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 90E645DE1E4C89A400D0D47C /* TestNSCache.swift */; };
313314
AE35A1861CBAC85E0042DB84 /* SwiftFoundation.h in Headers */ = {isa = PBXBuildFile; fileRef = AE35A1851CBAC85E0042DB84 /* SwiftFoundation.h */; settings = {ATTRIBUTES = (Public, ); }; };
314315
BD8042161E09857800487EB8 /* TestNSLengthFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD8042151E09857800487EB8 /* TestNSLengthFormatter.swift */; };
315316
BDFDF0A71DFF5B3E00C04CC5 /* TestNSPersonNameComponents.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDFDF0A61DFF5B3E00C04CC5 /* TestNSPersonNameComponents.swift */; };
@@ -752,6 +753,7 @@
752753
848A30571C137B3500C83206 /* TestNSHTTPCookie.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TestNSHTTPCookie.swift; path = TestFoundation/TestNSHTTPCookie.swift; sourceTree = SOURCE_ROOT; };
753754
84BA558D1C16F90900F48C54 /* TestNSTimeZone.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSTimeZone.swift; sourceTree = "<group>"; };
754755
88D28DE61C13AE9000494606 /* TestNSGeometry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSGeometry.swift; sourceTree = "<group>"; };
756+
90E645DE1E4C89A400D0D47C /* TestNSCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSCache.swift; sourceTree = "<group>"; };
755757
A5A34B551C18C85D00FD972B /* TestNSByteCountFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSByteCountFormatter.swift; sourceTree = "<group>"; };
756758
AE35A1851CBAC85E0042DB84 /* SwiftFoundation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SwiftFoundation.h; sourceTree = "<group>"; };
757759
BD8042151E09857800487EB8 /* TestNSLengthFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestNSLengthFormatter.swift; sourceTree = "<group>"; };
@@ -1398,6 +1400,7 @@
13981400
BF8E65301DC3B3CB005AB5C3 /* TestNotification.swift */,
13991401
BDFDF0A61DFF5B3E00C04CC5 /* TestNSPersonNameComponents.swift */,
14001402
CD1C7F7C1E303B47008E331C /* TestNSError.swift */,
1403+
90E645DE1E4C89A400D0D47C /* TestNSCache.swift */,
14011404
);
14021405
name = Tests;
14031406
sourceTree = "<group>";
@@ -2234,6 +2237,7 @@
22342237
CC5249C01D341D23007CB54D /* TestUnitConverter.swift in Sources */,
22352238
5B13B3331C582D4C00651CE2 /* TestNSJSONSerialization.swift in Sources */,
22362239
5B13B33C1C582D4C00651CE2 /* TestNSOrderedSet.swift in Sources */,
2240+
90E645DF1E4C89A400D0D47C /* TestNSCache.swift in Sources */,
22372241
5B13B34A1C582D4C00651CE2 /* TestNSURL.swift in Sources */,
22382242
EA54A6FB1DB16D53009E0809 /* TestObjCRuntime.swift in Sources */,
22392243
5B13B34D1C582D4C00651CE2 /* TestNSUUID.swift in Sources */,

Foundation/NSCache.swift

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,49 @@ private class NSCacheEntry<KeyType : AnyObject, ObjectType : AnyObject> {
2020
}
2121
}
2222

23+
fileprivate class NSCacheKey: NSObject {
24+
25+
var value: AnyObject
26+
27+
init(_ value: AnyObject) {
28+
self.value = value
29+
super.init()
30+
}
31+
32+
override var hashValue: Int {
33+
switch self.value {
34+
case let nsObject as NSObject:
35+
return nsObject.hashValue
36+
case let hashable as Hashable:
37+
return hashable.hashValue
38+
default: return 0
39+
}
40+
}
41+
42+
override func isEqual(_ object: Any?) -> Bool {
43+
guard let other = (object as? NSCacheKey) else { return false }
44+
45+
if self.value === other.value {
46+
return true
47+
} else {
48+
guard let left = self.value as? NSObject,
49+
let right = other.value as? NSObject else { return false }
50+
51+
return left.isEqual(right)
52+
}
53+
}
54+
}
55+
2356
open class NSCache<KeyType : AnyObject, ObjectType : AnyObject> : NSObject {
24-
private var _entries = Dictionary<UnsafeRawPointer, NSCacheEntry<KeyType, ObjectType>>()
57+
58+
private var _entries = Dictionary<NSCacheKey, NSCacheEntry<KeyType, ObjectType>>()
2559
private let _lock = NSLock()
2660
private var _totalCost = 0
2761
private var _byCost: NSCacheEntry<KeyType, ObjectType>?
2862

2963
open var name: String = ""
30-
open var totalCostLimit: Int = -1 // limits are imprecise/not strict
31-
open var countLimit: Int = -1 // limits are imprecise/not strict
64+
open var totalCostLimit: Int = 0 // limits are imprecise/not strict
65+
open var countLimit: Int = 0 // limits are imprecise/not strict
3266
open var evictsObjectsWithDiscardedContent: Bool = false
3367

3468
public override init() {}
@@ -38,10 +72,10 @@ open class NSCache<KeyType : AnyObject, ObjectType : AnyObject> : NSObject {
3872
open func object(forKey key: KeyType) -> ObjectType? {
3973
var object: ObjectType?
4074

41-
let keyRef = unsafeBitCast(key, to: UnsafeRawPointer.self)
75+
let key = NSCacheKey(key)
4276

4377
_lock.lock()
44-
if let entry = _entries[keyRef] {
78+
if let entry = _entries[key] {
4579
object = entry.value
4680
}
4781
_lock.unlock()
@@ -81,7 +115,7 @@ open class NSCache<KeyType : AnyObject, ObjectType : AnyObject> : NSObject {
81115
}
82116

83117
open func setObject(_ obj: ObjectType, forKey key: KeyType, cost g: Int) {
84-
let keyRef = unsafeBitCast(key, to: UnsafeRawPointer.self)
118+
let keyRef = NSCacheKey(key)
85119

86120
_lock.lock()
87121
_totalCost += g
@@ -104,7 +138,9 @@ open class NSCache<KeyType : AnyObject, ObjectType : AnyObject> : NSObject {
104138
insert(entry)
105139
}
106140
} else {
107-
_entries[keyRef] = NSCacheEntry(key: key, value: obj, cost: g)
141+
let entry = NSCacheEntry(key: key, value: obj, cost: g)
142+
_entries[keyRef] = entry
143+
insert(entry)
108144
}
109145
_lock.unlock()
110146

@@ -149,14 +185,14 @@ open class NSCache<KeyType : AnyObject, ObjectType : AnyObject> : NSObject {
149185

150186
_lock.lock()
151187
for entry in toRemove {
152-
_entries.removeValue(forKey: unsafeBitCast(entry.key, to: UnsafeRawPointer.self)) // the cost list is already fixed up in the purge routines
188+
_entries.removeValue(forKey: NSCacheKey(entry.key)) // the cost list is already fixed up in the purge routines
153189
}
154190
_lock.unlock()
155191
}
156192

157-
open func removeObject(forKey key: AnyObject) {
158-
let keyRef = unsafeBitCast(key, to: UnsafeRawPointer.self)
159-
193+
open func removeObject(forKey key: KeyType) {
194+
let keyRef = NSCacheKey(key)
195+
160196
_lock.lock()
161197
if let entry = _entries.removeValue(forKey: keyRef) {
162198
_totalCost -= entry.cost

TestFoundation/TestNSCache.swift

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// This source file is part of the Swift.org open source project
2+
//
3+
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
4+
// Licensed under Apache License v2.0 with Runtime Library Exception
5+
//
6+
// See http://swift.org/LICENSE.txt for license information
7+
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
8+
//
9+
10+
#if DEPLOYMENT_RUNTIME_OBJC || os(Linux)
11+
import Foundation
12+
import XCTest
13+
#else
14+
import SwiftFoundation
15+
import SwiftXCTest
16+
#endif
17+
18+
class TestNSCache : XCTestCase {
19+
20+
static var allTests: [(String, (TestNSCache) -> () throws -> Void)] {
21+
return [
22+
("test_setWithUnmutableKeys", test_setWithUnmutableKeys),
23+
("test_setWithMutableKeys", test_setWithMutableKeys),
24+
("test_costLimit", test_costLimit),
25+
("test_countLimit", test_countLimit),
26+
]
27+
}
28+
29+
func test_setWithUnmutableKeys() {
30+
let cache = NSCache<NSString, NSString>()
31+
32+
var key1 = NSString(string: "key")
33+
var key2 = NSString(string: "key")
34+
var value = NSString(string: "value")
35+
36+
cache.setObject(value, forKey: key1)
37+
38+
XCTAssertEqual(cache.object(forKey: key1), value, "should be equal to \(value) when using first key")
39+
XCTAssertEqual(cache.object(forKey: key2), value, "should be equal to \(value) when using second key")
40+
41+
value = NSString(string: "value1")
42+
cache.setObject(value, forKey: key2)
43+
44+
XCTAssertEqual(cache.object(forKey: key1), value, "should be equal to \(value) when using first key")
45+
XCTAssertEqual(cache.object(forKey: key2), value, "should be equal to \(value) when using second key")
46+
47+
key1 = "kkey"
48+
key2 = "kkey"
49+
let value1 = NSString(string: "value1")
50+
let value2 = NSString(string: "value1")
51+
cache.setObject(value1, forKey: key1)
52+
53+
XCTAssertEqual(cache.object(forKey: key1), value1, "should be equal to \(value1) when using first key")
54+
XCTAssertEqual(cache.object(forKey: key2), value1, "should be equal to \(value1) when using second key")
55+
XCTAssertEqual(cache.object(forKey: key1), value2, "should be equal to \(value1) when using first key")
56+
XCTAssertEqual(cache.object(forKey: key2), value2, "should be equal to \(value1) when using second key")
57+
}
58+
59+
func test_setWithMutableKeys() {
60+
let cache = NSCache<NSMutableString, NSString>()
61+
62+
let key1 = NSMutableString(string: "key")
63+
let key2 = NSMutableString(string: "key")
64+
let value = NSString(string: "value")
65+
66+
cache.setObject(value, forKey: key1)
67+
68+
XCTAssertEqual(cache.object(forKey: key1), value, "should be equal to \(value) when using first key")
69+
XCTAssertEqual(cache.object(forKey: key2), value, "should be equal to \(value) when using second key")
70+
71+
key1.append("1")
72+
73+
XCTAssertEqual(cache.object(forKey: key1), value, "should be equal to \(value) when using first key")
74+
XCTAssertNil(cache.object(forKey: key2), "should be nil")
75+
}
76+
77+
func test_costLimit() {
78+
let cache = NSCache<NSString, NSString>()
79+
cache.totalCostLimit = 10
80+
81+
cache.setObject("object0", forKey: "0", cost: 4)
82+
cache.setObject("object2", forKey: "2", cost: 5)
83+
84+
cache.setObject("object1", forKey: "1", cost: 5)
85+
86+
XCTAssertNil(cache.object(forKey: "0"), "should be nil")
87+
XCTAssertEqual(cache.object(forKey: "2"), "object2", "should be equal to 'object2'")
88+
XCTAssertEqual(cache.object(forKey: "1"), "object1", "should be equal to 'object1'")
89+
}
90+
91+
func test_countLimit() {
92+
let cache = NSCache<NSString, NSString>()
93+
cache.countLimit = 2
94+
95+
let key1 = NSString(string: "key1")
96+
let key2 = NSString(string: "key2")
97+
let key3 = NSString(string: "key3")
98+
let value = NSString(string: "value")
99+
100+
cache.setObject(value, forKey: key1)
101+
cache.setObject(value, forKey: key2)
102+
cache.setObject(value, forKey: key3)
103+
104+
XCTAssertEqual(cache.object(forKey: key2), value, "should be equal to \(value)")
105+
XCTAssertEqual(cache.object(forKey: key3), value, "should be equal to \(value)")
106+
XCTAssertNil(cache.object(forKey: key1), "should be nil")
107+
108+
}
109+
}

TestFoundation/main.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ XCTMain([
2525
testCase(TestNSArray.allTests),
2626
testCase(TestNSBundle.allTests),
2727
testCase(TestNSByteCountFormatter.allTests),
28+
testCase(TestNSCache.allTests),
2829
testCase(TestNSCalendar.allTests),
2930
testCase(TestNSCharacterSet.allTests),
3031
testCase(TestNSCompoundPredicate.allTests),

0 commit comments

Comments
 (0)