@@ -53,15 +53,15 @@ class StoredCachedURLResponse: NSObject, NSSecureCoding {
53
53
func encode( with aCoder: NSCoder ) {
54
54
aCoder. encode ( cachedURLResponse. response, forKey: " response " )
55
55
aCoder. encode ( cachedURLResponse. data as NSData , forKey: " data " )
56
- aCoder. encode ( cachedURLResponse. storagePolicy. rawValue, forKey: " storagePolicy " )
56
+ aCoder. encode ( Int ( bitPattern : cachedURLResponse. storagePolicy. rawValue) , forKey: " storagePolicy " )
57
57
aCoder. encode ( cachedURLResponse. userInfo as NSDictionary ? , forKey: " userInfo " )
58
58
aCoder. encode ( cachedURLResponse. date as NSDate , forKey: " date " )
59
59
}
60
60
61
61
required init ? ( coder aDecoder: NSCoder ) {
62
62
guard let response = aDecoder. decodeObject ( of: URLResponse . self, forKey: " response " ) ,
63
63
let data = aDecoder. decodeObject ( of: NSData . self, forKey: " data " ) ,
64
- let storagePolicy = URLCache . StoragePolicy ( rawValue: UInt ( aDecoder. decodeInt64 ( forKey: " storagePolicy " ) ) ) ,
64
+ let storagePolicy = URLCache . StoragePolicy ( rawValue: UInt ( bitPattern : aDecoder. decodeInteger ( forKey: " storagePolicy " ) ) ) ,
65
65
let date = aDecoder. decodeObject ( of: NSDate . self, forKey: " date " ) else {
66
66
return nil
67
67
}
@@ -196,7 +196,7 @@ open class CachedURLResponse : NSObject, NSCopying {
196
196
open class URLCache : NSObject {
197
197
198
198
private static let sharedLock = NSLock ( )
199
- private static var _shared = URLCache ( memoryCapacity : 4 * 1024 * 1024 , diskCapacity : 20 * 1024 * 1024 , diskPath : nil )
199
+ private static var _shared : URLCache ?
200
200
201
201
/*!
202
202
@method sharedURLCache
@@ -218,7 +218,13 @@ open class URLCache : NSObject {
218
218
open class var shared : URLCache {
219
219
get {
220
220
return sharedLock. performLocked {
221
- return _shared
221
+ if let shared = _shared {
222
+ return shared
223
+ }
224
+
225
+ let shared = URLCache ( memoryCapacity: 4 * 1024 * 1024 , diskCapacity: 20 * 1024 * 1024 , diskPath: nil )
226
+ _shared = shared
227
+ return shared
222
228
}
223
229
}
224
230
set {
@@ -258,59 +264,39 @@ open class URLCache : NSObject {
258
264
private var inMemoryCacheContents : [ String : CacheEntry ] = [ : ]
259
265
260
266
func evictFromMemoryCacheAssumingLockHeld( maximumSize: Int ) {
261
- let sizes : [ Int ] = inMemoryCacheOrder. map {
262
- inMemoryCacheContents [ $0] !. cost
263
- }
264
-
265
- var totalSize = sizes. reduce ( 0 , + )
266
-
267
- guard totalSize > maximumSize else { return }
268
-
269
- var identifiersToRemove : Set < String > = [ ]
270
- for (index, identifier) in inMemoryCacheOrder. enumerated ( ) {
271
- identifiersToRemove. insert ( identifier)
272
- totalSize -= sizes [ index]
273
- if totalSize < maximumSize {
267
+ var totalSize = inMemoryCacheContents. values. reduce ( 0 ) { $0 + $1. cost }
268
+
269
+ var countEvicted = 0
270
+ for identifier in inMemoryCacheOrder {
271
+ if totalSize > maximumSize {
272
+ countEvicted += 1
273
+ let entry = inMemoryCacheContents. removeValue ( forKey: identifier) !
274
+ totalSize -= entry. cost
275
+ } else {
274
276
break
275
277
}
276
278
}
277
279
278
- for identifier in identifiersToRemove {
279
- inMemoryCacheContents. removeValue ( forKey: identifier)
280
- }
281
- inMemoryCacheOrder. removeAll ( where: { identifiersToRemove. contains ( $0) } )
280
+ inMemoryCacheOrder. removeSubrange ( 0 ..< countEvicted)
282
281
}
283
282
284
283
func evictFromDiskCache( maximumSize: Int ) {
285
- var entries : [ DiskEntry ] = [ ]
286
- enumerateDiskEntries ( includingPropertiesForKeys: [ . fileSizeKey] ) { ( entry, stop) in
287
- entries. append ( entry)
288
- }
289
-
290
- entries. sort { ( a, b) -> Bool in
291
- a. date < b. date
284
+ let entries = diskEntries ( includingPropertiesForKeys: [ . fileSizeKey] ) . sorted {
285
+ $0. date < $1. date
292
286
}
293
287
294
- let sizes : [ Int ] = entries. map {
295
- return ( try ? $0 . url. resourceValues ( forKeys: [ . fileSizeKey] ) . fileSize) ?? 0
288
+ let sizes = entries. map { ( entry ) in
289
+ ( try ? entry . url. resourceValues ( forKeys: [ . fileSizeKey] ) . fileSize) ?? 0
296
290
}
297
-
291
+
298
292
var totalSize = sizes. reduce ( 0 , + )
299
293
300
- guard totalSize > maximumSize else { return }
301
-
302
- var urlsToRemove : [ URL ] = [ ]
303
294
for (index, entry) in entries. enumerated ( ) {
304
- urlsToRemove. append ( entry. url)
305
- totalSize -= sizes [ index]
306
- if totalSize < maximumSize {
307
- break
295
+ if totalSize > maximumSize {
296
+ try ? FileManager . default. removeItem ( at: entry. url)
297
+ totalSize -= sizes [ index]
308
298
}
309
299
}
310
-
311
- for url in urlsToRemove {
312
- try ? FileManager . default. removeItem ( at: url)
313
- }
314
300
}
315
301
316
302
/*!
@@ -330,8 +316,10 @@ open class URLCache : NSObject {
330
316
self . memoryCapacity = memoryCapacity
331
317
self . diskCapacity = diskCapacity
332
318
319
+ let url : URL ?
320
+
333
321
if let path = path {
334
- cacheDirectory = URL ( fileURLWithPath: path)
322
+ url = URL ( fileURLWithPath: path)
335
323
} else {
336
324
do {
337
325
let caches = try FileManager . default. url ( for: . cachesDirectory, in: . userDomainMask, appropriateFor: nil , create: true )
@@ -342,15 +330,23 @@ open class URLCache : NSObject {
342
330
343
331
// We append a Swift Foundation identifier to avoid clobbering a Darwin cache that may exist at the same path;
344
332
// the two on-disk cache formats aren't compatible.
345
- let url = caches
346
- . appendingPathComponent ( " org.swift.Foundation .URLCache " , isDirectory: true )
333
+ url = caches
334
+ . appendingPathComponent ( " org.swift.foundation .URLCache " , isDirectory: true )
347
335
. appendingPathComponent ( directoryName, isDirectory: true )
336
+ } catch {
337
+ url = nil
338
+ }
339
+ }
340
+
341
+ if let url = url {
342
+ do {
348
343
try FileManager . default. createDirectory ( at: url, withIntermediateDirectories: true )
349
-
350
344
cacheDirectory = url
351
345
} catch {
352
346
cacheDirectory = nil
353
347
}
348
+ } else {
349
+ cacheDirectory = nil
354
350
}
355
351
}
356
352
@@ -381,7 +377,7 @@ open class URLCache : NSObject {
381
377
var identifier : String
382
378
383
379
init ? ( _ url: URL ) {
384
- if url. pathExtension. localizedCompare ( DiskEntry . pathExtension) != . orderedSame {
380
+ if url. pathExtension. caseInsensitiveCompare ( DiskEntry . pathExtension) != . orderedSame {
385
381
return nil
386
382
}
387
383
@@ -410,35 +406,42 @@ open class URLCache : NSObject {
410
406
}
411
407
}
412
408
413
- private func diskContentsURL( for request: URLRequest , forCreationAt date: Date ? = nil ) -> URL ? {
414
- guard let identifier = self . identifier ( for: request) else { return nil }
415
- guard let directory = cacheDirectory else { return nil }
416
-
417
- var foundURL : URL ?
418
-
419
- enumerateDiskEntries { ( entry, stop) in
420
- if entry. identifier == identifier {
421
- foundURL = entry. url
422
- stop = true
423
- }
409
+ private func diskEntries( includingPropertiesForKeys keys: [ URLResourceKey ] = [ ] ) -> [ DiskEntry ] {
410
+ var entries : [ DiskEntry ] = [ ]
411
+ enumerateDiskEntries ( includingPropertiesForKeys: keys) { ( entry, stop) in
412
+ entries. append ( entry)
424
413
}
414
+ return entries
415
+ }
416
+
417
+ private func diskContentLocators( for request: URLRequest , forCreationAt date: Date ? = nil ) -> ( identifier: String , url: URL ) ? {
418
+ guard let directory = cacheDirectory else { return nil }
419
+ guard let identifier = self . identifier ( for: request) else { return nil }
425
420
426
421
if let date = date {
427
- // If we're trying to _create_ an entry and it already exists, then we can't -- we should evict the old one first.
428
- if foundURL != nil {
429
- return nil
430
- }
431
-
432
- // Create the new URL
422
+ // Create a new URL, which may or may not exist on disk.
433
423
let interval = Int64 ( date. timeIntervalSinceReferenceDate)
434
- return directory. appendingPathComponent ( " \( interval) . \( identifier) . \( DiskEntry . pathExtension) " )
424
+ return ( identifier , directory. appendingPathComponent ( " \( interval) . \( identifier) . \( DiskEntry . pathExtension) " ) )
435
425
} else {
436
- return foundURL
426
+ var foundURL : URL ?
427
+
428
+ enumerateDiskEntries { ( entry, stop) in
429
+ if entry. identifier == identifier {
430
+ foundURL = entry. url
431
+ stop = true
432
+ }
433
+ }
434
+
435
+ if let foundURL = foundURL {
436
+ return ( identifier, foundURL)
437
+ }
437
438
}
439
+
440
+ return nil
438
441
}
439
442
440
443
private func diskContents( for request: URLRequest ) throws -> StoredCachedURLResponse ? {
441
- guard let url = diskContentsURL ( for: request) else { return nil }
444
+ guard let url = diskContentLocators ( for: request) ? . url else { return nil }
442
445
443
446
let data = try Data ( contentsOf: url)
444
447
return try NSKeyedUnarchiver . unarchivedObject ( ofClasses: [ StoredCachedURLResponse . self] , from: data) as? StoredCachedURLResponse
@@ -505,13 +508,28 @@ open class URLCache : NSObject {
505
508
do {
506
509
evictFromDiskCache ( maximumSize: diskCapacity - entry. cost)
507
510
508
- if let oldURL = diskContentsURL ( for: request) {
509
- try FileManager . default. removeItem ( at: oldURL)
511
+ let locators = diskContentLocators ( for: request, forCreationAt: Date ( ) )
512
+ if let newURL = locators? . url {
513
+ try serialized. write ( to: newURL, options: . atomic)
510
514
}
511
515
512
- if let newURL = diskContentsURL ( for: request, forCreationAt: Date ( ) ) {
513
- try serialized. write ( to: newURL, options: . atomic)
516
+ if let identifier = locators? . identifier {
517
+ // Multiple threads and/or processes may be writing the same key at the same time. If writing the contents race for the exact same timestamp, we can't do much about that. (One of the two will exist, due to the .atomic; the other will error out.) But if the timestamps differ, we may end up with duplicate keys on disk.
518
+ // If so, best-effort clear all entries except the one with the highest date.
519
+
520
+ // Refetch a snapshot of the directory contents from disk; do not trust prior state:
521
+ let entriesToRemove = diskEntries ( ) . filter {
522
+ $0. identifier == identifier
523
+ } . sorted {
524
+ $1. date < $0. date
525
+ } . dropFirst ( ) // Keep the one with the latest date.
526
+
527
+ for entry in entriesToRemove {
528
+ // Do not interrupt cleanup if one fails.
529
+ try ? FileManager . default. removeItem ( at: entry. url)
530
+ }
514
531
}
532
+
515
533
} catch { /* Best effort -- do not store on error. */ }
516
534
}
517
535
}
@@ -534,7 +552,7 @@ open class URLCache : NSObject {
534
552
}
535
553
}
536
554
537
- if let oldURL = diskContentsURL ( for: request) {
555
+ if let oldURL = diskContentLocators ( for: request) ? . url {
538
556
try ? FileManager . default. removeItem ( at: oldURL)
539
557
}
540
558
}
@@ -573,15 +591,12 @@ open class URLCache : NSObject {
573
591
}
574
592
575
593
do { // Disk cache:
576
- var urlsToRemove : [ URL ] = [ ]
577
- enumerateDiskEntries { ( entry, stop) in
578
- if entry. date > date {
579
- urlsToRemove. append ( entry. url)
580
- }
594
+ let entriesToRemove = diskEntries ( ) . filter {
595
+ $0. date > date
581
596
}
582
597
583
- for url in urlsToRemove {
584
- try ? FileManager . default. removeItem ( at: url)
598
+ for entry in entriesToRemove {
599
+ try ? FileManager . default. removeItem ( at: entry . url)
585
600
}
586
601
}
587
602
}
0 commit comments