A typed key-value storage solution to store Codable types in various persistence layers like User Defaults, File System, Core Data, Keychain, and more with a few lines of code!
Features
macOS Catalina+, iOS 13+, tvOS 13+, watchOS 6+, any Linux supporting Swift 5.4+.
Store any Codable type.
Single API with implementations using User Default, file system, Core Data, Keychain, and fakes for testing.
Thread-safe implementations.
Swappable implementations with a type-erased store.
When working on an app that uses the composable architecture, I fell in love with how reducers use an environment type that holds any dependencies the feature needs, such as API clients, analytics clients, and more.
Stores tries to abstract the concept of a store and provide various implementations that can be injected in such environment and swapped easily when running tests or based on a remote flag.
It all boils down to the two protocols SingleObjectStore and MultiObjectStore defined in the Blueprints layer, which provide the abstract concepts of stores that can store a single or multiple objects of a generic Codable type.
The two protocols are then implemented in the different modules as explained in the chart below:
Usage
Let’s say you have a User struct defined as below:
struct User: Codable {
let id: Int
let name: String
}
Here’s how you store it using Stores:
1. Conform to Identifiable
This is required to make the store associate an object with its id.
extension User: Identifiable {}
The property id can be on any Hashable type. Read more.
2. Create a store
Stores comes pre-equipped with the following stores:
UserDefaults
// Store for multiple objects
let store = MultiUserDefaultsStore<User>(suiteName: "users")
// Store for a single object
let store = SingleUserDefaultsStore<User>(suiteName: "users")
FileSystem
// Store for multiple objects
let store = MultiFileSystemStore<User>(path: "users")
// Store for a single object
let store = SingleFileSystemStore<User>(path: "users")
CoreData
// Store for multiple objects
let store = MultiCoreDataStore<User>(databaseName: "users")
// Store for a single object
let store = SingleCoreDataStore<User>(databaseName: "users")
Keychain
// Store for multiple objects
let store = MultiKeychainStore<User>(identifier: "users")
// Store for a single object
let store = SingleKeychainStore<User>(identifier: "users")
Fakes (for testing)
// Store for multiple objects
let store = MultiObjectStoreFake<User>()
// Store for a single object
let store = SingleObjectStoreFake<User>()
You can create a custom store by implementing the protocols in Blueprints
Realm
// Store for multiple objects
final class MultiRealmStore<Object: Codable & Identifiable>: MultiObjectStore {
// ...
}
// Store for a single object
final class SingleRealmStore<Object: Codable>: SingleObjectStore {
// ...
}
SQLite
// Store for multiple objects
final class MultiSQLiteStore<Object: Codable & Identifiable>: MultiObjectStore {
// ...
}
// Store for a single object
final class SingleSQLiteStore<Object: Codable>: SingleObjectStore {
// ...
}
3. Inject the store
Assuming we have a view model that uses a store to fetch data:
struct UsersViewModel {
let store: AnyMultiObjectStore<User>
}
Inject the appropriate store implementation:
let coreDataStore = MultiCoreDataStore<User>(databaseName: "users")
let prodViewModel = UsersViewModel(store: coreDataStore.eraseToAnyStore())
or:
let fakeStore = MultiObjectStoreFake<User>()
let testViewModel = UsersViewModel(store: fakeStore.eraseToAnyStore())
4. Save, retrieve, update, or remove objects
let john = User(id: 1, name: "John Appleseed")
// Save an object to a store
try store.save(john)
// Save an array of objects to a store
try store.save([jane, steve, jessica])
// Get an object from store
let user = store.object(withId: 1)
// Get an array of object in store
let users = store.objects(withIds: [1, 2, 3])
// Get an array of all objects in store
let allUsers = store.allObjects()
// Check if store has an object
print(store.containsObject(withId: 10)) // false
// Remove an object from a store
try store.remove(withId: 1)
// Remove multiple objects from a store
try store.remove(withIds: [1, 2, 3])
// Remove all objects in a store
try store.removeAll()
🗂 Stores
A typed key-value storage solution to store
Codable
types in various persistence layers like User Defaults, File System, Core Data, Keychain, and more with a few lines of code!Features
Codable
type.Motivation
When working on an app that uses the composable architecture, I fell in love with how reducers use an environment type that holds any dependencies the feature needs, such as API clients, analytics clients, and more.
Stores tries to abstract the concept of a store and provide various implementations that can be injected in such environment and swapped easily when running tests or based on a remote flag.
It all boils down to the two protocols
SingleObjectStore
andMultiObjectStore
defined in the Blueprints layer, which provide the abstract concepts of stores that can store a single or multiple objects of a genericCodable
type.The two protocols are then implemented in the different modules as explained in the chart below:
Usage
Let’s say you have a
User
struct defined as below:Here’s how you store it using Stores:
1. Conform to
Identifiable
This is required to make the store associate an object with its id.
The property
id
can be on anyHashable
type. Read more.2. Create a store
Stores comes pre-equipped with the following stores:
UserDefaults
FileSystem
CoreData
Keychain
Fakes (for testing)
You can create a custom store by implementing the protocols in
Blueprints
Realm
SQLite
3. Inject the store
Assuming we have a view model that uses a store to fetch data:
Inject the appropriate store implementation:
or:
4. Save, retrieve, update, or remove objects
Documentation
Read the full documentation at Swift Package Index.
Installation
You can add Stores to an Xcode project by adding it as a package dependency.
Stores
: the entire library with all stores.UserDefaultsStore
: use User Defaults to persist data.FileSystemStore
: persist data by saving it to the file system.CoreDataStore
: use a Core Data database to persist data.KeychainStore
: persist data securely in the Keychain.Blueprints
: protocols only, this is a good option if you do not want to use any of the provided stores and build yours.StoresTestUtils
to use the fakes in your tests target.Credits and thanks
License
Stores is released under the MIT license. See LICENSE for more information.