develop
Cache 앱의 캐시 사용하기 본문
반응형
MemoryCache
메모리 캐시는 NSCache를 사용하여 캐시를 관리한다.
메모리를 사용하기 때문에 앱의 메모리가 커지면 캐시가 삭제될 수 있다.
Swift에서는 NSCache라는 클래스가 있다.
https://developer.apple.com/documentation/foundation/nscache
class NSCache<KeyType, ObjectType> : NSObject where KeyType : AnyObject, ObjectType : AnyObject
Key와 Value를 Generic으로 구현되어 있다.
사용 방법은
let cache = NSCache<NSString, UIImage>()
SET
cache.setObject(UIImage(), forKey: "image1")
cache.setObject(UIImage(), forKey: "image2")
cache.setObject(UIImage(), forKey: "image3", cost: 2)
cost는 0이 default이다.
GET
print(cache.object(forKey: "image1"))
print(cache.object(forKey: "image2"))
REMOVE
cache.removeObject(forKey: "image1")
cache.removeObject(forKey: "image2")
cache.removeAllObjects()
LIMIT
cache.countLimit = 50
cache.totalCostLimit = 60
countLimit은 제한 갯수이고
totalCostLimit는 set할때 cost를 인자로 받는데 cost의 총 합이다.
LIMIT에 도달하면 cost기준으로 낮은 cost가 먼저 지워진다.
NSCache는 memory에 저장되고 disk에 저장하려면 FileManager를 사용하여야 한다.
class MemoryCache: NSObject {
static let shared = MemoryCache(name: "cache")
let cache = NSCache<NSString, NSData>()
init(name: String, countLimit: Int = 50) {
self.cache.name = name
self.cache.countLimit = countLimit
}
subscript(_ key: String) -> Data? {
set {
if let data = newValue {
self.cache.setObject(NSData(data: data), forKey: key as NSString)
} else {
self.cache.removeObject(forKey: key as NSString)
}
}
get {
guard let data = self.cache.object(forKey: key as NSString) else { return nil }
return Data(referencing: data)
}
}
func removeAll() {
self.cache.removeAllObjects()
}
}
let memoryCache = MemoryCache(name: "customCache")
memoryCache["http://www.image.com/image1.png"] = Data()
memoryCache["http://www.image.com/image2.png"] = Data()
memoryCache["http://www.image.com/image3.png"] = Data()
MemoryCache.shared["http://www.image.com/image1.png"] = Data()
MemoryCache.shared["http://www.image.com/image2.png"] = Data()
MemoryCache.shared["http://www.image.com/image3.png"] = Data()
DiskCache
디스크 캐시는 데이터를 파일로 저장을 한다.
디스크 캐시를 사용하게 되면 앱의 용량이 커질 수 있다.
DocumentDirectory 가져오기
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
폴더 생성되어 있는지 확인
FileManager.default.fileExists(atPath: folderURL.path)
폴더 생성
FileManager.default.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil)
파일 저장
try? Date().write(to: writeURL)
파일 불러오기
FileManager.default.contents(atPath: writeURL.path)
또는
try? Data(contentsOf: writeURL.absoluteString)
폴더 또는 파일 삭제
try? FileManager.default.removeItem(at: writeURL)
class DiskCache: NSObject {
static let shared = DiskCache(name: "cache")
var folderURL: URL? {
return FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first?.appendingPathComponent(self.name)
}
var name: String = "cache"
var countLimit: Int
var sizeLimit: Int
init(name: String, countLimit: Int = 3, sizeLimit: Int = 1000*1000*100) {
self.name = name
self.countLimit = countLimit
self.sizeLimit = sizeLimit
super.init()
self.createFolder()
}
subscript(_ key: String) -> Data? {
set {
guard let fileURL = URL(string: key) else { return }
guard let writeURL = self.folderURL?.appendingPathComponent(fileURL.lastPathComponent) else { return }
if let data = newValue {
try? data.write(to: writeURL)
} else {
try? FileManager.default.removeItem(at: writeURL)
}
self.sweep()
}
get {
guard let fileURL = URL(string: key) else { return nil }
guard let writeURL = self.folderURL?.appendingPathComponent(fileURL.lastPathComponent) else { return nil }
return FileManager.default.contents(atPath: writeURL.path)
}
}
func removeAll() {
if let folderURL = self.folderURL {
try? FileManager.default.removeItem(at: folderURL)
}
self.createFolder()
}
private func createFolder() {
guard let folderURL = self.folderURL else { return }
if FileManager.default.fileExists(atPath: folderURL.path) { return }
try? FileManager.default.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil)
}
struct Entry {
let url: URL
let meta: URLResourceValues
init?(url: URL?, meta: URLResourceValues?) {
guard let url = url else { return nil }
guard let meta = meta else { return nil }
self.url = url
self.meta = meta
}
}
private func sweep() {
guard let folderURL = self.folderURL else { return }
let keys: [URLResourceKey] = [.contentAccessDateKey, .totalFileAllocatedSizeKey, .contentModificationDateKey]
guard let urls = try? FileManager.default.contentsOfDirectory(at: folderURL, includingPropertiesForKeys: keys, options: .skipsHiddenFiles) else { return }
if urls.isEmpty { return }
let past = Date.distantPast
let forKeys = Set(keys)
var entrys = urls.compactMap { (url) -> Entry? in
return Entry(url: url, meta: try? url.resourceValues(forKeys: forKeys))
}.sorted(by: {
let firstModDate = $0.meta.contentModificationDate ?? past
let firstAccDate = $0.meta.contentAccessDate ?? past
let firstDate = firstModDate > firstAccDate ? firstModDate : firstAccDate
let lastModDate = $1.meta.contentModificationDate ?? past
let lastAccDate = $1.meta.contentAccessDate ?? past
let lastDate = lastModDate > lastAccDate ? lastModDate : lastAccDate
return firstDate > lastDate
})
var count = entrys.count
var totalSize = entrys.reduce(0, { $0 + ($1.meta.totalFileAllocatedSize ?? 0) })
guard count > self.countLimit || totalSize > self.sizeLimit else { return }
while ( count > self.countLimit || totalSize > self.sizeLimit ), let entry = entrys.popLast() {
count -= 1
totalSize -= (entry.meta.totalFileAllocatedSize ?? 0)
try? FileManager.default.removeItem(at: entry.url)
}
}
}
let diskCache = DiskCache(name: "customCache")
diskCache["http://www.image.com/image1.png"] = Data()
diskCache["http://www.image.com/image2.png"] = Data()
diskCache["http://www.image.com/image3.png"] = Data()
DiskCache.shared["http://www.image.com/image1.png"] = Data()
DiskCache.shared["http://www.image.com/image2.png"] = Data()
DiskCache.shared["http://www.image.com/image3.png"] = Data()
MemoryCache와 DiskCache를 같이 쓰고 MemoryCache에 데이터가 없으면 DiskCache에 데이터를 조회하는 코드이다.
DiskCache를 사용하려면 FileManager를 이용하여야 한다.
class Cache: NSObject {
static let shared = Cache(name: "cache")
let memoryCache: MemoryCache
let diskCache: DiskCache
init(name: String, countLimit: Int = 3, sizeLimit: Int = 1000*1000*100) {
self.memoryCache = MemoryCache(name: name, countLimit: countLimit)
self.diskCache = DiskCache(name: name, countLimit: countLimit, sizeLimit: sizeLimit)
}
subscript(_ key: String) -> Data? {
set {
self.memoryCache[key] = newValue
self.diskCache[key] = newValue
}
get {
if let data = self.memoryCache[key] {
return data
} else {
return self.diskCache[key]
}
}
}
func removeAll() {
self.memoryCache.removeAll()
self.diskCache.removeAll()
}
}
let cache = Cache(name: "customCache")
cache["http://www.image.com/image1.png"] = Data()
cache["http://www.image.com/image2.png"] = Data()
cache["http://www.image.com/image3.png"] = Data()
Cache.shared["http://www.image.com/image1.png"] = Data()
Cache.shared["http://www.image.com/image2.png"] = Data()
Cache.shared["http://www.image.com/image3.png"] = Data()
반응형
'iOS' 카테고리의 다른 글
autoreleasepool (0) | 2021.01.25 |
---|---|
BackgroundFetch 앱 백그라운드 상태에서 로직 실행하기 (0) | 2021.01.24 |
Generic (0) | 2021.01.21 |
Throttle, Debounce (0) | 2021.01.20 |
Declaration Attributes (0) | 2021.01.19 |
Comments