Skip to content

Memory, Disk Caching 관련 라이브러리 입니다.

Notifications You must be signed in to change notification settings

leesoongin/CacheStorage

Repository files navigation

CacheStorage

CacheStorage는 iOS에서 메모리(NSCache)와 디스크(FileManager)를 활용하여 데이터를 효과적으로 캐싱할 수 있는 라이브러리입니다. 데이터를 저장, 조회, 삭제할 수 있으며, 캐싱 변경 사항을 Combine 퍼블리셔를 통해 실시간으로 구독할 수 있습니다.

flowchart TB
    %% CacheStorage Overview
    subgraph CacheStorage
        direction TB
        A[CacheStorage]:::main
        
        subgraph Memory_Caching
            direction TB
            B[MemoryStorage]:::sub
            B --> D[NSCache]:::main
            D -.-> F[NSCacheKey]:::main
            D -.-> G[NSCacheObject]:::main
        end

        subgraph Disk_Caching
            direction TB
            C[DiskStorage]:::sub
            C --> E[FileManager]:::main
        end

        subgraph Functionalities
            direction TB
            H[Save]:::sub
            I[Retrieve]:::sub
            J[Remove]:::sub
            K[RemoveAll]:::sub
            L[StorageObserver]:::sub
        end
    end

    %% Connections
    A --> Memory_Caching
    A --> Disk_Caching
    A --> Functionalities

    %% Styles
    classDef main fill:#DFFFD6,stroke:#333,stroke-width:2,color:#000;
    classDef sub fill:#DFFFD6,stroke:#333,stroke-width:2,color:#000;

Loading

목차

  1. 📁 구성
  2. ⚙️ 동작 원리
  3. 🧑‍💻 사용 예제
  4. 다양한 타입의 Key 활용
  5. ✅ 주요 기능

📁 구성

주요 파일 및 클래스

  • CacheStorage.swift: 메모리와 디스크 캐싱을 통합 관리하는 메인 클래스.
  • MemoryStorage: NSCache를 사용한 메모리 캐싱 관리.
  • DiskStorage: FileManager를 사용한 디스크 캐싱 관리.
  • NSCacheKey.swift: Hashable 프로토콜을 준수하여 NSCache에서 안전하게 키를 사용할 수 있도록 구현.
  • NSCacheObject.swift: 캐시에서 객체를 안전하게 관리하기 위한 래퍼 클래스.

❗ 동시성 이슈 방지 ❗

  • 이슈:
    하나의 storage에서 동시에 ReadWrite 동작이 반복될 경우, 의도하지 않은 결과가 발생할 가능성이 존재합니다.

  • Serial Queue:
    동시성 문제를 방지하기 위해, memoryStoragediskStorage 내부에서 각각의 Save, Retrieve, Remove 등의 동작이 Serial Queue에서 실행되도록 구현되었습니다. 이를 통해 동시성 문제가 발생하지 않도록 안전한 환경을 보장합니다.


⚙️ 동작 원리

NSCacheKey 와 NSCacheObject

CacheStorage는 캐시 키를 Hashable 프로토콜을 준수하는 모든 객체로 사용할 수 있도록 설계되었습니다. NSCache는 기본적으로 NSObject를 키로 사용하는데, 이를 보완하기 위해 다음과 같은 래퍼 객체를 제공합니다:

  • NSCacheKey:
    • 모든 Hashable 타입을 감싸 NSObject처럼 동작하도록 래핑.
    • Hashable 한 객체를 Key로 사용하기 위하여 정의. 다양한 형태의 Key 객체를 생성하여 사용할 수 있습니다.
public final class NSCacheKey<T: Hashable>: NSObject {
    public let value: T
    
    public init(value: T) {
        self.value = value
    }
}
  • NSCacheObject:
    • Cacheable 프로토콜을 따르는 객체를 랩핑하여 저장되는 객체의 모델입니다.
    • 저장되는 객체의 Expiration, addedDate 정보를 가집니다.
//MARK: Cacheable
public protocol Cacheable: Hashable, Codable {
    var expiration: CacheStorageExpiration { get }
}

//MARK: NSCacheObject
public final class NSCacheObject<T: Cacheable>: NSObject, Codable {
    public var value: T?
    public let expiration: CacheStorageExpiration
    
    private let addedDate: Date
    
    public init(_ value: T, expiration: CacheStorageExpiration) {
        self.value = value
        self.expiration = expiration
        self.addedDate = Date()
    }
    
    // ...
}

🧑‍💻 사용 예제

0. Cacheable 을 채택하는 Sample struct

struct SampleCacheableObject: Cacheable {
    var id: String
    var expiration: CacheStorageExpiration
    
    init(id: String, expiration: CacheStorageExpiration) {
        self.id = id
        self.expiration = expiration
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
    
    static func == (lhs: SampleCacheableObject, rhs: SampleCacheableObject) -> Bool {
        lhs.id == rhs.id
    }
}

1. Memory, Disk Configuration 정의 및 CacheStorage 객체 생성

  • memory, disk configuration 을 정의할 때, Key로 사용될 타입과 저장될 Object의 타입을 지정해주어야 합니다.
// 1. Configuration 정의
let memoryConfig = MemoryStorage<String, SampleCacheableObject>.Config(totalCostLimit: 1024 * 10)
let diskConfig = DiskStorage<String, SampleCacheableObject>.Config()

// 2. CacheStorage 객체 생성
let cacheStorage = CacheStorage(
    memoryConfig: memoryConfig,
    diskConfig: diskConfig
)

2. Save, Retrieve , Remove, RemoveAll, StorageObserver

  • cacheStorage 를 통해 save, retrieve, remove, removeAll 작업을 수행할 수 있습니다.
  • cacheStoragestorageObserver 를 통해 이벤트 감지를 할 수 있습니다. storageObserver 는 이벤트를 Combine의 AnyPublisher 형태로 리턴합니다.
// Data Save
cacheStorage.save(with: beStoreObject, key: "string_key_1")

// Data Remove All
cacheStorage.removeAll()
        
// Data Retrieve
let retrievedValue = try? cacheStorage.retrieve(forKey: "string_key_1")

// Data Remove
try? cacheStorage.remove(forKey: "string_key_1")

// Observe Storage event
cacheStorage.storageObserver
    .sink { result in
        switch result {
        case .success(let changeSet):
            // changeSet.key
            // changeSet.object
        case .failure(let error):
            // error handle
        }
    }
    .store(in: &cancellable)

다양한 타입의 Key 활용

CacheStorage 의 Key 는 Hashable 하다면 사용 가능합니다.

1. String Type

// 1. Config 객체 생성, Key 타입 String
let memoryConfig = MemoryStorage<String, SampleCacheableObject>.Config(totalCostLimit: 1024 * 10)
let diskConfig = DiskStorage<String, SampleCacheableObject>.Config()

// 2. CacheStorage 객체 생성
let cacheStorage = CacheStorage(
    memoryConfig: memoryConfig,
    diskConfig: diskConfig
)

cacheStorage.save(with: beStoreObject, key: "string_key_1")
// ... do something

2. Array Type

// 1. Config 객체 생성, Key 타입 [String]
let memoryConfig = MemoryStorage<[String], SampleCacheableObject>.Config(totalCostLimit: 1024 * 10)
let diskConfig = DiskStorage<[String], SampleCacheableObject>.Config()

// 2. CacheStorage 객체 생성
let cacheStorage = CacheStorage(
    memoryConfig: memoryConfig,
    diskConfig: diskConfig
)

cacheStorage.save(with: beStoreObject, key: ["one", "two", "three", "four"])
// ... do something

3. Dictionary Type

// 1. Config 객체 생성, Key 타입 Dictionary [String: String]
let memoryConfig = MemoryStorage<[String: String], SampleCacheableObject>.Config(totalCostLimit: 1024 * 10)
let diskConfig = DiskStorage<[String: String], SampleCacheableObject>.Config()

// 2. CacheStorage 객체 생성
let cacheStorage = CacheStorage(
    memoryConfig: memoryConfig,
    diskConfig: diskConfig
)

cacheStorage.save(with: beStoreObject, key: [
    "first_key": "first_value",
     "second_key": "second_value", 
     "third_key": "third_value"
     ]
    )
// ... do something

4. Custom Type

//MAKR: Custom Key Struct Definition
final class SampleCustomKey: Hashable {
    var id: String
    var something: String
    
    init(id: String, something: String) {
        self.id = id
        self.something = something
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
        hasher.combine(something)
    }
    
    static func == (lhs: SampleCustomKey, rhs: SampleCustomKey) -> Bool {
        lhs.id == rhs.id && lhs.something == rhs.something
    }
}

//MARK: Sample
// 1. Config 객체 생성, Key 타입 Dictionary [String: String]
let memoryConfig = MemoryStorage<SampleCustomKey, SampleCacheableObject>.Config(totalCostLimit: 1024 * 10)
let diskConfig = DiskStorage<SampleCustomKey, SampleCacheableObject>.Config()

// 2. CacheStorage 객체 생성
let cacheStorage = CacheStorage(
    memoryConfig: memoryConfig,
    diskConfig: diskConfig
)

let firstSampleCustomKey = SampleCustomKey(id: "first_custom_key")
let secondSampleCustomKey = SampleCustomKey(id: "second_custom_key")

// Data save
cacheStorage.save(with: beStoreObject, key: firstSampleCustomKey)
// or
cacheStorage.save(with: beStoreObject, key: secondSampleCustomKey)

// do something ...

3. CustomKey의 유니크 특성 확인

동일한 키를 사용할 경우

let customKey = CustomKey(name: "SharedKey")

cache.save(value: "First Entry", forKey: customKey)
cache.save(value: "Second Entry", forKey: customKey) // 덮어씌워짐

if let retrieved: String = cache.retrieve(forKey: customKey) {
    print("CustomKey로 가져온 데이터: \(retrieved)") // "Second Entry"
}

다른 UUID를 가진 키를 사용할 경우

let uniqueKey1 = CustomKey(id: UUID(), name: "Key1")
let uniqueKey2 = CustomKey(id: UUID(), name: "Key1") // 같은 이름, 다른 UUID

cache.save(value: "Value for Key1", forKey: uniqueKey1)

if let data: String = cache.retrieve(forKey: uniqueKey2) {
    print("가져온 데이터: \(data)")
} else {
    print("UUID가 다르므로 데이터가 없습니다.") // 출력
}

✅ 주요 기능

  • 메모리 및 디스크 캐싱 계층 제공
  • 다양한 타입의 키와 값을 저장 가능
  • 제네릭으로 유연한 키 타입 지원 (Hashable 프로토콜 준수)
  • 사용자 정의 키를 활용한 캐싱 (NSCacheKey와 호환)
  • 캐시 데이터 자동 정리 (용량 초과 시 오래된 데이터 삭제)
  • Thread-Safe 동작
  • 캐시 변경 사항 실시간 관찰 (storageObserver 퍼블리셔 사용)

About

Memory, Disk Caching 관련 라이브러리 입니다.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages