Skip to content

Commit ca6df2d

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 ca6df2d

File tree

1 file changed

+75
-65
lines changed

1 file changed

+75
-65
lines changed

Foundation/NSCache.swift

Lines changed: 75 additions & 65 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
@@ -92,101 +92,111 @@ open class NSCache<KeyType : AnyObject, ObjectType : AnyObject> : NSObject {
9292
let oldNext = entry.nextByCost
9393
oldPrev?.nextByCost = oldNext
9494
oldNext?.prevByCost = oldPrev
95-
if entry === _byCost {
96-
_byCost = entry.nextByCost
95+
if entry === _head {
96+
_head = oldNext
9797
}
9898
}
9999

100100
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
101+
guard var currentElement = _head else {
102+
// The cache is empty
103+
entry.prevByCost = nil
104+
entry.nextByCost = nil
105+
_head = entry
106+
return
107+
}
108+
109+
repeat {
110+
if currentElement.cost > entry.cost {
111+
// Insert entry before currentElement
112+
if let previousElement = currentElement.prevByCost {
113+
// Insert entry between previousElement and currentElement
114+
previousElement.nextByCost = entry
115+
entry.prevByCost = previousElement
116+
117+
entry.nextByCost = currentElement
118+
currentElement.prevByCost = entry
119+
} else {
120+
// Insert entry at the head
121+
entry.prevByCost = nil
122+
entry.nextByCost = currentElement
123+
currentElement.prevByCost = entry
124+
125+
_head = entry
126+
}
127+
return
128+
} else {
129+
if let nextElement = currentElement.nextByCost {
130+
// Compare entry with nextElement in the lext loop
131+
currentElement = nextElement
132+
} else {
133+
// Insert entry at the tail
134+
currentElement.nextByCost = entry
135+
entry.prevByCost = currentElement
136+
entry.nextByCost = nil
137+
return
111138
}
112-
element = e.nextByCost
113139
}
114-
}
140+
} while true
115141
}
116142

117143
open func setObject(_ obj: ObjectType, forKey key: KeyType, cost g: Int) {
118144
let keyRef = NSCacheKey(key)
119145

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

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

133150
if let entry = _entries[keyRef] {
151+
costDiff = g - entry.cost
152+
entry.cost = g
153+
134154
entry.value = obj
135-
if entry.cost != g {
136-
entry.cost = g
155+
156+
if costDiff != 0 {
137157
remove(entry)
138158
insert(entry)
139159
}
140160
} else {
141161
let entry = NSCacheEntry(key: key, value: obj, cost: g)
142162
_entries[keyRef] = entry
143163
insert(entry)
164+
165+
costDiff = g
144166
}
145-
_lock.unlock()
146-
147-
var toRemove = [NSCacheEntry<KeyType, ObjectType>]()
148167

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-
}
168+
_totalCost += costDiff
165169

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-
}
170+
var purgeAmount = (totalCostLimit > 0) ? (_totalCost - totalCostLimit) : 0
171+
while purgeAmount > 0 {
172+
if let entry = _head {
173+
delegate?.cache(unsafeDowncast(self, to:NSCache<AnyObject, AnyObject>.self), willEvictObject: entry.value)
174+
175+
_totalCost -= entry.cost
176+
purgeAmount -= entry.cost
177+
178+
remove(entry) // _head will be changed to next entry in remove(_:)
179+
_entries[NSCacheKey(entry.key)] = nil
180+
} else {
181+
break
176182
}
177-
_lock.unlock()
178183
}
179184

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

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-
}
190200
_lock.unlock()
191201
}
192202

@@ -204,7 +214,7 @@ open class NSCache<KeyType : AnyObject, ObjectType : AnyObject> : NSObject {
204214
open func removeAllObjects() {
205215
_lock.lock()
206216
_entries.removeAll()
207-
_byCost = nil
217+
_head = nil
208218
_totalCost = 0
209219
_lock.unlock()
210220
}

0 commit comments

Comments
 (0)