
이번시간엔 Disk, Memory 캐시에 핵심 포인트를 정리해 보려고 합니다.
- 네트워크 요청 횟수를 줄이기
- 이미지 로딩 체감을 개선하기
- 앱 종료 이후에도 캐시를 유지
- 메타데이터를 갱신하여 회신화 하기
NSCache 기반의 인메모리 캐시 방식totalCostLimit 기준으로 시스템에서 자동 제거 -> 메모리 압박 상황관리public init(totalCostLimit: Int = 64 * 1024 * 1024) {
self.cache = NSCache<NSString, EntryBox>()
self.cache.totalCostLimit = totalCostLimit
}
FileManager 기반의 디스크 캐시private struct LastPath {
static let data = ".data" // 이미지 데이터
static let metadata = ".meta.json" // 갱신정보들
}
public var filenameSafeHash: String {
let data = Data(rawValue.utf8)
let digest = SHA256.hash(data: data)
return digest.map { String(format: "%02x", $0) }.joined()
}
<hash>.data는 이미지 원본 데이터<hash>.meta.json은 CacheMetadata를 JSON으로 저장public struct CacheMetadata: CacheSerializer {
public let originalUrlString: String
public let createdDate: Date
public var eTag: ETagCache?
public var accessCount: Int
public var lastAccessTime: Date
public var lastModified: Date?
}
디스크 캐시에서 접근 정보를 기록하여 pruning 기준으로 사용됩니다.
마지막 접근 시간과 접근 횟수는 LRU, 유통기한을 통해 정리할때 활용됩니다.
touch()를 호출합니다.accessCount와 lastAccessTime을 최신화하여 LRU 기준을 유지합니다.public mutating func touch() {
accessCount += 1
lastAccessTime = Date()
}
metadata.touch()로 접근 정보를 갱신합니다.private func readEntry(for key: CacheKey) -> CacheEntry<CacheMetadata>? {
// ... load data/metadata
metadata.touch()
// ... re-save metadata
return CacheEntry(data: data, metadata: metadata)
}
DiskAccessRecorder에 쌓아둡니다.disk.touch(keys)를 호출합니다.flush()로 반영합니다.public func get(_ key: CacheKey) async -> CacheEntry<CacheMetadata>? {
if let entry = await memory.get(key) {
Task { await accessRecorder.record(key) }
return entry
}
// ...
}
func record(_ key: CacheKey) {
pending.insert(key)
scheduleFlushIfNeeded()
}
if now.timeIntervalSince(item.lastAccessTime) > ageLimit {
try? fileManager.removeItem(at: item.dataURL)
try? fileManager.removeItem(at: item.metaURL)
}
...
for item in items where totalSize > diskLimit {
try? fileManager.removeItem(at: item.dataURL)
try? fileManager.removeItem(at: item.metaURL)
totalSize -= item.size
removed += 1
}

이번엔 저번편에 이어서
좀더 구현쪽에 가깝게 정리를 해보았습니다.
전체를 설명하기 보단 핵심 설명과, 흐름을 다시한번 자세히 정리하는게
좋을 것 같다는 생각이 들었어서 이번편은 여기서 마무리 지어보오록 하겠습니다.
모두 고생 하셨습니다.