{"id":28746748,"url":"https://github.com/narek-sv/keyvaluestorage","last_synced_at":"2025-06-16T15:12:40.650Z","repository":{"id":49839844,"uuid":"518340424","full_name":"narek-sv/KeyValueStorage","owner":"narek-sv","description":"An elegant, fast, thread-safe, multipurpose key-value storage, compatible with all Apple platforms.","archived":false,"fork":false,"pushed_at":"2025-05-15T10:46:25.000Z","size":178,"stargazers_count":17,"open_issues_count":2,"forks_count":2,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-06-16T10:56:13.400Z","etag":null,"topics":["cocoapods","icloud","ios","key-value","keychain","maccatalyst","macos","multiplatform","storage","store","swift","swift-package-manager","thread-safe","tvos","watchos"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/narek-sv.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-07-27T06:42:23.000Z","updated_at":"2025-06-15T07:48:22.000Z","dependencies_parsed_at":"2023-12-29T22:34:51.974Z","dependency_job_id":"8b7f13ec-fe1f-4231-8517-2279dc594410","html_url":"https://github.com/narek-sv/KeyValueStorage","commit_stats":{"total_commits":40,"total_committers":1,"mean_commits":40.0,"dds":0.0,"last_synced_commit":"b5e62ea997350f23eccea1ec32ed3ad3459fcf9f"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"purl":"pkg:github/narek-sv/KeyValueStorage","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/narek-sv%2FKeyValueStorage","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/narek-sv%2FKeyValueStorage/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/narek-sv%2FKeyValueStorage/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/narek-sv%2FKeyValueStorage/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/narek-sv","download_url":"https://codeload.github.com/narek-sv/KeyValueStorage/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/narek-sv%2FKeyValueStorage/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260183302,"owners_count":22971204,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cocoapods","icloud","ios","key-value","keychain","maccatalyst","macos","multiplatform","storage","store","swift","swift-package-manager","thread-safe","tvos","watchos"],"created_at":"2025-06-16T15:12:38.769Z","updated_at":"2025-06-16T15:12:40.636Z","avatar_url":"https://github.com/narek-sv.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# KeyValueStorage\n\n![Build \u0026 Test](https://github.com/narek-sv/KeyValueStorage/actions/workflows/swift.yml/badge.svg)\n[![Coverage](https://img.shields.io/badge/coverage-\u003e=90%25-brightgreen)](https://github.com/narek-sv/KeyValueStorage/actions/workflows/swift.yml)\n[![Swift Package Manager compatible](https://img.shields.io/badge/Swift%20Package%20Manager-compatible-success.svg)](https://github.com/apple/swift-package-manager)\n[![CocoaPods compatible](https://img.shields.io/cocoapods/v/KeyValueStorageSwift)](https://cocoapods.org/pods/KeyValueStorageSwift)\n\n---\n\nEnhance 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.\n\n\n---\n## Supported Platforms\n\n| | | | |\n| --- | --- | --- | --- |\n| **iOS** | **macOS** | **watchOS** | **tvOS** |\n| 13.0+ | 10.15+ | 6.0+ | 13.0+ |\n\n## Built-in Storage Types\n\n| | | | |\n| --- | --- | --- | --- |\n| **In Memory** | **User Defaults** | **Keychain** | **File System** |\n\n---\n## App Groups\n\n`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.\n\nBy 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.\n\n---\n## Usage\n\nThe framework is capable of working with any type that conforms to `Codable` and `Sendable`.\nThe 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.\n\nFirst, you need to declare the key. You can use one of the built-in types:\n\n* `UserDefaultsKey`\n* `KeychainKey`\n* `InMemoryKey`\n* `FileKey`\n\nor you can define your own ones. [See how to do that](#custom-storages)\n\n```swift\nimport KeyValueStorage\n\nlet key = UserDefaultsKey\u003cString\u003e(key: \"myKey\")\n// or alternatively provide the domain\nlet otherKey = UserDefaultsKey\u003cString\u003e(key: \"myKey\", domain: \"sharedContainer\")\n```\n\nAs you can see, the key holds all the necessary information about the value:\n* The key name - `\"myKey\"`\n* The storage type - `UserDefaults`\n* The value type - `String`\n* The domain (*optional*) - `\"sharedContainer\"`\n\n\nNow all that is left is to instantiate the storage and use it:\n\n```swift\n// Instantiate the storage\nlet storage = UnifiedStorage()\n\n// Saves the item and associates it with the key, \n// or overrides the value if there is already such an item\ntry await storage.save(\"Alice\", forKey: key)\n\n// Returns the item associated with the key or returns nil if there is no such item\nlet value = try await storage.fetch(forKey: key) \n\n// Deletes the item associated with the key or does nothing if there is no such item\ntry await storage.delete(forKey: key)\n\n// Sets the item identified by the key to the provided value\ntry await storage.set(\"Bob\", forKey: key) // save\ntry await storage.set(nil, forKey: key) // delete\n\n// Clears only the storage associated with the specified storage and domain\ntry await storage.clear(storage: InMemoryStorage.self, forDomain: \"someDomain\")\n\n// Clears only the storage associated with the specified storage for all domains\ntry await storage.clear(storage: InMemoryStorage.self)\n\n// Clears the whole storage content\ntry await storage.clear()\n```\n\n---\n## Type Inference\n\nThe 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.\n\n```swift\nstruct MyType: Codable, Sendable { ... }\n\nlet key = UserDefaultsKey\u003cMyType\u003e(key: \"myKey\")\nlet value = try await storage.fetch(forKey: key) // inferred type for value is MyType\ntry await storage.save(/* accepts only MyType*/, forKey: key)\n```\n\n---\n## Custom Storages\n\n`UnifiedStorage` has 4 built-in storage types:\n* `In-memory` - This storage type persists the items only within an app session.\n* `User-Defaults` - This storage type persists the items within the app's lifetime.\n* `File-System` -  This storage saves your key-values as separate files in your file system.\n* `Keychain` - This storage type keeps the items in secure storage and persists even after app re-installations. Supports `iCloud` synchronization.\n\nYou can also define your own storage, and it will work with it seamlessly with `UnifiedStorage` out of the box.\nAll you need to do is:\n1. Define your own type that conforms to the `KeyValueDataStorage` protocol:\n```swift\nclass NewStorage: KeyValueDataStorage { ... }\n```\n2. Define the new key type (optional, for ease of use):\n```swift\ntypealias NewStorageKey\u003cValue: CodingValue\u003e = UnifiedStorageKey\u003cNewStorage, Value\u003e\n```\n\nThat's it. You can use it now as the built-in storages:\n\n```swift\nlet key = NewStorageKey\u003cUUID\u003e(key: customKey)\ntry await storage.save(UUID(), forKey: key)\n```\n\n***NOTE***! You need to handle the thread safety of your storage on your own.\n\n---\n## Xcode autocompletion \n\nTo get the advantages of Xcode autocompletion, it is recommended to declare all your keys in the extension of the `UnifiedStorageKey`, like this:\n\n```swift\nextension UnifiedStorageKey {\n    static var key1: UserDefaultsKey\u003cInt\u003e {\n        .init(key: \"key1\", domain: nil)\n    }\n    \n    static var key2: InMemoryKey\u003cDate\u003e {\n        .init(key: \"key2\", domain: \"sharedContainer\")\n    }\n    \n    static var key3: KeychainKey\u003cDouble\u003e {\n        .init(key: .init(name: \"key3\", accessibility: .afterFirstUnlock, isSynchronizable: true), \n              domain: .init(groupId: \"groupId\", teamId: \"teamId\"))\n    }\n    \n    static var key4: FileKey\u003cUUID\u003e {\n        .init(key: \"key4\", domain: \"otherContainer\")\n    }\n}\n```\n\nthen Xcode will suggest all the keys specified in the extension when you put a dot:\n\u003cimg width=\"620\" alt=\"Screenshot 2024-03-03 at 13 43 39\" src=\"https://github.com/narek-sv/KeyValueStorage/assets/23353201/992873cd-1030-4c95-87cb-e3788c09e5cc\"\u003e\n\n---\n## Keychain\n\nUse `accessibility` parameter to specify the security level of the keychain storage.\nBy default the `.whenUnlocked` option is used. It is one of the most restrictive options and provides good data protection.\n\nYou 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.\n\nHere are all the supported accessibility types:\n* `afterFirstUnlock`\n* `afterFirstUnlockThisDeviceOnly`\n* `whenPasscodeSetThisDeviceOnly`\n* `whenUnlocked`\n* `whenUnlockedThisDeviceOnly`\n\nSet `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.\n\n```swift\nlet key = KeychainKey\u003cString\u003e(key: .init(name: \"key\", accessibility: .afterFirstUnlock, isSynchronizable: true),\n                              domain: .init(groupId: \"groupId\", teamId: \"teamId\"))\n```\n\n---\n## Observation\n\nThe `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.\n\nBy 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.\n\n\nCombine style publishers:\n```swift\nlet key = InMemoryKey\u003cString\u003e(key: \"key\")\nguard let publisher = try await storage.publisher(forKey: key) else {\n    // The storage is not properly configured\n    return\n}\n\nlet subscription = publisher.sink { value in\n    print(value) // String?\n}\n```\n\nConcurrency style async streams:\n```swift\nguard let stream = try await storage.stream(forKey: key) else {\n    // The storage is not properly configured\n    return\n}\n\nfor await value in stream {\n    print(value) // String?\n}\n```\n\nHowever, it's important to note that `UnifiedStorage` can only observe changes made through its own methods.\n\n---\n## Error handling\n\nDespite 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.\n\n---\n## Thread Safety\n\nAll 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.\n\n---\n## Tests\n\nThe whole framework is thoroughly validated with high-quality unit tests. \nAdditionally, it serves as an excellent demonstration of how to use the framework as intended.\n\n---\n## Installation\n\n### [Swift Package Manager](https://swift.org/package-manager/)\n\nOnce 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`:\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/narek-sv/KeyValueStorage.git\", .upToNextMajor(from: \"2.0.0\"))\n]\n```\n\nor\n\n* In Xcode select *File \u003e Add Packages*.\n* Enter the project's URL: https://github.com/narek-sv/KeyValueStorage.git\n\nIn any file you'd like to use the package in, don't forget to\nimport the framework:\n\n```swift\nimport KeyValueStorage\n```\n\n### [CocoaPods](https://cocoapods.org)\n\nTo integrate KeyValueStorage into your Xcode project using CocoaPods, specify it in your `Podfile`:\n\n```ruby\npod 'KeyValueStorageSwift'\n```\n\nThen run `pod install`.\n\nIn any file you'd like to use the package in, don't forget to\nimport the framework:\n\n```swift\nimport KeyValueStorageSwift\n```\n\n---\n## License\n\nSee [License.md](https://github.com/narek-sv/KeyValueStorage/blob/main/LICENSE) for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnarek-sv%2Fkeyvaluestorage","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnarek-sv%2Fkeyvaluestorage","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnarek-sv%2Fkeyvaluestorage/lists"}