Giter Site home page Giter Site logo

krzemienski / shallows Goto Github PK

View Code? Open in Web Editor NEW

This project forked from dreymonde/shallows

0.0 0.0 0.0 125 KB

๐Ÿ›ถ Your lightweight persistence toolbox

Home Page: https://medium.com/anysuggestion

License: MIT License

Swift 99.41% Ruby 0.59%

shallows's Introduction

Shallows

Swift Platform

Shallows is a generic abstraction layer over lightweight data storage and persistence. It provides a Storage<Key, Value> type, instances of which can be easily transformed and composed with each other. It gives you an ability to create highly sophisticated, effective and reliable caching/persistence solutions.

Shallows is deeply inspired by Carlos and this amazing talk by Brandon Kase.

Shallows is a really small, component-based project, so if you need even more controllable solution โ€“ build one yourself! Our source code is there to help.

Usage

struct City : Codable {
    let name: String
    let foundationYear: Int
}

let diskStorage = DiskStorage.main.folder("cities", in: .cachesDirectory)
    .mapJSONObject(City.self)

diskStorage.retrieve(forKey: "Beijing") { (result) in
    if let city = result.value { print(city) }
}

let kharkiv = City(name: "Kharkiv", foundationYear: 1654)
diskStorage.set(kharkiv, forKey: "Kharkiv")

Guide

A main type of Shallows is Storage<Key, Value>. It's an abstract, type-erased structure which doesn't contain any logic -- it needs to be provided with one. The most basic one is MemoryStorage:

let storage = MemoryStorage<String, Int>().asStorage() // Storage<String, Int>

Storage instances have retrieve and set methods, which are asynhronous and fallible:

storage.retrieve(forKey: "some-key") { (result) in
    switch result {
    case .success(let value):
        print(value)
    case .failure(let error):
        print(error)
    }
}
storage.set(10, forKey: "some-key") { (result) in
    switch result {
    case .success:
        print("Value set!")
    case .failure(let error):
        print(error)
    }
}

Transforms

Keys and values can be mapped:

let storage = DiskStorage.main.folder("images", in: .cachesDirectory)
let images = storage
    .mapValues(to: UIImage.self,
               transformIn: { data in try UIImage.init(data: data).unwrap() },
               transformOut: { image in try UIImagePNGRepresentation(image).unwrap() })

enum ImageKeys : String {
    case kitten, puppy, fish
}

let keyedImages = images
    .usingStringKeys()
    .mapKeys(toRawRepresentableType: ImageKeys.self)

keyedImages.retrieve(forKey: .kitten, completion: { result in /* .. */ })

NOTE: There are several convenience methods defined on Storage with value of Data: .mapString(withEncoding:), .mapJSON(), .mapJSONDictionary(), .mapJSONObject(_:) .mapPlist(format:), .mapPlistDictionary(format:), .mapPlistObject(_:).

Storages composition

Another core concept of Shallows is composition. Hitting a disk every time you request an image can be slow and inefficient. Instead, you can compose MemoryStorage and FileSystemStorage:

let efficient = MemoryStorage<String, UIImage>().combined(with: imageStorage)

It does several things:

  1. When trying to retrieve an image, the memory storage first will be checked first, and if it doesn't contain a value, the request will be made to disk storage.
  2. If disk storage stores a value, it will be pulled to memory storage and returned to a user.
  3. When setting an image, it will be set both to memory and disk storage.

Read-only storage

If you don't want to expose writing to your storage, you can make it a read-only storage:

let readOnly = storage.asReadOnlyStorage() // ReadOnlyStorage<Key, Value>

Read-only storages can also be mapped and composed:

let immutableFileStorage = DiskStorage.main.folder("immutable", in: .applicationSupportDirectory)
    .mapString(withEncoding: .utf8)
    .asReadOnlyStorage()
let storage = MemoryStorage<Filename, String>()
    .backed(by: immutableFileStorage)
    .asReadOnlyStorage() // ReadOnlyStorage<Filename, String>

Write-only storage

In similar way, write-only storage is also available:

let writeOnly = storage.asWriteOnlyStorage() // WriteOnlyStorage<Key, Value>

Single element storage

You can have a storage with keys Void. That means that you can store only one element there. Shallows provides a convenience .singleKey method to create it:

let settings = DiskStorage.main.folder("settings", in: .applicationSupportDirectory)
    .mapJSONDictionary()
    .singleKey("settings") // Storage<Void, [String : Any]>
settings.retrieve { (result) in
    // ...
}

Synchronous storage

Storages in Shallows are asynchronous by design. However, in some situations (for example, when scripting or testing) it could be useful to have synchronous storages. You can make any storage synchronous by calling .makeSyncStorage() on it:

let strings = DiskStorage.main.folder("strings", in: .cachesDirectory)
    .mapString(withEncoding: .utf8)
    .makeSyncStorage() // SyncStorage<String, String>
let existing = try strings.retrieve(forKey: "hello")
try strings.set(existing.uppercased(), forKey: "hello")

Mutating value for key

Shallows provides a convenient .update method on storages:

let arrays = MemoryStorage<String, [Int]>()
arrays.update(forKey: "some-key", { $0.append(10) })

Zipping storages

Zipping is a very powerful feature of Shallows. It allows you to compose your storages in a way that you get result only when both of them completes for your request. For example:

let strings = MemoryStorage<String, String>()
let numbers = MemoryStorage<String, Int>()
let zipped = zip(strings, numbers) // Storage<String, (String, Int)>
zipped.retrieve(forKey: "some-key") { (result) in
    if let (string, number) = result.value {
        print(string)
        print(number)
    }
}
zipped.set(("shallows", 3), forKey: "another-key")

Isn't it nice?

Different ways of composition

Storages can be composed in different ways. If you look at the combined method, it actually looks like this:

public func combined<StorageType : StorageProtocol>(with storage: StorageType,
                     pullStrategy: StorageCombinationPullStrategy,
                     setStrategy: StorageCombinationSetStrategy) -> Storage<Key, Value> where StorageType.Key == Key, StorageType.Value == Value

Where pullStrategy defaults to .pullThenComplete and setStrategy defaults to .frontFirst. Available options are:

public enum StorageCombinationPullStrategy {
    case pullThenComplete
    case completeThenPull
    case neverPull
}

public enum StorageCombinationSetStrategy {
    case backFirst
    case frontFirst
    case frontOnly
    case backOnly
}

You can change these parameters to accomplish a behavior you want.

Recovering from errors

You can protect your storage instance from failures using fallback(with:) or defaulting(to:) methods:

let storage = MemoryStorage<String, Int>()
let protected = storage.fallback(with: { error in
    switch error {
    case MemoryStorageError.noValue:
        return 15
    default:
        return -1
    }
})
let storage = MemoryStorage<String, Int>()
let defaulted = storage.defaulting(to: -1)

This is especially useful when using update method:

let storage = MemoryStorage<String, [Int]>()
storage.defaulting(to: []).update(forKey: "first", { $0.append(10) })

That means that in case of failure retrieving existing value, update will use default value of [] instead of just failing the whole update.

Using NSCacheStorage

NSCache is a tricky class: it supports only reference types, so you're forced to use, for example, NSData instead of Data and so on. To help you out, Shallows provides a set of convenience extensions for legacy Foundation types:

let nscache = NSCacheStorage<NSURL, NSData>()
    .toNonObjCKeys()
    .toNonObjCValues() // Storage<URL, Data>

Making your own storage

To create your own caching layer, you should conform to StorageProtocol. That means that you should define these two methods:

func retrieve(forKey key: Key, completion: @escaping (Result<Value>) -> ())
func set(_ value: Value, forKey key: Key, completion: @escaping (Result<Void>) -> ())

Where Key and Value are associated types.

NOTE: Please be aware that you should care about thread-safety of your implementation. Very often retrieve and set will not be called from the main thread, so you should make sure that no race conditions will occur.

To use it as Storage<Key, Value> instance, simply call .asStorage() on it:

let storage = MyStorage().asStorage()

You can also conform to a ReadableStorageProtocol only. That way, you only need to define a retrieve(forKey:completion:) method.

Installation

Shallows is available through Carthage. To install, just write into your Cartfile:

github "dreymonde/Shallows" ~> 0.8.0

shallows's People

Contributors

dreymonde avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.