Skip to content

Commit

Permalink
Update README
Browse files Browse the repository at this point in the history
  • Loading branch information
dreymonde committed Jan 19, 2018
1 parent af7bb64 commit 1cd82ac
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 75 deletions.
113 changes: 44 additions & 69 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,34 +11,24 @@

## Usage

### Showcase

Using **Shallows** for two-step JSON storage (memory and disk):

```swift
struct Player : Codable {
struct City : Codable {
let name: String
let rating: Int
let foundationYear: Int
}

let memoryStorage = MemoryStorage<String, Player>()
let diskStorage = FileSystemStorage.inDirectory(.cachesDirectory, appending: "cache")
.mapJSONObject(Player.self)
.usingStringKeys()
let combinedStorage = memoryStorage.combined(with: diskStorage)
combinedStorage.retrieve(forKey: "Higgins") { (result) in
if let player = result.value {
print(player.name)
}
}
combinedStorage.set(Player(name: "Mark", rating: 1), forKey: "Selby") { (result) in
if result.isSuccess {
print("Success!")
}
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
## 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`:

Expand Down Expand Up @@ -67,34 +57,31 @@ storage.set(10, forKey: "some-key") { (result) in
}
```

#### Transforms
### Transforms

Keys and values can be mapped:

```swift
let stringStorage = storage.mapValues(transformIn: { String($0) },
transformOut: { try Int($0).unwrap() }) // Storage<String, String>
// ...
enum EnumKey : String {
case first, second, third
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 keyedStorage: Storage<EnumKey, String> = stringStorage.mapKeys({ $0.rawValue })
```

The concept of keys and values transformations is really powerful and it lies in the core of **Shallows**. For example, `FileSystemStorage` provides a `Storage<String, Data>` instances, and you can easily map `Data` to something useful. For example, `UIImage`:
let keyedImages = images
.usingStringKeys()
.mapKeys(toRawRepresentableType: ImageKeys.self)

```swift
// FileSystemStorage is a storage of Filename : Data
let fileSystemStorage = FileSystemStorage.inDirectory(.cachesDirectory, appending: "shallows-caches-1")
let imageStorage = fileSystemStorage.mapValues(transformIn: { try UIImage(data: $0).unwrap() },
transformOut: { try UIImagePNGRepresentation($0).unwrap() })
keyedImages.retrieve(forKey: .kitten, completion: { result in /* .. */ })
```

Now you have an instance of type `Storage<String, UIImage>` which can be used to store images without much fuss.

**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
### 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`:

Expand All @@ -108,11 +95,7 @@ It does several things:
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.

Great things about composing storages is that in the end, you still has your `Storage<Key, Value>` instance. That means that you can recompose storage layers however you want without breaking the usage code. It also makes the code that depends on `Storage` very easy to test.

The huge advantage of **Shallows** is that it doesn't try to hide the actual mechanism - the behavior of your storages is perfectly clear, and still very simple to understand and easy to use. You control how many layers your storage has, how it acts and what it stores. **Shallows** is not an end-product - instead, it's a tool that will help you build exactly what you need.

#### Read-only storage
### Read-only storage

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

Expand All @@ -123,58 +106,54 @@ let readOnly = storage.asReadOnlyStorage() // ReadOnlyStorage<Key, Value>
Read-only storages can also be mapped and composed:

```swift
let immutableFileStorage = FileSystemStorage.inDirectory(.cachesDirectory, appending: "shallows-immutable")
let immutableFileStorage = DiskStorage.main.folder("immutable", in: .applicationSupportDirectory)
.mapString(withEncoding: .utf8)
.asReadOnlyStorage()
let storage = MemoryStorage<String, String>()
.combined(with: immutableFileStorage)
.asReadOnlyStorage() // ReadOnlyStorage<String, String>
let storage = MemoryStorage<Filename, String>()
.backed(by: immutableFileStorage)
.asReadOnlyStorage() // ReadOnlyStorage<Filename, String>
```

#### Write-only storage
### Write-only storage

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

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

#### Single element storage
### 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:

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

#### Synchronous storage
### Synchronous storage

Storages in **Shallows** are asynchronous by it's nature. 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:
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:

```swift
let strings = FileSystemStorage.inDirectory(.cachesDirectory, appending: "strings")
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")
```

However, be careful with that: some storages may be designed to complete more than one time (for example, some storages may quickly return value stored in a local storage and then ask the server for an update). Making a storage like this synchronous will kill that functionality.

#### Mutating value for key
### Mutating value for key

**Shallows** provides a convenient `.update` method on storages:

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

#### Zipping storages
Expand All @@ -191,16 +170,12 @@ zipped.retrieve(forKey: "some-key") { (result) in
print(number)
}
}
zipped.set(("shallows", 3), forKey: "another-key") { (result) in
if result.isSuccess {
print("Yay!")
}
}
zipped.set(("shallows", 3), forKey: "another-key")
```

Isn't it nice?

#### Different ways of composition
### Different ways of composition

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

Expand Down Expand Up @@ -229,7 +204,7 @@ public enum StorageCombinationSetStrategy {

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

#### Recovering from errors
### Recovering from errors

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

Expand Down Expand Up @@ -259,7 +234,7 @@ 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`
### 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:

Expand Down Expand Up @@ -294,7 +269,7 @@ You can also conform to a `ReadableStorageProtocol` only. That way, you only nee
**Shallows** is available through [Carthage][carthage-url]. To install, just write into your Cartfile:

```ruby
github "dreymonde/Shallows" ~> 0.7.0
github "dreymonde/Shallows" ~> 0.8.0
```

[carthage-url]: https://github.com/Carthage/Carthage
Expand Down
6 changes: 0 additions & 6 deletions Sources/Shallows/DiskStorage/DiskStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,6 @@ public struct Filename : RawRepresentable, Hashable, ExpressibleByStringLiteral

public final class DiskFolderStorage : StorageProtocol {

public typealias Key = Filename
public typealias Value = Data

public let storageName: String
public let folderURL: URL

Expand Down Expand Up @@ -145,9 +142,6 @@ extension DiskFolderStorage {

public final class DiskStorage : StorageProtocol {

public typealias Key = URL
public typealias Value = Data

public var storageName: String {
return "disk"
}
Expand Down
94 changes: 94 additions & 0 deletions Tests/ShallowsTests/XCTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
//

import XCTest
import Shallows
#if os(iOS)
import UIKit
#endif

class ShallowsXCTests : XCTestCase {

Expand All @@ -16,3 +20,93 @@ class ShallowsXCTests : XCTestCase {
}

}



func readme() {
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")
}

func readme2() {
#if os(iOS)
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 /* .. */ })
#endif
}

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

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

func readme5() throws {
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")
}

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

func readme7() {
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")
}

0 comments on commit 1cd82ac

Please sign in to comment.