Skip to content

Commit 28b4194

Browse files
committed
Fix and optimize the logic for maintaining the internal priority queue NSCache.
Currently when multiple objects are set into an NSCache object, the internal priority queue is not maintained correctly. The logic for maintaining the queue is correct now.
1 parent ea68075 commit 28b4194

File tree

1 file changed

+78
-66
lines changed

1 file changed

+78
-66
lines changed

Foundation/NSCache.swift

Lines changed: 78 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,106 @@ 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) {
118137
let keyRef = NSCacheKey(key)
119138

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

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

133143
if let entry = _entries[keyRef] {
144+
costDiff = g - entry.cost
145+
entry.cost = g
146+
134147
entry.value = obj
135-
if entry.cost != g {
136-
entry.cost = g
148+
149+
if costDiff != 0 {
137150
remove(entry)
138151
insert(entry)
139152
}
140153
} else {
141154
let entry = NSCacheEntry(key: key, value: obj, cost: g)
142155
_entries[keyRef] = entry
143156
insert(entry)
157+
158+
costDiff = g
144159
}
145-
_lock.unlock()
146160

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-
}
161+
_totalCost += costDiff
165162

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-
}
163+
var purgeAmount = (totalCostLimit > 0) ? (_totalCost - totalCostLimit) : 0
164+
while purgeAmount > 0 {
165+
if let entry = _head {
166+
delegate?.cache(unsafeDowncast(self, to:NSCache<AnyObject, AnyObject>.self), willEvictObject: entry.value)
167+
168+
_totalCost -= entry.cost
169+
purgeAmount -= entry.cost
170+
171+
remove(entry) // _head will be changed to next entry in remove(_:)
172+
_entries[NSCacheKey(entry.key)] = nil
173+
} else {
174+
break
176175
}
177-
_lock.unlock()
178176
}
179177

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

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-
}
190193
_lock.unlock()
191194
}
192195

@@ -204,7 +207,16 @@ open class NSCache<KeyType : AnyObject, ObjectType : AnyObject> : NSObject {
204207
open func removeAllObjects() {
205208
_lock.lock()
206209
_entries.removeAll()
207-
_byCost = nil
210+
211+
while let currentElement = _head {
212+
let nextElement = currentElement.nextByCost
213+
214+
currentElement.prevByCost = nil
215+
currentElement.nextByCost = nil
216+
217+
_head = nextElement
218+
}
219+
208220
_totalCost = 0
209221
_lock.unlock()
210222
}

0 commit comments

Comments
 (0)