-
Notifications
You must be signed in to change notification settings - Fork 1.2k
URLCache sketches. Mainly memory cache. #2312
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -129,6 +129,9 @@ open class CachedURLResponse : NSObject, NSSecureCoding, NSCopying { | |
open class URLCache : NSObject { | ||
|
||
/*! | ||
private static let _sharedCacheLock: NSLock = NSLock() | ||
private static var _sharedCache: URLCache! | ||
|
||
@method sharedURLCache | ||
@abstract Returns the shared URLCache instance. | ||
@discussion Unless set explicitly, this method returns an URLCache | ||
|
@@ -147,14 +150,27 @@ open class URLCache : NSObject { | |
*/ | ||
open class var shared: URLCache { | ||
get { | ||
NSUnimplemented() | ||
_sharedCacheLock.lock() | ||
defer { _sharedCacheLock.unlock() } | ||
if _sharedCache == nil { | ||
// TODO: diskPath? | ||
_sharedCache = URLCache(memoryCapacity: 4 * 1024 * 1024, diskCapacity: 20 * 1024 * 1024, diskPath: nil) | ||
} | ||
|
||
return _sharedCache | ||
} | ||
set { | ||
NSUnimplemented() | ||
_sharedCacheLock.lock() | ||
defer { _sharedCacheLock.unlock() } | ||
_sharedCache = newValue | ||
} | ||
} | ||
|
||
/*! | ||
private let _memoryCache: _URLMemoryCache<_URLCacheKey, CachedURLResponse> | ||
private let _diskPath: String | ||
private let _diskCache: _URLDiskCache<_URLCacheKey, CachedURLResponse> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also happens here. The variables should be outside the comment, not inside. |
||
|
||
@method initWithMemoryCapacity:diskCapacity:diskPath: | ||
@abstract Initializes an URLCache with the given capacity and | ||
path. | ||
|
@@ -167,7 +183,11 @@ open class URLCache : NSObject { | |
@result an initialized URLCache, with the given capacity, backed | ||
by disk. | ||
*/ | ||
public init(memoryCapacity: Int, diskCapacity: Int, diskPath path: String?) { NSUnimplemented() } | ||
public init(memoryCapacity: Int, diskCapacity: Int, diskPath path: String?) { | ||
self._diskPath = path ?? "/" // TODO? | ||
self._memoryCache = _URLMemoryCache(capacity: memoryCapacity) | ||
self._diskCache = _URLDiskCache(capacity: diskCapacity, path: self._diskPath) | ||
} | ||
|
||
/*! | ||
@method cachedResponseForRequest: | ||
|
@@ -180,7 +200,13 @@ open class URLCache : NSObject { | |
request, or nil if there is no NSCachedURLResponse stored with the | ||
given request. | ||
*/ | ||
open func cachedResponse(for request: URLRequest) -> CachedURLResponse? { NSUnimplemented() } | ||
open func cachedResponse(for request: URLRequest) -> CachedURLResponse? { | ||
guard let key = _URLCacheKey(request: request) else { | ||
return nil | ||
} | ||
|
||
return _memoryCache[key] ?? _diskCache[key] | ||
} | ||
|
||
/*! | ||
@method storeCachedResponse:forRequest: | ||
|
@@ -189,7 +215,25 @@ open class URLCache : NSObject { | |
@param cachedResponse The cached response to store. | ||
@param request the NSURLRequest to use as a key for the storage. | ||
*/ | ||
open func storeCachedResponse(_ cachedResponse: CachedURLResponse, for request: URLRequest) { NSUnimplemented() } | ||
open func storeCachedResponse(_ cachedResponse: CachedURLResponse, for request: URLRequest) { | ||
guard cachedResponse.storagePolicy != .notAllowed else { | ||
// Don't store. But don't remove - may be cache reuse case. | ||
return | ||
} | ||
|
||
guard let key = _URLCacheKey(request: request) else { | ||
return | ||
} | ||
|
||
_memoryCache[key] = cachedResponse | ||
if cachedResponse.storagePolicy != .allowedInMemoryOnly { | ||
_diskCache[key] = cachedResponse | ||
} else { | ||
// If we are not supposed to write to disk, ensure any existing | ||
// entry is removed. | ||
_diskCache[key] = nil | ||
} | ||
} | ||
|
||
/*! | ||
@method removeCachedResponseForRequest: | ||
|
@@ -199,14 +243,24 @@ open class URLCache : NSObject { | |
stored with the given request. | ||
@param request the NSURLRequest to use as a key for the lookup. | ||
*/ | ||
open func removeCachedResponse(for request: URLRequest) { NSUnimplemented() } | ||
open func removeCachedResponse(for request: URLRequest) { | ||
guard let key = _URLCacheKey(request: request) else { | ||
return | ||
} | ||
|
||
_memoryCache[key] = nil | ||
_diskCache[key] = nil | ||
} | ||
|
||
/*! | ||
@method removeAllCachedResponses | ||
@abstract Clears the given cache, removing all NSCachedURLResponse | ||
objects that it stores. | ||
*/ | ||
open func removeAllCachedResponses() { NSUnimplemented() } | ||
open func removeAllCachedResponses() { | ||
_memoryCache.removeAll() | ||
_diskCache.removeAll() | ||
} | ||
|
||
/*! | ||
@method removeCachedResponsesSince: | ||
|
@@ -220,15 +274,21 @@ open class URLCache : NSObject { | |
@discussion At the time this call is made, the in-memory cache will truncate its contents to the size given, if necessary. | ||
@result The in-memory capacity, measured in bytes, for the receiver. | ||
*/ | ||
open var memoryCapacity: Int | ||
open var memoryCapacity: Int { | ||
get { return _memoryCache.capacity } | ||
set { _memoryCache.capacity = newValue } | ||
} | ||
|
||
/*! | ||
@method diskCapacity | ||
@abstract The on-disk capacity of the receiver. | ||
@discussion At the time this call is made, the on-disk cache will truncate its contents to the size given, if necessary. | ||
@param diskCapacity the new on-disk capacity, measured in bytes, for the receiver. | ||
*/ | ||
open var diskCapacity: Int | ||
open var diskCapacity: Int { | ||
get { return _diskCache.capacity } | ||
set { _diskCache.capacity = newValue } | ||
} | ||
|
||
/*! | ||
@method currentMemoryUsage | ||
|
@@ -238,7 +298,9 @@ open class URLCache : NSObject { | |
usage of the in-memory cache. | ||
@result the current usage of the in-memory cache of the receiver. | ||
*/ | ||
open var currentMemoryUsage: Int { NSUnimplemented() } | ||
open var currentMemoryUsage: Int { | ||
return _memoryCache.currentCost | ||
} | ||
|
||
/*! | ||
@method currentDiskUsage | ||
|
@@ -248,11 +310,213 @@ open class URLCache : NSObject { | |
usage of the on-disk cache. | ||
@result the current usage of the on-disk cache of the receiver. | ||
*/ | ||
open var currentDiskUsage: Int { NSUnimplemented() } | ||
open var currentDiskUsage: Int { | ||
return _diskCache.currentCost | ||
} | ||
} | ||
|
||
extension URLCache { | ||
public func storeCachedResponse(_ cachedResponse: CachedURLResponse, for dataTask: URLSessionDataTask) { NSUnimplemented() } | ||
public func getCachedResponse(for dataTask: URLSessionDataTask, completionHandler: (CachedURLResponse?) -> Void) { NSUnimplemented() } | ||
public func removeCachedResponse(for dataTask: URLSessionDataTask) { NSUnimplemented() } | ||
} | ||
|
||
|
||
private struct _URLCacheKey: Hashable { | ||
private let value: Int | ||
|
||
init?(request: URLRequest) { | ||
// TODO: find out the protocol, find out if there's a handler, find out | ||
// if it is cacheable (data, file, ... are not) | ||
|
||
|
||
var hasher = Hasher() | ||
hasher.combine(request) // TODO: we should not hash everything | ||
value = hasher.finalize() | ||
} | ||
|
||
func hash(into hasher: inout Hasher) { | ||
hasher.combine(value) | ||
} | ||
} | ||
|
||
|
||
private protocol _URLCacheEntry { | ||
var cost: Int { get } | ||
} | ||
|
||
|
||
extension CachedURLResponse: _URLCacheEntry { | ||
var cost: Int { | ||
return data.count | ||
} | ||
} | ||
|
||
|
||
private class _URLMemoryCache<Key: Hashable, Value: _URLCacheEntry> { | ||
private class Entry<Value> { | ||
let key: Key | ||
let value: Value | ||
let cost: Int | ||
var index: Array<Entry<Value>>.Index! | ||
// var prev: Entry<Value>? = nil | ||
// var next: Entry<Value>? = nil | ||
|
||
init(key: Key, value: Value, cost: Int) { | ||
self.key = key | ||
self.value = value | ||
self.cost = cost | ||
} | ||
} | ||
|
||
private var _entries: [Key: Entry<Value>] | ||
private var _lruList: [Entry<Value>] | ||
private let _lock: NSLock | ||
|
||
init(capacity: Int) { | ||
self._capacity = capacity | ||
self._entries = [:] | ||
self._lruList = [] | ||
self._currentCost = 0 | ||
self._lock = NSLock() | ||
} | ||
|
||
private var _capacity: Int | ||
var capacity: Int { | ||
get { | ||
_lock.lock() | ||
defer { _lock.unlock() } | ||
|
||
return _capacity | ||
} | ||
set { | ||
_lock.lock() | ||
defer { _lock.unlock() } | ||
|
||
_capacity = newValue | ||
_performGarbageCollectionWhileLocked() | ||
} | ||
} | ||
|
||
private var _currentCost: Int | ||
var currentCost: Int { | ||
get { | ||
_lock.lock() | ||
defer { _lock.unlock() } | ||
|
||
return _currentCost | ||
} | ||
} | ||
|
||
subscript(_ key: Key) -> Value? { | ||
get { | ||
_lock.lock() | ||
defer { _lock.unlock() } | ||
|
||
let entry = _findEntryWhileLocked(key) | ||
return entry?.value; | ||
} | ||
set { | ||
// TODO | ||
// Check if the size is OK for the memory cache, otherwise remove | ||
|
||
_lock.lock() | ||
defer { _lock.unlock() } | ||
|
||
if let existing = _findEntryWhileLocked(key) { | ||
_removeWhileLocked(existing) | ||
} | ||
|
||
guard let newValue = newValue else { | ||
return | ||
} | ||
|
||
let entry = Entry(key: key, value: newValue, cost: 1) // TODO: cost | ||
_entries[key] = entry | ||
entry.index = _lruList.endIndex | ||
_lruList.insert(entry, at: entry.index) | ||
_currentCost += 1 // TODO: cost | ||
_performGarbageCollectionWhileLocked() | ||
} | ||
} | ||
|
||
func removeAll() { | ||
_lock.lock() | ||
defer { _lock.unlock() } | ||
|
||
while let entry = _lruList.first { | ||
_removeWhileLocked(entry) | ||
} | ||
} | ||
|
||
private func _performGarbageCollectionWhileLocked() { | ||
while _currentCost > _capacity, let entry = _lruList.first { | ||
_removeWhileLocked(entry) | ||
} | ||
} | ||
|
||
private func _removeWhileLocked(_ entry: Entry<Value>) { | ||
_entries[entry.key] = nil | ||
_lruList.remove(at: entry.index) | ||
_currentCost -= entry.cost | ||
} | ||
|
||
private func _findEntryWhileLocked(_ key: Key) -> Entry<Value>? { | ||
guard let entry = _entries[key] else { | ||
return nil | ||
} | ||
|
||
_lruList.remove(at: entry.index) | ||
entry.index = _lruList.endIndex | ||
_lruList.insert(entry, at: entry.index) | ||
|
||
return entry | ||
} | ||
} | ||
|
||
|
||
private class _URLDiskCache<Key: Hashable, Value: NSCoding> { | ||
private let _path: String | ||
|
||
init(capacity: Int, path: String) { | ||
self._capacity = capacity | ||
self._path = path | ||
self._currentCost = 0 // TODO: read from disk | ||
|
||
// TODO: run capacity setter code? | ||
} | ||
|
||
private var _capacity: Int | ||
var capacity: Int { | ||
get { | ||
// TODO | ||
return _capacity | ||
} | ||
set { | ||
// TODO | ||
_capacity = newValue | ||
} | ||
} | ||
|
||
private var _currentCost: Int | ||
var currentCost: Int { | ||
get { | ||
// TODO | ||
return _currentCost | ||
} | ||
} | ||
|
||
subscript(_ key: Key) -> Value? { | ||
get { | ||
// TODO | ||
return nil | ||
} | ||
set { | ||
// TODO | ||
} | ||
} | ||
|
||
func removeAll() { | ||
// TODO | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was moving commits between branches. The documentation comment should go below the two private variables. I didn’t review the code before pushing, sorry about that.