캐시
캐시란 자주 사용하는 데이터나 값을 미리 복사해 놓는 임시 저장소이다.
갑자기 iOS에서 이 개념이 왜 나올까 ??
부스트 캠프 과정 중 앱 개발을 할 때 항상 비용을 고려해야한다.
비용은 크게 2가지로 나눠진다
- 비용 Money
- 전원 Power
전원 Power는 말 그대로 우리 서비스를 이용할 때 배터리 소모량이 된다.
비용 Money은 우리 서비스를 이용하면서 유저가 지불해야하는 금액적인 비용이다.
금액적인 비용은 우리 서비스내의 제품을 구매할 때도 역시 필요하지만
앱이 네트워크가 필요하여 인터넷 비용도 있다.
우리가 컨트롤 할 수 있는 것은 바로 인터넷 비용이다.
우리는 유저의 인터넷비용을 최대한 기술적으로 절감시켜 유저 이탈 수를 막야한다.
대표적인 예로 바로 캐시 데이터를 통해 불필요한 네트워크 통신을 막아 비용을 절감시킬 수 있다.
우리는 오늘 이미지를 캐싱하여 불필요한 이미지를 불러오는 작업을 막아보자.
우리가 사용할 캐시는 크게 두가지 영역이다.
- Memoery Cache
- 애플리케이션 메모리 영역의 일부분을 캐싱
- 앱이 종료되면 사라지는 휘발성 캐시
- NSCache, URLCache 존재
- Disk Cache
- 데이터를 파일 형태로 디스크에 저장
- 반복적으로 발생하면 애플리케이션이 차지하는 용량이 커지는 단점
- 앱이 종료되어도 데이터가 사라지지 않는 비휘발성 캐시
- FileManager를 이용한다.
NSCache vs URLCache
NSCache
A mutable collection you use to temporarily store transient key-value pairs that are subject to eviction when resources are low.
class NSCache<KeyType, ObjectType> : NSObject where KeyType : AnyObject, ObjectType : AnyObject
정의
- iOS 애플리케이션에서 Memoery Caching에 주로 사용되는 클래스이다.
- key - value 형태의 임시 저장 가변 컬렉션
특징
1. 캐시를 잠글 필요 없이 별도 스레드에서 캐시 항목을 추가/ 삭제/ 검색 할 수 있다.
2. NSMutableDictionary 객체와 달리 키 객체를 복사하지 않는다.
- key-value 형태의 데이터를 임시로 저장할 때 NSMutableDictionary 가변 컬렉션이 있다.
- 그렇다면 차이가 NSCache와 차이가 뭘까 ??
- NSCache는 키 객체를 복사하지 않고 NSMutableDictionary을 key 객체가 복사된다.
- 차이는 다음 코드를 살펴보자, 출처는 밑의 stack over flow를 살펴보자.
- NSMutableDictionary은 key 변수에 새로운 값이 할당되어도 이전 key에 대한 값이 남아있고
- NSCache는 key 변수에 새로운 값이 할당되면 이전 키값에 대한 값이 남아 있지 않는다.
et mutableDic = NSMutableDictionary()
var dicKey: NSMutableString = "key" // K₁
mutableDic.setObject("one", forKey: dicKey) // K₂
dicKey.setString("changedKey") // still K₁
mutableDic.setObject("two", forKey: dicKey) // K₃
print(mutableDic.object(forKey: "key") ?? "") // "one"
print(mutableDic.object(forKey: "changedKey") ?? "") // "two"
// BUT:
let cache = NSCache<NSString, NSString>()
var cacheKey: NSMutableString = "key" // K₁
cache.setObject("one", forKey: cacheKey) // still K₁
cacheKey.setString("changedKey") // still K₁
cache.setObject("two", forKey: cacheKey) // still K₁!
print(cache.object(forKey: "key") ?? "") // "" !!!
print(cache.object(forKey: "changedKey") ?? "") // "two"
3. 연결리스트와 Dictionary를 함께 사용한다.
- 캐싱은 중간에 있는 데이터 추가/ 삭제가 빈번하게 발생할 수 있어 배열을 사용하면 시간복잡도가 크게 증가한다.
- 이를 해결하기위해 연결리스트를 도입, 하지만 연결리스트는 key-value 타입이 아니기 때문에 탐색에 (O(n)) 이 발생
- 그렇기 때문에 동시에 Dictionary를 도입하여 탐색을 (O(1))로 만든다.
4. 메모리를 과도하게 사용하지 않도록 자동 제거 정책이 있다.
- limitation을 넘어갈 경우 적은 용량(cost)의 데이터 부분 부터 삭제한다.
- 삭제될 수 있는 하위 구성요들은 NSDiscardableContent 프로토콜을 채택하여 캐시 제거 동작을 향상 시킬 수 있다.
- 기본적으로 캐시의 NSDiscardableContent 객체는 내용이 삭제될 경우 자동으로 제거되지만 이 자동 제거 정책은 변경할 수 있다.
- NSDiscardableContent 객체가 캐시에 저장되면 캐시는 삭제 명령 수행시 discardContentIfPossible()이 호출된다.
- NSDiscardableContent 객체의 삭제는 연결리스트를 통해서 삭제가 진행되는데 이 연결리스트는 cost로 정렬된 연결 리스트다. head부터 삭제가 진행되기 때문에 결과론적으로는 적은 cost의 데이터부터 삭제되게 된다.
- 의도는 명확히 알 수 없지만 용량이 큰 데이터의 경우 다시 계산되어야 할 때의 비용이 높기 때문에 적은 용량의 캐싱데이터부터 삭제하는 건 아닐까
Topics
Managing the Name
// 캐시의 이름
var name: String
Managing Cache Size
// 캐시가 가질 수 있는 최대한의 객체 수
var countLimit: Int
// 객체를 제거하기 전에 캐시가 보유할 수 있는 최대 비용
var totalCostLimit: Int
Managing Discardable Content
// 캐시가 내용이 삭제된 NSDiscardableContent를 자동으로 제거할지의 여부
var evictsObjectsWithDiscardedContent: Bool
// 클래스의 객체의 하위 구성 요소가 사용되지 않을 때 삭제되어도 된다면 이 프로토콜을 채택함으로써 응용 프로그램의 메모리 사용 공간을 줄일 수 있다.
protocol NSDiscardableContent
Managing the Delegate
// 캐시의 위임자
var delegate: NSCacheDelegate?
// NSCache의 위임자 객체는 이 프로토콜을 채택한다.
이 프로토콜을 채택하면 캐시에서 객체가 추출되거나 제거될 때의 작업들을 특수화할 수 있다.
protocol NSCacheDelegate
Getting a Cached value
// 주어진 Key과 연결된 값을 반환
func object(forKey: KeyType) -> ObjectType?
Adding and Removing Cached Values
// 캐시에 주어진 키와 그에 대응되는 값을 저장
func setObject(ObjectType, forKey: KeyType)
// 비용을 명시하여 값을 저장.
func setObject(ObjectType, forKey: KeyType, cost: Int)
// 캐시에 주어진 키에 대응되는 값을 제거
func removeObject(forKey: KeyType)
// 캐시 비움
removeAllObjects()
URLCache
An object that maps URL requests to cached response objects.
class URLCache : NSObject
정의
URL 요청으로 캐시된 response를 매핑해주는 캐시 객체
특징
1. 메모리와 디스크 상 복합 캐시를 제공할 수 있으며 두 부분의 크기를 제어할 수 잇다.
2. Thread Safe
- 동시에 여러 실행 컨텍스트에서 안전하게 호출될 수 있지만 동일한 요청에 대한 응답을 읽고 쓸 때 race condition이 발생할 수 있다
- 그럴경우 URLCache의 서브클래스는 스레드로부터 안전한 방식으로 재정의된 메서드를 구현해야한다.
Topics
Getting and setting shared cache
// 공유 URL 캐시 인스턴스
class var shared: URLCache
Creating a new cache object
// 지정된 메모리 및 디스크 용량과 디렉토리를 사용하여 URL 캐시 객체를 생성합니다.
init(memoryCapacity: Int, diskCapacity: Int, directory: URL?)
Getting and storing cached objects
// 지정된 URL 요청에 대해 캐시에 저장된 URL 응답을 반환
func cachedResponse(for: URLRequest) -> CachedURLResponse?
// 스레드 안전한 방식, 데이터 작업에 대한 캐시된 URL 응답을 가져와 제공된 완료 핸들러에 전달한다.
func getCachedResponse(for: URLSessionDataTask, completionHandler: (CachedURLResponse?) -> Void)
// 지정된 요청에 대해 캐시된 URL 응답을 저장
func storeCachedResponse(CachedURLResponse, for: URLRequest)
// 스레드 안전한 방식, 지정된 데이터 작업에 대해 캐시된 URL 응답을 저장합니다.
func storeCachedResponse(CachedURLResponse, for: URLSessionDataTask)
Removing cached objects
// 지정된 URL 요청에 대한 캐시된 URL 응답을 제거
func removeCachedResponse(for: URLRequest):
// 스레드 안전한 방식, 지정된 데이터 작업에 대한 캐시된 URL 응답을 제거
func removeCachedResponse(for: URLSessionDataTask)
// 지정된 날짜 이후에 캐시된 모든 응답을 제거
func removeCachedResponses(since: Date):
// 캐시에 저장된 모든 URL 응답을 제거
func removeAllCachedResponses():
Getting and setting on-disk cache properties
// 디스크 캐시의 현재 크기(바이트)입니다.
var currentDiskUsage: Int:
// 디스크 캐시의 용량(바이트)입니다.
var diskCapacity: Int:
Getting and setting in-memoery cache properties
// 메모리 캐시의 현재 크기(바이트)
var currentMemoryUsage: Int
// 메모리 캐시의 용량
var memoryCapacity: Int
Cache storage policies
// CachedURLResponse 객체에서 사용되는 캐싱 전략을 지정하는 상수
enum URLCache.StoragePolicy
allowed:
캐시된 응답을 디스크와 메모리에 모두 저장할 수 있는 정책입니다.
기본적으로 이 정책이 사용되며, 캐시된 응답을 디스크에 저장해 나중에 사용하도록 허용합니다.
memoryOnly:
캐시된 응답을 메모리에만 저장할 수 있는 정책입니다.
디스크에 저장되지 않으며, 앱이 종료되면 캐시된 응답이 제거됩니다. 즉, 응답이 일시적으로만 사용되도록 하고 싶을 때 적합합니다.
notAllowed:
캐시된 응답을 저장하지 않는 정책입니다.
이 정책을 사용하면 캐시에서 응답을 전혀 저장하지 않으며, 항상 새롭게 요청해야 합니다.
코드
실제 코드는 여기를 참고 바란다.
참고
'iOS > UIKit' 카테고리의 다른 글
Auto Layout이란 (0) | 2024.12.15 |
---|---|
특정 시기에 아이콘 자동 변경하기 (0) | 2024.10.26 |
키보드 반응하기 (1) | 2024.10.13 |
UIHostingController (0) | 2024.10.12 |
UIEditMenuInteraction (3) | 2024.10.12 |