• Stars
    star
    2,969
  • Rank 15,244 (Top 0.4 %)
  • Language
    Swift
  • License
    Other
  • Created about 9 years ago
  • Updated 2 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

๐Ÿ“ฆ Nothing but Cache.

Cache

CI Status Version Carthage Compatible License Platform Documentation 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. 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. Cache utilizes Codable in Swift 4 to perform serialization.

Read the story here Open Source Stories: From Cachable to Generic Storage in Cache

Key features

  • Work with Swift 4 Codable. Anything conforming to Codable will be saved and loaded easily by Storage.
  • Hybrid with memory and disk storage.
  • Many options via DiskConfig and MemoryConfig.
  • Support expiry and clean up of expired objects.
  • Thread safe. Operations can be accessed from any queue.
  • Sync by default. Also support Async APIs.
  • Extensive unit test coverage and great documentation.
  • iOS, tvOS and macOS support.

Usage

Storage

Cache is built based on Chain-of-responsibility pattern, in which there are many processing objects, each knows how to do 1 task and delegates to the next one, so can you compose Storages the way you like.

For now the following Storage are supported

  • MemoryStorage: save object to memory.
  • DiskStorage: save object to disk.
  • HybridStorage: save object to memory and disk, so you get persistented object on disk, while fast access with in memory objects.
  • SyncStorage: blocking APIs, all read and write operations are scheduled in a serial queue, all sync manner.
  • AsyncStorage: non-blocking APIs, operations are scheduled in an internal queue for serial processing. No read and write should happen at the same time.

Although you can use those Storage at your discretion, you don't have to. Because we also provide a convenient Storage which uses HybridStorage under the hood, while exposes sync and async APIs through SyncStorage and AsyncStorage.

All you need to do is to specify the configuration you want with DiskConfig and MemoryConfig. The default configurations are good to go, but you can customise a lot.

let diskConfig = DiskConfig(name: "Floppy")
let memoryConfig = MemoryConfig(expiry: .never, countLimit: 10, totalCostLimit: 10)

let storage = try? Storage(
  diskConfig: diskConfig,
  memoryConfig: memoryConfig,
  transformer: TransformerFactory.forCodable(ofType: User.self) // Storage<String, User>
)

Generic, Type safety and Transformer

All Storage now are generic by default, so you can get a type safety experience. Once you create a Storage, it has a type constraint that you don't need to specify type for each operation afterwards.

If you want to change the type, Cache offers transform functions, look for Transformer and TransformerFactory for built-in transformers.

let storage: Storage<String, User> = ...
storage.setObject(superman, forKey: "user")

let imageStorage = storage.transformImage() // Storage<String, UIImage>
imageStorage.setObject(image, forKey: "image")

let stringStorage = storage.transformCodable(ofType: String.self) // Storage<String, String>
stringStorage.setObject("hello world", forKey: "string")

Each transformation allows you to work with a specific type, however the underlying caching mechanism remains the same, you're working with the same Storage, just with different type annotation. You can also create different Storage for each type if you want.

Transformer is necessary because the need of serialising and deserialising objects to and from Data for disk persistency. Cache provides default Transformer for Data, Codable and UIImage/NSImage

Codable types

Storage supports any objects that conform to Codable protocol. You can make your own things conform to Codable so that can be saved and loaded from Storage.

The supported types are

  • Primitives like Int, Float, String, Bool, ...
  • Array of primitives like [Int], [Float], [Double], ...
  • Set of primitives like Set<String>, Set<Int>, ...
  • Simply dictionary like [String: Int], [String: String], ...
  • Date
  • URL
  • Data

Error handling

Error handling is done via try catch. Storage throws errors in terms of StorageError.

public enum StorageError: Error {
  /// Object can not be found
  case notFound
  /// Object is found, but casting to requested type failed
  case typeNotMatch
  /// The file attributes are malformed
  case malformedFileAttributes
  /// Can't perform Decode
  case decodingFailed
  /// Can't perform Encode
  case encodingFailed
  /// The storage has been deallocated
  case deallocated
  /// Fail to perform transformation to or from Data
  case transformerFail
}

There can be errors because of disk problem or type mismatch when loading from storage, so if want to handle errors, you need to do try catch

do {
  let storage = try Storage(diskConfig: diskConfig, memoryConfig: memoryConfig)
} catch {
  print(error)
}

Configuration

Here is how you can play with many configuration options

let diskConfig = DiskConfig(
  // The name of disk storage, this will be used as folder name within directory
  name: "Floppy",
  // Expiry date that will be applied by default for every added object
  // if it's not overridden in the `setObject(forKey:expiry:)` method
  expiry: .date(Date().addingTimeInterval(2*3600)),
  // Maximum size of the disk cache storage (in bytes)
  maxSize: 10000,
  // Where to store the disk cache. If nil, it is placed in `cachesDirectory` directory.
  directory: try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask,
    appropriateFor: nil, create: true).appendingPathComponent("MyPreferences"),
  // Data protection is used to store files in an encrypted format on disk and to decrypt them on demand
  protectionType: .complete
)
let memoryConfig = MemoryConfig(
  // Expiry date that will be applied by default for every added object
  // if it's not overridden in the `setObject(forKey:expiry:)` method
  expiry: .date(Date().addingTimeInterval(2*60)),
  /// The maximum number of objects in memory the cache should hold
  countLimit: 50,
  /// The maximum total cost that the cache can hold before it starts evicting objects
  totalCostLimit: 0
)

On iOS, tvOS we can also specify protectionType on DiskConfig to add a level of security to files stored on disk by your app in the appโ€™s container. For more information, see FileProtectionType

Sync APIs

Storage is sync by default and is thread safe, you can access it from any queues. All Sync functions are constrained by StorageAware protocol.

// Save to storage
try? storage.setObject(10, forKey: "score")
try? storage.setObject("Oslo", forKey: "my favorite city", expiry: .never)
try? storage.setObject(["alert", "sounds", "badge"], forKey: "notifications")
try? storage.setObject(data, forKey: "a bunch of bytes")
try? storage.setObject(authorizeURL, forKey: "authorization URL")

// Load from storage
let score = try? storage.object(forKey: "score")
let favoriteCharacter = try? storage.object(forKey: "my favorite city")

// Check if an object exists
let hasFavoriteCharacter = try? storage.existsObject(forKey: "my favorite city")

// Remove an object in storage
try? storage.removeObject(forKey: "my favorite city")

// Remove all objects
try? storage.removeAll()

// Remove expired objects
try? storage.removeExpiredObjects()

Entry

There is time you want to get object together with its expiry information and meta data. You can use Entry

let entry = try? storage.entry(forKey: "my favorite city")
print(entry?.object)
print(entry?.expiry)
print(entry?.meta)

meta may contain file information if the object was fetched from disk storage.

Custom Codable

Codable works for simple dictionary like [String: Int], [String: String], ... It does not work for [String: Any] as Any is not Codable conformance, it will raise fatal error at runtime. So when you get json from backend responses, you need to convert that to your custom Codable objects and save to Storage instead.

struct User: Codable {
  let firstName: String
  let lastName: String
}

let user = User(fistName: "John", lastName: "Snow")
try? storage.setObject(user, forKey: "character")

Async APIs

In async fashion, you deal with Result instead of try catch because the result is delivered at a later time, in order to not block the current calling queue. In the completion block, you either have value or error.

You access Async APIs via storage.async, it is also thread safe, and you can use Sync and Async APIs in any order you want. All Async functions are constrained by AsyncStorageAware protocol.

storage.async.setObject("Oslo", forKey: "my favorite city") { result in
  switch result {
    case .success:
      print("saved successfully")
    case .failure(let error):
      print(error)
  }
}

storage.async.object(forKey: "my favorite city") { result in
  switch result {
    case .success(let city):
      print("my favorite city is \(city)")
    case .failure(let error):
      print(error)
  }
}

storage.async.existsObject(forKey: "my favorite city") { result in
  if case .success(let exists) = result, exists {
    print("I have a favorite city")
  }
}

storage.async.removeAll() { result in
  switch result {
    case .success:
      print("removal completes")
    case .failure(let error):
      print(error)
  }
}

storage.async.removeExpiredObjects() { result in
  switch result {
    case .success:
      print("removal completes")
    case .failure(let error):
      print(error)
  }
}

Expiry date

By default, all saved objects have the same expiry as the expiry you specify in DiskConfig or MemoryConfig. You can overwrite this for a specific object by specifying expiry for setObject

// Default expiry date from configuration will be applied to the item
try? storage.setObject("This is a string", forKey: "string")

// A given expiry date will be applied to the item
try? storage.setObject(
  "This is a string",
  forKey: "string"
  expiry: .date(Date().addingTimeInterval(2 * 3600))
)

// Clear expired objects
storage.removeExpiredObjects()

Observations

Storage allows you to observe changes in the cache layer, both on a store and a key levels. The API lets you pass any object as an observer, while also passing an observation closure. The observation closure will be removed automatically when the weakly captured observer has been deallocated.

Storage observations

// Add observer
let token = storage.addStorageObserver(self) { observer, storage, change in
  switch change {
  case .add(let key):
    print("Added \(key)")
  case .remove(let key):
    print("Removed \(key)")
  case .removeAll:
    print("Removed all")
  case .removeExpired:
    print("Removed expired")
  }
}

// Remove observer
token.cancel()

// Remove all observers
storage.removeAllStorageObservers()

Key observations

let key = "user1"

let token = storage.addObserver(self, forKey: key) { observer, storage, change in
  switch change {
  case .edit(let before, let after):
    print("Changed object for \(key) from \(String(describing: before)) to \(after)")
  case .remove:
    print("Removed \(key)")
  }
}

// Remove observer by token
token.cancel()

// Remove observer for key
storage.removeObserver(forKey: key)

// Remove all observers
storage.removeAllKeyObservers()

Handling JSON response

Most of the time, our use case is to fetch some json from backend, display it while saving the json to storage for future uses. If you're using libraries like Alamofire or Malibu, you mostly get json in the form of dictionary, string, or data.

Storage can persist String or Data. You can even save json to Storage using JSONArrayWrapper and JSONDictionaryWrapper, but we prefer persisting the strong typed objects, since those are the objects that you will use to display in UI. Furthermore, if the json data can't be converted to strongly typed objects, what's the point of saving it ? ๐Ÿ˜‰

You can use these extensions on JSONDecoder to decode json dictionary, string or data to objects.

let user = JSONDecoder.decode(jsonString, to: User.self)
let cities = JSONDecoder.decode(jsonDictionary, to: [City].self)
let dragons = JSONDecoder.decode(jsonData, to: [Dragon].self)

This is how you perform object converting and saving with Alamofire

Alamofire.request("https://gameofthrones.org/mostFavoriteCharacter").responseString { response in
  do {
    let user = try JSONDecoder.decode(response.result.success, to: User.self)
    try storage.setObject(user, forKey: "most favorite character")
  } catch {
    print(error)
  }
}

What about images

If you want to load image into UIImageView or NSImageView, then we also have a nice gift for you. It's called Imaginary and uses Cache under the hood to make your life easier when it comes to working with remote images.

Installation

Cocoapods

Cache is available through CocoaPods. To install it or update it, use the following line to your Podfile:

pod 'Cache', :git => 'https://github.com/hyperoslo/Cache.git'

Carthage

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

github "hyperoslo/Cache"

You also need to add SwiftHash.framework in your copy-frameworks script.

Author

  • Hyper made this with โค๏ธ
  • Inline MD5 implementation from SwiftHash

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.

More Repositories

1

ImagePicker

๐Ÿ“ท Reinventing the way ImagePicker works.
Swift
4,867
star
2

Whisper

๐Ÿ“ฃ Whisper is a component that will make the task of display messages and in-app notifications simple. It has three different views inside
Swift
3,754
star
3

Presentation

๐Ÿ“‘ Presentation helps you to make tutorials, release notes and animated pages.
Swift
3,046
star
4

BarcodeScanner

๐Ÿ”Ž A simple and beautiful barcode scanner.
Swift
1,688
star
5

Lightbox

๐ŸŒŒ A convenient and easy to use image viewer for your iOS app
Swift
1,640
star
6

Gallery

๐Ÿ“น Your next favorite image and video picker
Swift
1,419
star
7

Sugar

โ˜• Something sweet that goes great with your Cocoa
Swift
1,068
star
8

Compass

๐ŸŒ Compass helps you setup a central navigation system for your application
Swift
826
star
9

Imaginary

๐Ÿฆ„ Remote images, as easy as one, two, three.
Swift
614
star
10

Pages

๐Ÿ“„ UIPageViewController made simple
Swift
495
star
11

iOS-playbook

Hyper's iOS playbook
228
star
12

capistrano-foreman

Capistrano tasks for foreman and upstart.
Ruby
136
star
13

Tabby

โ›ฉ A fancy tabbar
Swift
103
star
14

Keychains

๐Ÿ”‘ A keychain wrapper that is so easy to use that your cat could use it.
Swift
75
star
15

Aftermath

๐Ÿ”ฎ Stateless message-driven micro-framework in Swift.
Swift
71
star
16

novel

๐Ÿ“– A content management system (CMS) built in Swift
Swift
68
star
17

OhMyAuth

๐Ÿ” Simple OAuth2 library with a support of multiple services.
Swift
66
star
18

Brick

๐Ÿ’ง A generic view model for both basic and complex scenarios
Swift
59
star
19

gamification

Gamification is a collection of models for Ruby on Rails that allows you to make anything a game
Ruby
51
star
20

Signature

UIView with signature support
Objective-C
49
star
21

playbook

How we do things, and why
42
star
22

SwiftPackage

๐Ÿ† Template to make a Swift package
Ruby
38
star
23

hyper-alerts

Hyper Alerts notifies people whenever someone posts to Facebook or Twitter
Ruby
34
star
24

knowledge_base

Knowledge Base is a bunch of models for Ruby on Rails that you probably need to build your own
Ruby
31
star
25

SwiftProject

๐Ÿ† Generate Swift project with necessary toolings
Swift
30
star
26

github-s3

Shell scripts that make it really easy to archive and restore repositories between GitHub and AWS S3
Shell
20
star
27

cellular

Sending and receiving SMSs with Ruby through pluggable backends.
Ruby
20
star
28

mingle

Social media integration for Ruby on Rails
Ruby
20
star
29

activeadmin_polymorphic

Ruby
20
star
30

HYPEventManager

HYPEventManager is the easiest way to add, update and remove iOS calendar events.
Objective-C
17
star
31

hyper-recipes

Ruby
16
star
32

Contract

The easiest way to sign your soul away
Objective-C
15
star
33

Postman

ยซYou're nothing but a drifter who found a bag of mailยป
Objective-C
15
star
34

Minced

Convert JSON keys to camelCase
Objective-C
14
star
35

code-review

A simple application that selects a random commit for code review.
Ruby
14
star
36

CardStack

A container view controller implementing a stack of "cards" (each card is a view controller)
Objective-C
13
star
37

hyper-radar

๐Ÿ’ก Research and development at Hyper
12
star
38

Offline

๐Ÿ“ดOffline request storage.
Swift
11
star
39

TimeAgo

Swift
11
star
40

Scatter

Customizable Scatter Chart for iOS
Objective-C
11
star
41

AftermathCompass

Message-driven navigation system built on top of Aftermath and Compass.
Swift
10
star
42

AsyncWall

Swift
10
star
43

heroku-deploy

Deploying your applications to Heroku should never involve manually chaining shell commands together
Ruby
10
star
44

HYPLocalNotificationManager

Handle local notifications like a pro
Objective-C
9
star
45

hyper-content-for-angular

Inject content into a different part of your page
JavaScript
9
star
46

tasuku

Tasks for Ruby on Rails
Ruby
9
star
47

CollectionAnimations

UICollectionView animations
Swift
9
star
48

UIViewController-HYPKeyboardToolbar

Snap a toolbar to a keyboard like a pro
Objective-C
8
star
49

refile-input

Refile support for Formtastic
Ruby
8
star
50

singleton-rails

Adds singleton functionallity to ActiveRecord models
Ruby
8
star
51

Champagne

The Champagne Web Framework.
Swift
7
star
52

obix

Ruby OBIX parser
Ruby
6
star
53

android-playbook

6
star
54

feeder

Provides simple feed functionality through an engine
Ruby
6
star
55

hyper-says

Heard @hyperoslo
JavaScript
6
star
56

embeddable

Embeddable makes it easier to embed videos
Ruby
6
star
57

tv

The TV in our lobby
HTML
6
star
58

hyper_admin

Admin solution for Ruby on Rails.
Ruby
6
star
59

openid-token-proxy

Retrieves and refreshes OpenID tokens on behalf of a user when dealing with complex authentication schemes, such as client-side certificates
Ruby
6
star
60

Flasker

๐Ÿถ Secure User Defaults
Swift
6
star
61

ImageCrop

android library to zoom, pan and crop pictures
Java
6
star
62

facebook-messenger-demo

A demo rails app for facebook-messenger gem
Ruby
6
star
63

yr

Yr makes it easy to get weather forecast from Yr.no.
Ruby
5
star
64

Orchestra

Swift
5
star
65

date-interval-picker

A nice little date interval picker for Android
Java
5
star
66

NSString-HYPWordExtractor

The easiest way of extracting all words from a string
Objective-C
5
star
67

Transition

Swift
5
star
68

pissuu

Python client for the Issuu API
Python
5
star
69

javascript-playbook

A place to define our conventions for working with JavaScript
5
star
70

NSString-HYPFormula

Creating and running string-based formulas have never been this easy
Objective-C
5
star
71

heroku-pages

Easily inspect, view and upload your Heroku error and maintenance pages
Ruby
5
star
72

ios-foundation

๐Ÿ…An established foundation within iOS team
Ruby
5
star
73

bot

Bot all the thingsโ„ข
CoffeeScript
5
star
74

slack-andreasbot

A bot that is always typing. Just like the non-bot Andreas.
Ruby
5
star
75

deviser

Allows impersonation straight from the command line for Devise projects
Ruby
4
star
76

DistributedSpaceLayout

This is a tiny library extending LinearLayout to automatically distribute evenly spaces between children
Java
4
star
77

django-mobile

Simple, easy to use Django SMS app with support for pluggable backends
Python
4
star
78

hyper-validator-base

Enables validation for inputs in conjunction with hyper-validator modules.
JavaScript
4
star
79

api-playbook

A place to define the conventions we use to build APIs
4
star
80

NSManagedObjectContext-HYPSafeSave

Warns you of unsafe NSManagedObjectContext saves
Objective-C
4
star
81

HYPLocationManager

The easiest way to use CLLocationManager.
Objective-C
4
star
82

gulp-frontend-starterkit

Gulp Frontend StarterKit is a minimal mix of tasks and tools integrated with [Gulp](http://gulpjs.com/) to form a asset pipeline. It provides an efficient and modular workflow to develop and deploy static websites quickly.
JavaScript
4
star
83

one-repo-split-angular-rails-example

this is a prototype of playing together with angular and rails on the same repo, but without mixing their code together (heroku deployed)
Ruby
3
star
84

eslint-config

Hyper's ESLint config
JavaScript
3
star
85

reverse-proxy

Simple reverse proxy application.
Ruby
3
star
86

Sequent

Sequent iOS Framework
Swift
3
star
87

spot-hyper

Spot Hyper shares where you are and what you working on in realtime with other team members, right from any slack channel
Ruby
3
star
88

AftermathSpots

Swift
3
star
89

mysql-s3

A shell script that archives a MySQL database to AWS S3
Shell
3
star
90

Masquerade

Swift
3
star
91

grumbles-the-lame-wizard

"Grumbles the Lame Wizard" game
Ruby
3
star
92

hyper-guides

Miscellaneous guides from Hyper
Ruby
3
star
93

Depot

A convenience storage/caching library for Android.
Java
2
star
94

HYPInputValidators

Objective-C
2
star
95

HYPPopoverBackgroundView

Makes popover controllers look so good you'll want to lick them
Objective-C
2
star
96

HYPImagePicker

UIImagePickerController without the tears
Shell
2
star
97

AsyncMediaSlider

A simple to use media slider that loads remote images asynchronously on demand
Objective-C
2
star
98

HYPWebView

WebView++
Objective-C
2
star
99

amazon-s3-backup

Utility that creates storage buckets and transfer jobs that backup Amazon S3 Buckets to Google Cloud Storage
JavaScript
2
star
100

AftermathTools

Development tools for Aftermath.
Swift
2
star