Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/dreymonde/Shallows
🛶 Your lightweight persistence toolbox
https://github.com/dreymonde/Shallows
Last synced: 3 days ago
JSON representation
🛶 Your lightweight persistence toolbox
- Host: GitHub
- URL: https://github.com/dreymonde/Shallows
- Owner: dreymonde
- License: mit
- Created: 2017-04-23T21:34:15.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2022-05-01T13:02:05.000Z (over 2 years ago)
- Last Synced: 2024-12-05T20:02:48.610Z (7 days ago)
- Language: Swift
- Homepage: https://medium.com/anysuggestion
- Size: 211 KB
- Stars: 623
- Watchers: 13
- Forks: 20
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-ios - Shallows - Your lightweight persistence toolbox. (Database / Getting Started)
- awesome-swift - Shallows - Your lightweight persistence toolbox. (Libs / Data Management)
- awesome-swift - Shallows - Your lightweight persistence toolbox. (Libs / Data Management)
- awesome-ios - Shallows
- fucking-awesome-swift - Shallows - Your lightweight persistence toolbox. (Libs / Data Management)
- awesome-ios-star - Shallows - Your lightweight persistence toolbox. (Database / Getting Started)
- fucking-awesome-ios - Shallows - Your lightweight persistence toolbox. (Database / Getting Started)
- fucking-awesome-ios - Shallows - Your lightweight persistence toolbox. (Database / Getting Started)
- awesome-swift - Shallows - Your lightweight persistence toolbox ` 📝 5 months ago` (Data Management [🔝](#readme))
README
# Shallows
[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fdreymonde%2FShallows%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/dreymonde/Shallows) [![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fdreymonde%2FShallows%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/dreymonde/Shallows)
**Shallows** is a generic abstraction layer over lightweight data storage and persistence. It provides a `Storage` 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][carlos-github-url] and [this amazing talk][composable-caches-in-swift-url] by [Brandon Kase][brandon-kase-twitter-url].
**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
```swift
struct City : Codable {
let name: String
let foundationYear: Int
}let diskStorage = DiskStorage.main.folder("cities", in: .cachesDirectory)
.mapJSONObject(City.self) // Storagelet kharkiv = City(name: "Kharkiv", foundationYear: 1654)
diskStorage.set(kharkiv, forKey: "kharkiv")diskStorage.retrieve(forKey: "kharkiv") { (result) in
if let city = try? result.get() { print(city) }
}// or
let city = try await diskStorage.retrieve(forKey: "kharkiv")
```
## Guide
A main type of **Shallows** is `Storage`. 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`:
```swift
let storage = MemoryStorage().asStorage() // Storage
```Storage instances have `retrieve` and `set` methods, which are asynhronous and fallible:
```swift
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:
```swift
let storage = DiskStorage.main.folder("images", in: .cachesDirectory) // Storage
let images = storage
.mapValues(to: UIImage.self,
transformIn: { data in try UIImage.init(data: data).unwrap() },
transformOut: { image in try UIImagePNGRepresentation(image).unwrap() }) // Storageenum ImageKeys : String {
case kitten, puppy, fish
}let keyedImages = images
.usingStringKeys()
.mapKeys(toRawRepresentableType: ImageKeys.self) // StoragekeyedImages.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`:
```swift
let efficient = MemoryStorage().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:
```swift
let readOnly = storage.asReadOnlyStorage() // ReadOnlyStorage
```Read-only storages can also be mapped and composed:
```swift
let immutableFileStorage = DiskStorage.main.folder("immutable", in: .applicationSupportDirectory)
.mapString(withEncoding: .utf8)
.asReadOnlyStorage()
let storage = MemoryStorage()
.backed(by: immutableFileStorage)
.asReadOnlyStorage() // ReadOnlyStorage
```### Write-only storage
In similar way, write-only storage is also available:
```swift
let writeOnly = storage.asWriteOnlyStorage() // WriteOnlyStorage
```### Different ways of composition
**Compositions available for `Storage`**:
- `.combined(with:)` (see [Storages composition](#Storages-composition))
- `.backed(by:)` will work the same as `combined(with:)`, but it will not push the value to the back storage
- `.pushing(to:)` will not retrieve the value from the back storage, but will push to it on `set`**Compositions available for `ReadOnlyStorage`**:
- `.backed(by:)`
**Compositions available for `WriteOnlyStorage`**:
- `.pushing(to:)`
### 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 settings = DiskStorage.main.folder("settings", in: .applicationSupportDirectory)
.mapJSONDictionary()
.singleKey("settings") // Storage
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:
```swift
let strings = DiskStorage.main.folder("strings", in: .cachesDirectory)
.mapString(withEncoding: .utf8)
.makeSyncStorage() // SyncStorage
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:
```swift
let arrays = MemoryStorage()
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:
```swift
let strings = MemoryStorage()
let numbers = MemoryStorage()
let zipped = zip(strings, numbers) // Storage
zipped.retrieve(forKey: "some-key") { (result) in
if let (string, number) = try? result.get() {
print(string)
print(number)
}
}
zipped.set(("shallows", 3), forKey: "another-key")
```Isn't it nice?
### Recovering from errors
You can protect your storage instance from failures using `fallback(with:)` or `defaulting(to:)` methods:
```swift
let storage = MemoryStorage()
let protected = storage.fallback(with: { error in
switch error {
case MemoryStorageError.noValue:
return 15
default:
return -1
}
})
``````swift
let storage = MemoryStorage()
let defaulted = storage.defaulting(to: -1)
```This is _especially_ useful when using `update` method:
```swift
let storage = MemoryStorage()
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:
```swift
let nscache = NSCacheStorage()
.toNonObjCKeys()
.toNonObjCValues() // Storage
```### Making your own storage
To create your own caching layer, you should conform to `StorageProtocol`. That means that you should define these two methods:
```swift
func retrieve(forKey key: Key, completion: @escaping (Result) -> ())
func set(_ value: Value, forKey key: Key, completion: @escaping (Result) -> ())
```Where `Key` and `Value` are associated types.
**NOTE:** Please be aware that you are responsible for the 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` instance, simply call `.asStorage()` on it:
```swift
let storage = MyStorage().asStorage()
```You can also conform to a `ReadOnlyStorageProtocol` only. That way, you only need to define a `retrieve(forKey:completion:)` method.
## Installation
#### Swift Package Manager
Starting with Xcode 11, **Shallows** is officially available *only* via Swift Package Manager.
In Xcode 11 or greater, in you project, select: `File > Swift Packages > Add Package Dependency`
In the search bar type
```
https://github.com/dreymonde/Shallows
```Then proceed with installation.
> If you can't find anything in the panel of the Swift Packages you probably haven't added yet your github account.
You can do that under the **Preferences** panel of your Xcode, in the **Accounts** section.For command-line based apps, you can just add this directly to your **Package.swift** file:
```swift
dependencies: [
.package(url: "https://github.com/dreymonde/Shallows", from: "0.13.0"),
]
```#### Manual
Of course, you always have an option of just copying-and-pasting the code.
#### Deprecated dependency managers
Last **Shallows** version to support [Carthage][carthage-url] and [Cocoapods][cocoapods-url] is **0.10.0**. Carthage and Cocoapods will no longer be officially supported.
Carthage:
```ruby
github "dreymonde/Shallows" ~> 0.10.0
```Cocoapods:
```ruby
pod 'Shallows', '~> 0.10.0'
```[carthage-url]: https://github.com/Carthage/Carthage
[swift-badge]: https://img.shields.io/badge/Swift-5.1-orange.svg?style=flat
[swift-url]: https://swift.org
[platform-badge]: https://img.shields.io/badge/platform-iOS%20%7C%20macOS%20%7C%20watchOS%20%7C%20tvOS-lightgrey.svg
[platform-url]: https://developer.apple.com/swift/
[carlos-github-url]: https://github.com/WeltN24/Carlos
[composable-caches-in-swift-url]: https://www.youtube.com/watch?v=8uqXuEZLyUU
[brandon-kase-twitter-url]: https://twitter.com/bkase_
[avenues-github-url]: https://github.com/dreymonde/Avenues
[avenues-shallows-github-url]: https://github.com/dreymonde/Avenues-Shallows
[cocoapods-url]: https://github.com/CocoaPods/CocoaPods