Skip to content

Commit ea41bb0

Browse files
authored
Merge pull request #1047 from zetasq/master
2 parents e4ee8d5 + 9124877 commit ea41bb0

File tree

2 files changed

+121
-76
lines changed

2 files changed

+121
-76
lines changed

Foundation/NSCache.swift

Lines changed: 79 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ open class NSCache<KeyType : AnyObject, ObjectType : AnyObject> : NSObject {
5858
private var _entries = Dictionary<NSCacheKey, NSCacheEntry<KeyType, ObjectType>>()
5959
private let _lock = NSLock()
6060
private var _totalCost = 0
61-
private var _byCost: NSCacheEntry<KeyType, ObjectType>?
61+
private var _head: NSCacheEntry<KeyType, ObjectType>?
6262

6363
open var name: String = ""
6464
open var totalCostLimit: Int = 0 // limits are imprecise/not strict
@@ -90,103 +90,107 @@ open class NSCache<KeyType : AnyObject, ObjectType : AnyObject> : NSObject {
9090
private func remove(_ entry: NSCacheEntry<KeyType, ObjectType>) {
9191
let oldPrev = entry.prevByCost
9292
let oldNext = entry.nextByCost
93+
9394
oldPrev?.nextByCost = oldNext
9495
oldNext?.prevByCost = oldPrev
95-
if entry === _byCost {
96-
_byCost = entry.nextByCost
96+
97+
if entry === _head {
98+
_head = oldNext
9799
}
98100
}
99101

100102
private func insert(_ entry: NSCacheEntry<KeyType, ObjectType>) {
101-
if _byCost == nil {
102-
_byCost = entry
103-
} else {
104-
var element = _byCost
105-
while let e = element {
106-
if e.cost > entry.cost {
107-
let newPrev = e.prevByCost
108-
entry.prevByCost = newPrev
109-
entry.nextByCost = e
110-
break
111-
}
112-
element = e.nextByCost
113-
}
103+
guard var currentElement = _head else {
104+
// The cache is empty
105+
entry.prevByCost = nil
106+
entry.nextByCost = nil
107+
108+
_head = entry
109+
return
110+
}
111+
112+
guard entry.cost > currentElement.cost else {
113+
// Insert entry at the head
114+
entry.prevByCost = nil
115+
entry.nextByCost = currentElement
116+
currentElement.prevByCost = entry
117+
118+
_head = entry
119+
return
114120
}
121+
122+
while currentElement.nextByCost != nil && currentElement.nextByCost!.cost < entry.cost {
123+
currentElement = currentElement.nextByCost!
124+
}
125+
126+
// Insert entry between currentElement and nextElement
127+
let nextElement = currentElement.nextByCost
128+
129+
currentElement.nextByCost = entry
130+
entry.prevByCost = currentElement
131+
132+
entry.nextByCost = nextElement
133+
nextElement?.prevByCost = entry
115134
}
116135

117136
open func setObject(_ obj: ObjectType, forKey key: KeyType, cost g: Int) {
137+
let g = max(g, 0)
118138
let keyRef = NSCacheKey(key)
119139

120140
_lock.lock()
121-
_totalCost += g
122-
123-
var purgeAmount = 0
124-
if totalCostLimit > 0 {
125-
purgeAmount = (_totalCost + g) - totalCostLimit
126-
}
127141

128-
var purgeCount = 0
129-
if countLimit > 0 {
130-
purgeCount = (_entries.count + 1) - countLimit
131-
}
142+
let costDiff: Int
132143

133144
if let entry = _entries[keyRef] {
145+
costDiff = g - entry.cost
146+
entry.cost = g
147+
134148
entry.value = obj
135-
if entry.cost != g {
136-
entry.cost = g
149+
150+
if costDiff != 0 {
137151
remove(entry)
138152
insert(entry)
139153
}
140154
} else {
141155
let entry = NSCacheEntry(key: key, value: obj, cost: g)
142156
_entries[keyRef] = entry
143157
insert(entry)
158+
159+
costDiff = g
144160
}
145-
_lock.unlock()
146161

147-
var toRemove = [NSCacheEntry<KeyType, ObjectType>]()
148-
149-
if purgeAmount > 0 {
150-
_lock.lock()
151-
while _totalCost - totalCostLimit > 0 {
152-
if let entry = _byCost {
153-
_totalCost -= entry.cost
154-
toRemove.append(entry)
155-
remove(entry)
156-
} else {
157-
break
158-
}
159-
}
160-
if countLimit > 0 {
161-
purgeCount = (_entries.count - toRemove.count) - countLimit
162-
}
163-
_lock.unlock()
164-
}
162+
_totalCost += costDiff
165163

166-
if purgeCount > 0 {
167-
_lock.lock()
168-
while (_entries.count - toRemove.count) - countLimit > 0 {
169-
if let entry = _byCost {
170-
_totalCost -= entry.cost
171-
toRemove.append(entry)
172-
remove(entry)
173-
} else {
174-
break
175-
}
164+
var purgeAmount = (totalCostLimit > 0) ? (_totalCost - totalCostLimit) : 0
165+
while purgeAmount > 0 {
166+
if let entry = _head {
167+
delegate?.cache(unsafeDowncast(self, to:NSCache<AnyObject, AnyObject>.self), willEvictObject: entry.value)
168+
169+
_totalCost -= entry.cost
170+
purgeAmount -= entry.cost
171+
172+
remove(entry) // _head will be changed to next entry in remove(_:)
173+
_entries[NSCacheKey(entry.key)] = nil
174+
} else {
175+
break
176176
}
177-
_lock.unlock()
178177
}
179178

180-
if let del = delegate {
181-
for entry in toRemove {
182-
del.cache(unsafeDowncast(self, to:NSCache<AnyObject, AnyObject>.self), willEvictObject: entry.value)
179+
var purgeCount = (countLimit > 0) ? (_entries.count - countLimit) : 0
180+
while purgeCount > 0 {
181+
if let entry = _head {
182+
delegate?.cache(unsafeDowncast(self, to:NSCache<AnyObject, AnyObject>.self), willEvictObject: entry.value)
183+
184+
_totalCost -= entry.cost
185+
purgeCount -= 1
186+
187+
remove(entry) // _head will be changed to next entry in remove(_:)
188+
_entries[NSCacheKey(entry.key)] = nil
189+
} else {
190+
break
183191
}
184192
}
185193

186-
_lock.lock()
187-
for entry in toRemove {
188-
_entries.removeValue(forKey: NSCacheKey(entry.key)) // the cost list is already fixed up in the purge routines
189-
}
190194
_lock.unlock()
191195
}
192196

@@ -204,7 +208,16 @@ open class NSCache<KeyType : AnyObject, ObjectType : AnyObject> : NSObject {
204208
open func removeAllObjects() {
205209
_lock.lock()
206210
_entries.removeAll()
207-
_byCost = nil
211+
212+
while let currentElement = _head {
213+
let nextElement = currentElement.nextByCost
214+
215+
currentElement.prevByCost = nil
216+
currentElement.nextByCost = nil
217+
218+
_head = nextElement
219+
}
220+
208221
_totalCost = 0
209222
_lock.unlock()
210223
}

TestFoundation/TestNSCache.swift

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ class TestNSCache : XCTestCase {
2424
("test_costLimit", test_costLimit),
2525
("test_countLimit", test_countLimit),
2626
("test_hashableKey", test_hashableKey),
27-
("test_nonHashableKey", test_nonHashableKey)
27+
("test_nonHashableKey", test_nonHashableKey),
28+
("test_objectCorrectlyReleased", test_objectCorrectlyReleased)
2829
]
2930
}
3031

@@ -99,9 +100,9 @@ class TestNSCache : XCTestCase {
99100
let key3 = NSString(string: "key3")
100101
let value = NSString(string: "value")
101102

102-
cache.setObject(value, forKey: key1)
103-
cache.setObject(value, forKey: key2)
104-
cache.setObject(value, forKey: key3)
103+
cache.setObject(value, forKey: key1, cost: 1)
104+
cache.setObject(value, forKey: key2, cost: 2)
105+
cache.setObject(value, forKey: key3, cost: 3)
105106

106107
XCTAssertEqual(cache.object(forKey: key2), value, "should be equal to \(value)")
107108
XCTAssertEqual(cache.object(forKey: key3), value, "should be equal to \(value)")
@@ -134,9 +135,9 @@ class TestNSCache : XCTestCase {
134135
let key3 = TestHashableCacheKey(string: "key3")
135136
let value = NSString(string: "value")
136137

137-
cache.setObject(value, forKey: key1)
138-
cache.setObject(value, forKey: key2)
139-
cache.setObject(value, forKey: key3)
138+
cache.setObject(value, forKey: key1, cost: 1)
139+
cache.setObject(value, forKey: key2, cost: 2)
140+
cache.setObject(value, forKey: key3, cost: 3)
140141

141142
XCTAssertEqual(cache.object(forKey: key2), value, "should be equal to \(value)")
142143
XCTAssertEqual(cache.object(forKey: key3), value, "should be equal to \(value)")
@@ -162,12 +163,43 @@ class TestNSCache : XCTestCase {
162163
let key3 = TestCacheKey(string: "key3")
163164
let value = NSString(string: "value")
164165

165-
cache.setObject(value, forKey: key1)
166-
cache.setObject(value, forKey: key2)
167-
cache.setObject(value, forKey: key3)
166+
cache.setObject(value, forKey: key1, cost: 1)
167+
cache.setObject(value, forKey: key2, cost: 2)
168+
cache.setObject(value, forKey: key3, cost: 3)
168169

169170
XCTAssertEqual(cache.object(forKey: key2), value, "should be equal to \(value)")
170171
XCTAssertEqual(cache.object(forKey: key3), value, "should be equal to \(value)")
171172
XCTAssertNil(cache.object(forKey: key1), "should be nil")
172173
}
174+
175+
func test_objectCorrectlyReleased() {
176+
let cache = NSCache<NSString, AnyObject>()
177+
cache.totalCostLimit = 10
178+
179+
var object1 = NSObject()
180+
weak var weakObject1: NSObject? = object1
181+
182+
var object2 = NSObject()
183+
weak var weakObject2: NSObject? = object2
184+
185+
var object3 = NSObject()
186+
weak var weakObject3: NSObject? = object3
187+
188+
let object4 = NSObject()
189+
let object5 = NSObject()
190+
191+
cache.setObject(object1, forKey: "key1", cost: 1)
192+
cache.setObject(object2, forKey: "key2", cost: 2)
193+
cache.setObject(object3, forKey: "key3", cost: 3)
194+
cache.setObject(object4, forKey: "key4", cost: 4)
195+
cache.setObject(object5, forKey: "key5", cost: 5)
196+
197+
object1 = NSObject()
198+
object2 = NSObject()
199+
object3 = NSObject()
200+
201+
XCTAssertNil(weakObject1, "removed cached object not released")
202+
XCTAssertNil(weakObject2, "removed cached object not released")
203+
XCTAssertNil(weakObject3, "removed cached object not released")
204+
}
173205
}

0 commit comments

Comments
 (0)