NSCache
mutable Collection 으로 key-value쌍을 임시로 저장하는데 사용된다.
메모리캐시의 장점
캐시를 사용하여 비용이 많이 드는 데이터를 메모리에 임시로 저장 및 로드 한다.
메모리에 저장된 데이터는 접근이 빠르고 다시 계산할 필요가 없어 성능의 이점이 있다.
메모리캐시의 단점
많은 대용량 데이터를 캐싱할 때 다른 애플리케이션을 위한 RAM이 남아 있지 않을 정도 많은 객체를 캐시할 수 있다.
RAM을 확보하기 위해 애플리케이션을 종료할 수 있다.
자동 제거 정책
NSCache는 자체적으로 시스템 메모리를 너무 많이 사용하지 않도록 자동으로 제거되는 정책을 소유한다.
다른 응용 프로그램에서 메모리가 필요한 경우 이러한 정책은 캐시에서 일부 항목을 제거하여 메모리 사용 공간을 최소화한다.
객체와 달리 캐시는 저장된 key 객체를 복사하지 않는 특징이 존재
디폴트로 캐시 객체는 컨텐츠가 삭제되면 자동으로 제거 (변경 가능)
Header의 정책
NSURLCache는 ‘Cache-Control’ HTTP-Header 가 결정한다.
HTTP-Header에 설정가능한 케쉬 관련 표준
NSURLCacheStoragePolicy
캐싱 막기
캐싱을 끄기 위해서, 다음의 디렉티브들을 보낼 수 있다.
Cache-Control: no-cache, no-store, must-revalidate
정적 에셋 캐싱
변경되지 않을 애플리케이션 내 파일들에 대해, 보통 적극적인 캐싱을 추가할 수 있다. 이것은 예를 들자면, 이미지, CSS 파일 그리고 자바스크립트 파일과 같이 애플리케이션에 의해 서브되는 정적 파일들을 포함한다.
Cache-Control:public, max-age=31536000
추가로, Expires 헤더를 참고하자.
Source
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
private class NSCacheEntry<KeyType : AnyObject, ObjectType : AnyObject> {
var key: KeyType
var value: ObjectType
var cost: Int
// Linked List를 사용하여 데이터 추가 및 삭제에 효율적으로 동작한다.
// 탐색 시간 복잡도: O(n)
// 노드가 있기 때문에 데이터의 추가, 삭제로부터 자유롭다.
var prevByCost: NSCacheEntry?
var nextByCost: NSCacheEntry?
init(key: KeyType, value: ObjectType, cost: Int) {
self.key = key
self.value = value
self.cost = cost
}
}
fileprivate class NSCacheKey: NSObject {
var value: AnyObject // NSMutableDictionary 객체와 달리 캐시는 그 안에 들어 있는 key/object를 복사하지 않는다.
init(_ value: AnyObject) {
self.value = value
super.init()
}
override var hash: Int {
switch self.value {
case let nsObject as NSObject:
return nsObject.hashValue
case let hashable as AnyHashable:
return hashable.hashValue
default: return 0
}
}
override func isEqual(_ object: Any?) -> Bool {
guard let other = (object as? NSCacheKey) else { return false }
if self.value === other.value {
return true
} else {
guard let left = self.value as? NSObject,
let right = other.value as? NSObject else { return false }
return left.isEqual(right)
}
}
}
open class NSCache<KeyType : AnyObject, ObjectType : AnyObject> : NSObject {
// 별도의 Dictionary를 두어 데이터 접근에도 용이합니다. 데이터 접근 시간복잡도: O(1)
private var _entries = Dictionary<NSCacheKey, NSCacheEntry<KeyType, ObjectType>>()
private let _lock = NSLock()
private var _totalCost = 0
private var _head: NSCacheEntry<KeyType, ObjectType>?
open var name: String = ""
// 캐시가 보유할 수 있는 최대 비용을 설정할 수 있다.
// 캐시가 최대 cost를 초과하면 제거될 수 있다. 기본 값은 0으로 비용 제한이 없다.
open var totalCostLimit: Int = 0 // limits are imprecise/not strict
// 캐시에 허용되는 최대 객체 개수를 제한할 수 있다.
// 캐시 개수가 이 값을 초과하면 제거될 수 있습니다. 기본 값은 0으로 개수 제한이 없다.
open var countLimit: Int = 0 // limits are imprecise/not strict
open var evictsObjectsWithDiscardedContent: Bool = false
public override init() {}
open weak var delegate: NSCacheDelegate?
open func object(forKey key: KeyType) -> ObjectType? {
var object: ObjectType?
let key = NSCacheKey(key)
_lock.lock() // 캐시를 직접 lock 하지 않아도 다른 쓰레드에서 캐시의 항목을 추가, 제거 및 쿼리할 수 있다.
if let entry = _entries[key] {
object = entry.value
}
_lock.unlock()
return object
}
open func setObject(_ obj: ObjectType, forKey key: KeyType) {
setObject(obj, forKey: key, cost: 0)
}
private func remove(_ entry: NSCacheEntry<KeyType, ObjectType>) {
let oldPrev = entry.prevByCost
let oldNext = entry.nextByCost
oldPrev?.nextByCost = oldNext
oldNext?.prevByCost = oldPrev
if entry === _head {
_head = oldNext
}
}
private func insert(_ entry: NSCacheEntry<KeyType, ObjectType>) {
guard var currentElement = _head else {
// The cache is empty
entry.prevByCost = nil
entry.nextByCost = nil
_head = entry
return
}
guard entry.cost > currentElement.cost else {
// Insert entry at the head
entry.prevByCost = nil
entry.nextByCost = currentElement
currentElement.prevByCost = entry
_head = entry
return
}
while let nextByCost = currentElement.nextByCost, nextByCost.cost < entry.cost {
currentElement = nextByCost
}
// Insert entry between currentElement and nextElement
let nextElement = currentElement.nextByCost
currentElement.nextByCost = entry
entry.prevByCost = currentElement
entry.nextByCost = nextElement
nextElement?.prevByCost = entry
}
open func setObject(_ obj: ObjectType, forKey key: KeyType, cost g: Int) {
let g = max(g, 0)
let keyRef = NSCacheKey(key)
_lock.lock()
let costDiff: Int
if let entry = _entries[keyRef] {
costDiff = g - entry.cost
entry.cost = g
entry.value = obj
if costDiff != 0 {
remove(entry)
insert(entry)
}
} else {
let entry = NSCacheEntry(key: key, value: obj, cost: g)
_entries[keyRef] = entry
insert(entry)
costDiff = g
}
_totalCost += costDiff
var purgeAmount = (totalCostLimit > 0) ? (_totalCost - totalCostLimit) : 0
while purgeAmount > 0 {
if let entry = _head {
delegate?.cache(unsafeDowncast(self, to:NSCache<AnyObject, AnyObject>.self), willEvictObject: entry.value)
_totalCost -= entry.cost
purgeAmount -= entry.cost
remove(entry) // _head will be changed to next entry in remove(_:)
_entries[NSCacheKey(entry.key)] = nil
} else {
break
}
}
var purgeCount = (countLimit > 0) ? (_entries.count - countLimit) : 0
while purgeCount > 0 {
if let entry = _head {
delegate?.cache(unsafeDowncast(self, to:NSCache<AnyObject, AnyObject>.self), willEvictObject: entry.value)
_totalCost -= entry.cost
purgeCount -= 1
remove(entry) // _head will be changed to next entry in remove(_:)
_entries[NSCacheKey(entry.key)] = nil
} else {
break
}
}
_lock.unlock()
}
open func removeObject(forKey key: KeyType) {
let keyRef = NSCacheKey(key)
_lock.lock()
if let entry = _entries.removeValue(forKey: keyRef) {
_totalCost -= entry.cost
remove(entry)
}
_lock.unlock()
}
open func removeAllObjects() {
_lock.lock()
_entries.removeAll()
while let currentElement = _head {
let nextElement = currentElement.nextByCost
currentElement.prevByCost = nil
currentElement.nextByCost = nil
_head = nextElement
}
_totalCost = 0
_lock.unlock()
}
}
public protocol NSCacheDelegate : NSObjectProtocol {
func cache(_ cache: NSCache<AnyObject, AnyObject>, willEvictObject obj: Any)
}
extension NSCacheDelegate {
func cache(_ cache: NSCache<AnyObject, AnyObject>, willEvictObject obj: Any) {
// Default implementation does nothing
}
}
댓글남기기