Safe, statically-typed, store-agnostic key-value storage
Highlights
-
Fully Customizable:
Customize the persistence store, theKeyTypeclass, post-commit actions .. Make this framework yours! -
Batteries Included:
In case you just want to use stuff, the framework is shipped with pre-configured basic set of classes that you can just use. -
Portability, Check!:
If you're looking to share code between your app and extensions, you're covered! UseUserDefaultsStorewith a shared container suite name. -
Swift 6 Ready:
Full Swift 6 language mode with strict concurrency. All stores and keys areSendable.
Example:
final class WeatherService { private enum Keys: Namespace { static let id = "weather-service" static let temperature = Key<Keys, Double>(id: "temp", defaultValue: 0.0) } private let store: UserDefaultsStore var temperature: Double { return store.get(Keys.temperature) } init(store: UserDefaultsStore) { self.store = store } func weatherUpdate(temperature: Double) { store.set(Keys.temperature, temperature) } }
Features
Available Stores
| Store | Backend |
|---|---|
UserDefaultsStore |
UserDefaults |
CacheStore |
NSCache |
Type-safe, store-agnostic, nestable Key definitions
// Entries must belong to a "group", for namespacing struct Animals: Namespace { static let id = "animals" } let kingdom = Key<Animals, Void?>(id: "mammals", defaultValue: nil) kingdom.stringValue // "animals:mammals" // Nesting struct Cats: Namespace { typealias parent = Animals static let id = "cats" // Namespaces also have pre and post commit hooks func preCommitHook() { /* custom code */ } func postCommitHook() { /* custom code */ } } let cat = Key<Cats, Void?>(id: "lion", defaultValue: nil) cat.stringValue // "animals:cats:lion"
Initialize the store you want
// Use UserDefaultsStore for this example let store = UserDefaultsStore(suite: "io.kitz.testing") let key = Key<GlobalNamespace, Int?>(id: "key", defaultValue: nil) // With three simple functions store.set(key, value: 8) store.get(key) // 8 store.clear() // Start fresh every time for testing
Optionality is honored throughout
let nullable = Key<GlobalNamespace, String?>(id: "nullable", defaultValue: nil) store.get(nullable)?.isEmpty // nil store.set(nullable, value: "") store.get(nullable)?.isEmpty // true let nonnull = Key<GlobalNamespace, String>(id: "nonnull", defaultValue: "!") store.get(nonnull).isEmpty // false store.set(nonnull, value: "") store.get(nonnull).isEmpty // true
Custom objects easily supported via Codable
struct CustomObject: Codable { var strings: [String] } let customObject = CustomObject( strings: ["fill", "in", "the"] ) // Add a processing block to transform values on set let customKey = Key<GlobalNamespace, CustomObject?>(id: "custom", defaultValue: nil) { var processedValue = $0 processedValue?.strings.append("blank!") return processedValue } store.set(customKey, value: customObject) store.get(customKey)?.strings.joined(separator: " ") // fill in the blank!
You can also use UserDefaultsConvertible for custom serialization.
Make your own KeyType
// For example, make a key that posts notifications on change struct MyKey<G: Namespace, V>: KeyType { typealias NamespaceType = G typealias ValueType = V var stringValue: String var defaultValue: ValueType func didChange(_ oldValue: ValueType, newValue: ValueType) { NotificationCenter.default.post(name: .init(stringValue), object: nil) } }
Migrate from legacy keys
let migration = UserDefaultsStore.Migration( to: Key<MyNamespace, City>(id: "city", defaultValue: .default), from: "OldCityKey" ) { oldValue in // Convert the old raw value to the new type City(rawValue: oldValue as! String) } store.migrate([migration])
Getting Started
Swift Package Manager
You can add Storez to an Xcode project by adding it as a package dependency.
- In Xcode, from the File menu, select Add Packages...
- Enter "https://github.com/SwiftKitz/Storez" into the package repository URL text field
Depending on how your project is structured:
- If you have a single application target that needs access to the library, then add Storez directly to your application.
- If you want to use this library from multiple Xcode targets, or mixing Xcode targets and SPM targets, you likely want to create a shared framework that depends on Storez and then depend on that framework in all of your targets.
To use Storez in a Package.swift file, add this to the dependencies: section.
.package(url: "https://github.com/SwiftKitz/Storez.git", from: "5.0.0"),
Motivation
I've seen a lot of great attempts at statically-typed data stores, but they all build a tightly coupled design that limits the end-developer's freedom. With this framework, you can start prototyping right away with the shipped features, then replace the persistence store and KeyType functionality with your heart's content and keep your code the way it is!
Author
Mazyod (@Mazyod)
License
Storez is released under the MIT license. See LICENSE for details.