SquirrelCache

master

:package: Nothing but Cache.
LeoNavel/Cache

Cache

CircleCI license platform SPM swift

Table of Contents

Description

Cache Icon

Cache doesn't claim to be unique in this area, but it's not another monster library that gives you a god's power. So don't ask it to fetch something from network or magically set an image from url to your UIImageView. It does nothing but caching, but it does it well. It offers a good public API with out-of-box implementations and great customization possibilities.

Key features

  • Generic Cachable protocol to be able to cache any type you want.
  • SpecializedCache class to create a type safe cache storage by a given name for a specified Cachable-compliant type.
  • HybridCache class that works with every kind of Cachable-compliant types.
  • Flexible Config struct which is used in the initialization of SpecializedCache and HybridCache classes.
  • Possibility to set expiry date + automatic cleanup of expired objects.
  • Basic memory and disk cache functionality.
  • Data encoding and decoding required by Cachable protocol are implemented for UIImage, String, JSON and Data.
  • Error handling and logs.
  • Coding protocol brings power of NSCoding to Swift structs and enums
  • CacheArray allows to cache an array of Cachable objects.
  • Extensive unit test coverage and great documentation.
  • iOS, tvOS and macOS support.

Usage

Configuration

Cache is based on the concept of having front- and back- caches. A request to a front cache should be less time and memory consuming (NSCache is used by default here). The difference between front and back caching is that back caching is used for content that outlives the application life-cycle. See it more like a convenient way to store user information that should persist across application launches. Disk cache is the most reliable choice here. You can play with memory and disk cache setup using Config struct.

let config = Config(
  // Expiry date that will be applied by default for every added object
  // if it's not overridden in the add(key: object: expiry: completion:) method
  expiry: .date(Date().addingTimeInterval(100000)),
  /// The maximum number of objects in memory the cache should hold
  memoryCountLimit: 0,
  /// The maximum total cost that the cache can hold before it starts evicting objects
  memoryTotalCostLimit: 0,
  /// Maximum size of the disk cache storage (in bytes)
  maxDiskSize: 10000,
  // Where to store the disk cache. If nil, it is placed in an automatically generated directory in Caches
  cacheDirectory: NSSearchPathForDirectoriesInDomains(.documentDirectory,
                                                      FileManager.SearchPathDomainMask.userDomainMask,
                                                      true).first! + "/cache-in-documents"
)

Hybrid cache

HybridCache supports storing all kinds of objects, as long as they conform to the Cachable protocol.

// Initialization with default configuration
let cache = HybridCache(name: "Mix")
// Initialization with custom configuration
let customCache = HybridCache(name: "Custom", config: config)

Sync API

let cache = HybridCache(name: "Mix")
// Add object to cache
try cache.addObject("This is a string", forKey: "string", expiry: .never)
try cache.addObject(JSON.dictionary(["key": "value"]), "json")
try cache.addObject(UIImage(named: "image.png"), forKey: "image")
try cache.addObject(Data(bytes: [UInt8](repeating: 0, count: 10)), forKey: "data")

// Get object from cache
let string: String? = cache.object(forKey: "string") // "This is a string"
let json: JSON? = cache.object(forKey: "json")
let image: UIImage? = cache.object(forKey: "image")
let data: Data? = cache.object(forKey: "data")

// Get object with expiry date
let entry: CacheEntry<String>? = cache.cacheEntry(forKey: "string")
print(entry?.object) // Prints "This is a string"
print(entry?.expiry.date) // Prints expiry date

// Get total cache size on the disk
let size = try cache.totalDiskSize()

// Remove object from cache
try cache.removeObject(forKey: "data")

// Clear cache
// Pass `true` to keep the existing disk cache directory after removing
// its contents. The default value for `keepingRootDirectory` is `false`.
try cache.clear(keepingRootDirectory: true)

// Clear expired objects
try cache.clearExpired()

Async API

// Add object to cache
cache.async.addObject("This is a string", forKey: "string") { error in
  print(error)
}

// Get object from cache
cache.async.object(forKey: "string") { (string: String?) in
  print(string) // Prints "This is a string"
}

// Get object with expiry date
cache.async.cacheEntry(forKey: "string") { (entry: CacheEntry<String>?) in
  print(entry?.object) // Prints "This is a string"
  print(entry?.expiry.date) // Prints expiry date
}

// Remove object from cache
cache.async.removeObject(forKey: "string") { error in
  print(error)
}

// Clear cache
cache.async.clear() { error in
  print(error)
}

// Clear expired objects
cache.async.clearExpired() { error in
  print(error)
}

Specialized cache

SpecializedCache is a type safe alternative to HybridCache based on generics. Initialization with default or custom configuration, basic operations and working with expiry dates are done exactly in the same way as in HybridCache.

Subscript

// Create string cache, so it's possible to add only String objects
let cache = SpecializedCache<String>(name: "StringCache")
cache["key"] = "value"
print(cache["key"]) // Prints "value"
cache["key"] = nil
print(cache["key"]) // Prints nil

Note that default cache expiry will be used when you use subscript.

Sync API

NOTE: Images are not supported!!!

// Create image cache, so it's possible to add only UIImage objects
let cache = SpecializedCache<UIImage>(name: "ImageCache")

// Add object to cache
try cache.addObject(UIImage(named: "image.png"), forKey: "image")

// Get object from cache
let image = cache.object(forKey: "image")

// Get object with expiry date
let entry = cache.cacheEntry(forKey: "image")
print(entry?.object)
print(entry?.expiry.date) // Prints expiry date

// Get total cache size on the disk
let size = try cache.totalDiskSize()

// Remove object from cache
try cache.removeObject(forKey: "image")

// Clear cache
try cache.clear()

// Clear expired objects
try cache.clearExpired()

Async API

// Create string cache, so it's possible to add only String objects
let cache = SpecializedCache<String>(name: "StringCache")

// Add object to cache
cache.async.addObject("This is a string", forKey: "string") { error in
  print(error)
}

// Get object from cache
cache.async.object(forKey: "string") { string in
  print(string) // Prints "This is a string"
}

// Get object with expiry date
cache.async.cacheEntry(forKey: "string") { entry in
  print(entry?.object) // Prints "This is a string"
  print(entry?.expiry.date) // Prints expiry date
}

// Remove object from cache
cache.async.removeObject(forKey: "string") { error in
  print(error)
}

// Clear cache
cache.async.clear() { error in
  print(error)
}

// Clear expired objects
cache.async.clearExpired() { error in
  print(error)
}

Expiry date

// Default cache expiry date will be applied to the item
try cache.addObject("This is a string", forKey: "string")

// A given expiry date will be applied to the item
try cache.addObject(
  "This is a string",
  forKey: "string"
  expiry: .date(Date().addingTimeInterval(100000))
)

// Clear expired objects
cache.clearExpired()

Enabling data protection

Data protection adds a level of security to files stored on disk by your app in the app’s container. Follow App Distribution Guide to enable data protection on iOS, WatchKit Extension, tvOS.

In addition to that you can use a method on HybridCache and SpecializedCache to set file protection level (iOS and tvOS only):

try cache.setFileProtection(.complete)

It's also possible to update attributes of the disk cache folder:

try cache.setDiskCacheDirectoryAttributes([FileAttributeKey.immutable: true])

Cachable protocol

Encode and decode methods should be implemented if a type conforms to Cachable protocol.

struct User: Cachable {
  static func decode(_ data: Data) -> User? {
    var object: User?
    // Decode your object from data
    return object
  }

  func encode() -> Data? {
    var data: Data?
    // Encode your object to data
    return data
  }
}

Optional bonuses

JSON

JSON is a helper enum that could be Array([Any]) or Dictionary([String : Any]). Then you could cache JSON objects using the same API methods:

let cache = SpecializedCache<JSON>(name: "JSONCache")

// Dictionary
cache.async.addObject(JSON.dictionary(["key": "value"]), forKey: "dictionary")
cache.async.object(forKey: "dictionary") { json in
  print(json?.object)
}

// Array
cache.async.addObject(JSON.array([["key1": "value1"]]), forKey: "array")
cache.object("array") { json in
  print(json?.object)
}

Coding

Coding protocol works in the same way as NSCoding, but can be used for Swift structs and enums. It conforms to Cachable and uses NSKeyedArchiver and NSKeyedUnarchiver in its default implementations of encode and decode.

struct Post {
  let title: String
}

extension Post: Coding {
  func encode(with aCoder: NSCoder) {
    aCoder.encode(title, forKey: "title")
  }

  init?(coder aDecoder: NSCoder) {
    guard let title = aDecoder.decodeObject(forKey: "title") as? String else {
      return nil
    }
    self.init(title: title, text: text)
  }
}

// Save and fetch an instance of `Post` struct.
let cache = SpecializedCache<Post>(name: "PostCache")
let post = Post(title: "Title")

try cache.addObject(post, forKey: "post")
let object = cache.object(forKey: key)
print(object?.title) // Prints title

CacheArray

You can use CacheArray to cache an array of Cachable objects.

// SpecializedCache
let cache = SpecializedCache<CacheArray<String>>(name: "User")
let object = CacheArray(elements: ["string1", "string2"])
try cache.addObject(object, forKey: "array")
let array = cache.object(forKey: "array")?.elements
print(array) // Prints ["string1", "string2"]
// HybridCache
let cache = HybridCache(name: "Mix")
let object = CacheArray(elements: ["string1", "string2"])
try cache.addObject(object, forKey: "array")
let array = (cache.object(forKey: "array") as CacheArray<String>?)?.elements
print(array) // Prints ["string1", "string2"]

What about images?

As being said before, Cache works with any kind of Cachable types, with no preferences and extra care about specific ones. But don't be desperate, we have something nice for you. It's called Imaginary and uses Cache under the hood to make you life easier when it comes to working with remote images.

Installation

Cache is available through CocoaPods. To install it, simply add the following line to your Podfile:

pod 'Cache'

Cache is also available through Carthage. To install just write into your Cartfile:

github "hyperoslo/Cache"

Author

Hyper made this with ❤️. If you’re using this library we probably want to hire you! Send us an email at ios@hyper.no.

Contributing

We would love you to contribute to Cache, check the CONTRIBUTING file for more info.

License

Cache is available under the MIT license. See the LICENSE file for more info.

Description

  • Swift Tools 4.0.0
View More Packages from this Author

Dependencies

Last updated: Tue Jan 31 2023 14:21:23 GMT-0500 (GMT-05:00)