Skip to content

A minimalist memory and disk based cache backed by NSCache, NSKeyedArchive, and the Swift 4 Codable protocol.

License

Notifications You must be signed in to change notification settings

asowers1/CodableCache

Repository files navigation


PlatformsLanguagesCarthage compatibleCocoapods compatibleLicense

📦📲 CodableCache

What is CodableCache? It's a framework that allows for seamless memory caching and disk persistence of your plain old Swift structs. Simply define a model and conform to Codable – you're ready to use CodableCache.

📋🧐 Features

  • Simple to use transparent cache based on keys and generic types
  • Anything that's Codable works automatically
  • No serializers to write other than optional custom Codable encode/decode
  • Works with images via codable wrapper
  • Easy to integrate into existing workflows
  • backed by battle tested NSCache and NSKeyedArchiver
  • batteries included - by design, it's up to you to create workflows and handle cache errors

🎓📕 Some History

Codable Cache is a drop in replacement for my LeanCache framework, which was backed by specifying generic types conforming to NSCoding. It afforded workflows like let x: NSNumber? = Cache<NSNumber>("some interesting key") and that's still great, but writing serializers for NSCoding is a pain. Hence, CodableCache was born.

👩‍💻👨‍💻 Example Code

To get started, just import CodableCache, define a model that conforms to codable, and get coding. These are just a few examples of how you could use CodableCache.

Create a person manager backed by a persistent cache:

import CodableCache structPerson:Codable{letname:Stringletage:Double // kids are half ages if you recall 😜 }finalclassPersonManager{letcache:CodableCache<Person>init(cacheKey:AnyHashable){ cache =CodableCache<Person>(key: cacheKey)}func getPerson()->Person?{return cache.get()}func set(person:Person)throws{ cache.set(value: person)}}
And later use it like so:
varmyPerson=Person(name:"Andrew", age:26)letpersonManager=PersonManager(cacheKey:"myPerson")try? personManager.set(value: myPerson)iflet person = personManager.get(){print(person.name) // "Andrew" print(person.age) // 26 }

Cache JSON with confidence:

import CodableCache //... structTestData:Codable{lettesting:[Int]}func saveJSON(){letjson="""{"testing": [ 1, 2, 3 ] }"""guardlet data = json.data(using:.utf8)else{return}letdecodedTestData:TestDatado{ decodedTestData =try decoder.decode(TestData.self, from: data)try codableCache.set(value: decodedTestData)}catch{ // do something else return}}

Get your cached JSON for later use:

import CodableCache finalclassAppDelegate:UIResponder,UIApplicationDelegate,UNUserNotificationCenterDelegate{letappSettings=CodableCache<Settings>(key:"com.myApp.settings")func application(_ application:UIApplication, didFinishLaunchingWithOptions launchOptions:[UIApplicationLaunchOptionsKey:Any]?)->Bool{iflet settings = appSettings.get(){doSomethingUseful(with: settings)}returntrue} // ... }

Specify a different storage directory:

import CodableCache structPerson:Codable{letname:Stringletage:Double} // this data will not be purged by the system like .cachesDirectory would letpersistentPersonStorage=CodableCache<Person>(key:"myPerson", directory:.applicationSupportDirectory)

Creating a generic cache:

import CodableCache finalclassGenericCache<Cacheable:Codable>{letcache:CodableCache<Cacheable>init(cacheKey:AnyHashable){self.cache =CodableCache<Cacheable>(key: cacheKey)}func get()->Cacheable?{returnself.cache.get()}func set(value:Cacheable)throws{tryself.cache.set(value: value)}func clear()throws{tryself.cache.clear()}}
And later use it like so:
letmyCache=GenericCache<MyType>(cacheKey:String(describing:MyType.self))

😓🙃 Gotchas

Custom Encoders/Decoders

Make sure you're decoding as optional if your values are optionally typed

func testCustomDecoder(){structFoo:Codable{varbar:String?=""privateenumCodingKeys:String,CodingKey{case bar }init(bar:String?){self.bar = bar }init(from decoder:Decoder)throws{letcontainer=try decoder.container(keyedBy:CodingKeys.self)self.bar =try container.decode(String.self, forKey:.bar)}func encode(to encoder:Encoder)throws{varcontainer= encoder.container(keyedBy:CodingKeys.self)try container.encode(self.bar, forKey:.bar)}}letfoo0=Foo(bar:"Hello World")letfoo1=Foo(bar:nil)letfooCache=CodableCache<Foo>(key:String(describing:Foo.self))try? fooCache.set(value: foo0) // this is not nil because decoder expected `String` XCTAssertNotNil(fooCache.get())try? fooCache.set(value: foo1) // this is nil because decoder expected `String`, but it was given what we'd expect for `String?` XCTAssertNil(fooCache.get())}

To make the decoder work in this scenario, you would want to decode Foo.bar as String? like so:

self.bar =try container.decode(String?.self, forKey:.bar)

👩‍🔬 👨‍🎨 Philosophy

Using something heavyweight like CoreData, Realm, or SQLite is often overkill. More often than not we're just backing up some local state based on some JSON interface – using a spaceship for a walk down the block 🚀. Typically, we display this data to the user if it isn't stale and update it from the network if need be. Sorting and reordering is often a server side task, so relational databases and object graphs might be too expensive in terms of upstart modeling and your management time. With CodableCache we take a different approach by allowing you to quickly define models, skip boilerplate / serializers, and start saving your data at a lightning pace.

💻 🚀 Installation

Cocoapods

CocoaPods is a dependency manager for Cocoa projects. You can install it with the following command:

$ gem install cocoapods

CocoaPods 1.1+ is required to build CodableCache.

To integrate CodableCache into your Xcode project using CocoaPods, specify it in your Podfile:

use_frameworks!target'<Your Target Name>'dopod'CodableCache'end

Then, run the following command:

$ pod install

Carthage

Carthage is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks.

You can install Carthage with Homebrew using the following command:

$ brew update $ brew install carthage

To integrate CodableCache into your Xcode project using Carthage, specify it in your Cartfile:

github "asowers1/CodableCache" "master" 

Run

carthage update 

In your application targets “General” tab under the “Linked Frameworks and Libraries” section, drag and drop CodableCache-iOS.framework from the Carthage/Build/iOS directory that carthage update produced.

Swift Package Manager

The Swift Package Manager is a tool for automating the distribution of Swift code and is integrated into the swift compiler. It is in early development, but Alamofire does support its use on supported platforms.

Once you have your Swift package set up, adding CodableCache as a dependency is as easy as adding it to the dependencies value of your Package.swift.

dependencies:[.Package(url:"https://github.com/asowers1/CodableCache.git")]

🙋 🙋‍♂️ Contributing

Feel free to open an issue or pull request – I would be happy to help.

👩‍🔧 👨‍🔧 Authors and Contributors

Andrew Sowers

Joe Fabisevich

About

A minimalist memory and disk based cache backed by NSCache, NSKeyedArchive, and the Swift 4 Codable protocol.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •