https://github.com/narek-sv/keyvaluestorage
An elegant, fast, thread-safe, multipurpose key-value storage, compatible with all Apple platforms.
https://github.com/narek-sv/keyvaluestorage
cocoapods icloud ios key-value keychain maccatalyst macos multiplatform storage store swift swift-package-manager thread-safe tvos watchos
Last synced: 4 months ago
JSON representation
An elegant, fast, thread-safe, multipurpose key-value storage, compatible with all Apple platforms.
- Host: GitHub
- URL: https://github.com/narek-sv/keyvaluestorage
- Owner: narek-sv
- License: mit
- Created: 2022-07-27T06:42:23.000Z (about 3 years ago)
- Default Branch: main
- Last Pushed: 2025-05-15T10:46:25.000Z (5 months ago)
- Last Synced: 2025-06-16T10:56:13.400Z (4 months ago)
- Topics: cocoapods, icloud, ios, key-value, keychain, maccatalyst, macos, multiplatform, storage, store, swift, swift-package-manager, thread-safe, tvos, watchos
- Language: Swift
- Homepage:
- Size: 174 KB
- Stars: 17
- Watchers: 2
- Forks: 2
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# KeyValueStorage

[](https://github.com/narek-sv/KeyValueStorage/actions/workflows/swift.yml)
[](https://github.com/apple/swift-package-manager)
[](https://cocoapods.org/pods/KeyValueStorageSwift)---
Enhance your development with the state-of-the-art key-value storage framework, meticulously designed for speed, safety, and simplicity. Leveraging Swift's advanced error handling and concurrency features, the framework ensures thread-safe interactions, bolstered by a robust, modular, and protocol-oriented architecture. Unique to the solution, types of values are encoded within the keys, enabling compile-time type inference and eliminating the need for unnecessary casting. It is designed with App Groups in mind, facilitating seamless data sharing between your apps and extensions. Experience a testable, easily integrated storage solution that redefines efficiency and ease of use.
---
## Supported Platforms| | | | |
| --- | --- | --- | --- |
| **iOS** | **macOS** | **watchOS** | **tvOS** |
| 13.0+ | 10.15+ | 6.0+ | 13.0+ |## Built-in Storage Types
| | | | |
| --- | --- | --- | --- |
| **In Memory** | **User Defaults** | **Keychain** | **File System** |---
## App Groups`KeyValueStorage` also supports working with shared containers, which allows you to share your items among different ***App Extensions*** or ***your other Apps***. To do so, first, you need to configure your app by following the steps described in [this](https://developer.apple.com/documentation/security/keychain_services/keychain_items/sharing_access_to_keychain_items_among_a_collection_of_apps) article.
By providing corresponding `domain`s to each type of storage, you can enable the sharing of storage spaces. Alternatively, by doing so, you can also keep the containers isolated.
---
## UsageThe framework is capable of working with any type that conforms to `Codable` and `Sendable`.
The concept here is that first you need to declare the key. It contains every piece of information about how and where the value is stored.First, you need to declare the key. You can use one of the built-in types:
* `UserDefaultsKey`
* `KeychainKey`
* `InMemoryKey`
* `FileKey`or you can define your own ones. [See how to do that](#custom-storages)
```swift
import KeyValueStoragelet key = UserDefaultsKey(key: "myKey")
// or alternatively provide the domain
let otherKey = UserDefaultsKey(key: "myKey", domain: "sharedContainer")
```As you can see, the key holds all the necessary information about the value:
* The key name - `"myKey"`
* The storage type - `UserDefaults`
* The value type - `String`
* The domain (*optional*) - `"sharedContainer"`Now all that is left is to instantiate the storage and use it:
```swift
// Instantiate the storage
let storage = UnifiedStorage()// Saves the item and associates it with the key,
// or overrides the value if there is already such an item
try await storage.save("Alice", forKey: key)// Returns the item associated with the key or returns nil if there is no such item
let value = try await storage.fetch(forKey: key)// Deletes the item associated with the key or does nothing if there is no such item
try await storage.delete(forKey: key)// Sets the item identified by the key to the provided value
try await storage.set("Bob", forKey: key) // save
try await storage.set(nil, forKey: key) // delete// Clears only the storage associated with the specified storage and domain
try await storage.clear(storage: InMemoryStorage.self, forDomain: "someDomain")// Clears only the storage associated with the specified storage for all domains
try await storage.clear(storage: InMemoryStorage.self)// Clears the whole storage content
try await storage.clear()
```---
## Type InferenceThe framework leverages the full capabilities of ***Swift Generics***, so it can infer the types of values based on the key compile-time, eliminating the need for extra checks or type casting.
```swift
struct MyType: Codable, Sendable { ... }let key = UserDefaultsKey(key: "myKey")
let value = try await storage.fetch(forKey: key) // inferred type for value is MyType
try await storage.save(/* accepts only MyType*/, forKey: key)
```---
## Custom Storages`UnifiedStorage` has 4 built-in storage types:
* `In-memory` - This storage type persists the items only within an app session.
* `User-Defaults` - This storage type persists the items within the app's lifetime.
* `File-System` - This storage saves your key-values as separate files in your file system.
* `Keychain` - This storage type keeps the items in secure storage and persists even after app re-installations. Supports `iCloud` synchronization.You can also define your own storage, and it will work with it seamlessly with `UnifiedStorage` out of the box.
All you need to do is:
1. Define your own type that conforms to the `KeyValueDataStorage` protocol:
```swift
class NewStorage: KeyValueDataStorage { ... }
```
2. Define the new key type (optional, for ease of use):
```swift
typealias NewStorageKey = UnifiedStorageKey
```That's it. You can use it now as the built-in storages:
```swift
let key = NewStorageKey(key: customKey)
try await storage.save(UUID(), forKey: key)
```***NOTE***! You need to handle the thread safety of your storage on your own.
---
## Xcode autocompletionTo get the advantages of Xcode autocompletion, it is recommended to declare all your keys in the extension of the `UnifiedStorageKey`, like this:
```swift
extension UnifiedStorageKey {
static var key1: UserDefaultsKey {
.init(key: "key1", domain: nil)
}
static var key2: InMemoryKey {
.init(key: "key2", domain: "sharedContainer")
}
static var key3: KeychainKey {
.init(key: .init(name: "key3", accessibility: .afterFirstUnlock, isSynchronizable: true),
domain: .init(groupId: "groupId", teamId: "teamId"))
}
static var key4: FileKey {
.init(key: "key4", domain: "otherContainer")
}
}
```then Xcode will suggest all the keys specified in the extension when you put a dot:
---
## KeychainUse `accessibility` parameter to specify the security level of the keychain storage.
By default the `.whenUnlocked` option is used. It is one of the most restrictive options and provides good data protection.You can use `.afterFirstUnlock` if you need your app to access the keychain item while in the background. Note that it is less secure than the `.whenUnlocked` option.
Here are all the supported accessibility types:
* `afterFirstUnlock`
* `afterFirstUnlockThisDeviceOnly`
* `whenPasscodeSetThisDeviceOnly`
* `whenUnlocked`
* `whenUnlockedThisDeviceOnly`Set `synchronizable` property to `true` to enable keychain items synchronization across user's multiple devices. The synchronization will work for users who have the ***Keychain*** enabled in the ***iCloud*** settings on their devices. Deleting a synchronizable item will remove it from all devices.
```swift
let key = KeychainKey(key: .init(name: "key", accessibility: .afterFirstUnlock, isSynchronizable: true),
domain: .init(groupId: "groupId", teamId: "teamId"))
```---
## ObservationThe `UnifiedStorage` initializer takes a `factory` parameter that conforms to the `UnifiedStorageFactory` protocol, enabling customized storage instantiation and configuration. This feature is particularly valuable for mocking storage in tests or substituting default implementations with custom ones.
By default, this parameter is set to `DefaultUnifiedStorageFactory`, which omits observation capabilities to avoid excessive class burden. However, supplying an `ObservableUnifiedStorageFactory` instance as the parameter activates observation of all underlying storages for changes.
Combine style publishers:
```swift
let key = InMemoryKey(key: "key")
guard let publisher = try await storage.publisher(forKey: key) else {
// The storage is not properly configured
return
}let subscription = publisher.sink { value in
print(value) // String?
}
```Concurrency style async streams:
```swift
guard let stream = try await storage.stream(forKey: key) else {
// The storage is not properly configured
return
}for await value in stream {
print(value) // String?
}
```However, it's important to note that `UnifiedStorage` can only observe changes made through its own methods.
---
## Error handlingDespite the fact that all the methods of the `UnifiedStorage` are throwing, it will never throw an exception if you do all the initial setups correctly.
---
## Thread SafetyAll built-in types leverage the power of ***Swift Concurrency*** and are thread-safe and protected from race conditions and data racing. However, if you extend the storage with your own ones, it is your responsibility to make them thread-safe.
---
## TestsThe whole framework is thoroughly validated with high-quality unit tests.
Additionally, it serves as an excellent demonstration of how to use the framework as intended.---
## Installation### [Swift Package Manager](https://swift.org/package-manager/)
Once you have your Swift package set up, adding KeyValueStorage as a dependency is as easy as adding it to the `dependencies` value of your `Package.swift`:
```swift
dependencies: [
.package(url: "https://github.com/narek-sv/KeyValueStorage.git", .upToNextMajor(from: "2.0.0"))
]
```or
* In Xcode select *File > Add Packages*.
* Enter the project's URL: https://github.com/narek-sv/KeyValueStorage.gitIn any file you'd like to use the package in, don't forget to
import the framework:```swift
import KeyValueStorage
```### [CocoaPods](https://cocoapods.org)
To integrate KeyValueStorage into your Xcode project using CocoaPods, specify it in your `Podfile`:
```ruby
pod 'KeyValueStorageSwift'
```Then run `pod install`.
In any file you'd like to use the package in, don't forget to
import the framework:```swift
import KeyValueStorageSwift
```---
## LicenseSee [License.md](https://github.com/narek-sv/KeyValueStorage/blob/main/LICENSE) for more information.