Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
d36ad5a785
commit
36501f4218
27
README.md
27
README.md
@ -41,8 +41,19 @@ SharedPackage/
|
||||
└── UserProfile.swift
|
||||
SecureStorgageSample/
|
||||
├── ContentView.swift # Tabbed navigation
|
||||
├── Models/
|
||||
│ ├── Credential.swift
|
||||
│ └── SampleLocationData.swift
|
||||
├── StorageKeys.swift # 12 example key definitions
|
||||
├── StorageKeys/
|
||||
│ ├── UserDefaults/
|
||||
│ ├── Keychain/
|
||||
│ ├── FileSystem/
|
||||
│ ├── EncryptedFileSystem/
|
||||
│ └── Platform/
|
||||
├── WatchOptimized.swift # Watch data models
|
||||
├── Services/
|
||||
│ └── WatchConnectivityService.swift
|
||||
└── Views/
|
||||
├── UserDefaultsDemo.swift
|
||||
├── KeychainDemo.swift
|
||||
@ -52,8 +63,16 @@ SecureStorgageSample/
|
||||
SecureStorageSample Watch App/
|
||||
├── SecureStorageSampleApp.swift
|
||||
├── ContentView.swift
|
||||
├── Models/
|
||||
│ └── UserProfile.swift
|
||||
├── Protocols/
|
||||
│ └── WatchDataHandling.swift
|
||||
├── State/
|
||||
│ └── WatchProfileStore.swift
|
||||
└── Services/
|
||||
└── WatchConnectivityService.swift
|
||||
├── WatchConnectivityService.swift
|
||||
└── Handlers/
|
||||
└── UserProfileWatchHandler.swift
|
||||
```
|
||||
|
||||
## Storage Key Examples
|
||||
@ -87,6 +106,12 @@ The app demonstrates various storage configurations:
|
||||
- [LocalData](../localPackages/LocalData) - Local package for typed secure storage
|
||||
- SharedKit - Local package for shared iOS/watch models and constants
|
||||
|
||||
## Notes
|
||||
|
||||
- Storage keys are now split into one file per key and grouped by domain; platform-focused keys live in `StorageKeys/Platform` with comments calling out availability/sync focus.
|
||||
- The shared model/constants live in `SharedPackage` (`SharedKit`) to keep the watch/iOS data contract centralized.
|
||||
- The watch app uses a handler-based WatchConnectivity layer so new payload types can be added in `Services/Handlers` without bloating the main service.
|
||||
|
||||
## License
|
||||
|
||||
This sample is provided for demonstration purposes.
|
||||
|
||||
@ -9,6 +9,8 @@
|
||||
/* Begin PBXBuildFile section */
|
||||
EA179D562F17379800B1D54A /* LocalData in Frameworks */ = {isa = PBXBuildFile; productRef = EA179D552F17379800B1D54A /* LocalData */; };
|
||||
EA65D70D2F17DDEB00C48466 /* SecureStorageSample Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = EA65D6E52F17DD6700C48466 /* SecureStorageSample Watch App.app */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
|
||||
EA65D9442F17EAD800C48466 /* SharedKit in Frameworks */ = {isa = PBXBuildFile; productRef = EA65D7312F17DDEB00C48466 /* SharedKit */; };
|
||||
EA65D9452F17EAD800C48466 /* SharedKit in Frameworks */ = {isa = PBXBuildFile; productRef = EA65D7312F17DDEB00C48466 /* SharedKit */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@ -110,6 +112,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
EA65D9442F17EAD800C48466 /* SharedKit in Frameworks */,
|
||||
EA179D562F17379800B1D54A /* LocalData in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@ -118,6 +121,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
EA65D9452F17EAD800C48466 /* SharedKit in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
8
SecureStorgageSample/Models/Credential.swift
Normal file
8
SecureStorgageSample/Models/Credential.swift
Normal file
@ -0,0 +1,8 @@
|
||||
import Foundation
|
||||
|
||||
/// Simple credential model for keychain storage demo.
|
||||
nonisolated
|
||||
struct Credential: Codable, Sendable {
|
||||
let username: String
|
||||
let password: String
|
||||
}
|
||||
8
SecureStorgageSample/Models/SampleLocationData.swift
Normal file
8
SecureStorgageSample/Models/SampleLocationData.swift
Normal file
@ -0,0 +1,8 @@
|
||||
import Foundation
|
||||
|
||||
/// Location data model.
|
||||
nonisolated
|
||||
struct SampleLocationData: Codable, Sendable {
|
||||
let lat: Double
|
||||
let lon: Double
|
||||
}
|
||||
@ -1,271 +0,0 @@
|
||||
//
|
||||
// StorageKeys.swift
|
||||
// SecureStorgageSample
|
||||
//
|
||||
// Example StorageKey implementations demonstrating all variations
|
||||
// supported by the LocalData package.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import LocalData
|
||||
import SharedKit
|
||||
|
||||
// MARK: - Sample Data Models
|
||||
|
||||
/// Simple credential model for keychain storage demo.
|
||||
nonisolated
|
||||
struct Credential: Codable, Sendable {
|
||||
let username: String
|
||||
let password: String
|
||||
}
|
||||
|
||||
/// Location data model.
|
||||
nonisolated
|
||||
struct SampleLocationData: Codable, Sendable {
|
||||
let lat: Double
|
||||
let lon: Double
|
||||
}
|
||||
|
||||
// MARK: - UserDefaults Keys
|
||||
|
||||
extension StorageKeys {
|
||||
|
||||
/// Stores the app version in standard UserDefaults.
|
||||
/// - Domain: UserDefaults (standard)
|
||||
/// - Security: None
|
||||
/// - Sync: Automatic for small data
|
||||
struct AppVersionKey: StorageKey {
|
||||
typealias Value = String
|
||||
|
||||
let name = "last_app_version"
|
||||
let domain: StorageDomain = .userDefaults(suite: nil)
|
||||
let security: SecurityPolicy = .none
|
||||
let serializer: Serializer<String> = .json
|
||||
let owner = "SampleApp"
|
||||
let availability: PlatformAvailability = .all
|
||||
let syncPolicy: SyncPolicy = .automaticSmall
|
||||
}
|
||||
|
||||
/// Stores user preferences in a custom suite.
|
||||
/// - Domain: UserDefaults (custom suite)
|
||||
/// - Security: None
|
||||
/// - Sync: Never
|
||||
struct UserPreferencesKey: StorageKey {
|
||||
typealias Value = [String: AnyCodable]
|
||||
|
||||
let name = "user_preferences"
|
||||
let domain: StorageDomain = .userDefaults(suite: "group.com.example.securestorage")
|
||||
let security: SecurityPolicy = .none
|
||||
let serializer: Serializer<[String: AnyCodable]> = .json
|
||||
let owner = "SampleApp"
|
||||
let availability: PlatformAvailability = .all
|
||||
let syncPolicy: SyncPolicy = .never
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Keychain Keys
|
||||
|
||||
extension StorageKeys {
|
||||
|
||||
/// Stores user credentials securely in keychain.
|
||||
/// Configurable accessibility and access control.
|
||||
struct CredentialsKey: StorageKey {
|
||||
typealias Value = Credential
|
||||
|
||||
let name = "user_credentials"
|
||||
let domain: StorageDomain = .keychain(service: "com.example.securestorage")
|
||||
let security: SecurityPolicy
|
||||
let serializer: Serializer<Credential> = .json
|
||||
let owner = "SampleApp"
|
||||
let availability: PlatformAvailability = .phoneOnly
|
||||
let syncPolicy: SyncPolicy = .never
|
||||
|
||||
init(accessibility: KeychainAccessibility = .afterFirstUnlock, accessControl: KeychainAccessControl? = nil) {
|
||||
self.security = .keychain(accessibility: accessibility, accessControl: accessControl)
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores sensitive location data in keychain with biometric protection.
|
||||
struct LastLocationKey: StorageKey {
|
||||
typealias Value = SampleLocationData
|
||||
|
||||
let name = "last_known_location"
|
||||
let domain: StorageDomain = .keychain(service: "com.example.app.security")
|
||||
let security: SecurityPolicy = .keychain(
|
||||
accessibility: .afterFirstUnlock,
|
||||
accessControl: .userPresence
|
||||
)
|
||||
let serializer: Serializer<SampleLocationData> = .json
|
||||
let owner = "SampleApp"
|
||||
let availability: PlatformAvailability = .phoneOnly
|
||||
let syncPolicy: SyncPolicy = .never
|
||||
}
|
||||
|
||||
/// Stores API token in keychain.
|
||||
struct APITokenKey: StorageKey {
|
||||
typealias Value = String
|
||||
|
||||
let name = "api_token"
|
||||
let domain: StorageDomain = .keychain(service: "com.example.securestorage.api")
|
||||
let security: SecurityPolicy = .keychain(
|
||||
accessibility: .whenUnlockedThisDeviceOnly,
|
||||
accessControl: nil
|
||||
)
|
||||
let serializer: Serializer<String> = .json
|
||||
let owner = "SampleApp"
|
||||
let availability: PlatformAvailability = .phoneOnly
|
||||
let syncPolicy: SyncPolicy = .never
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - File System Keys
|
||||
|
||||
extension StorageKeys {
|
||||
|
||||
/// Stores user profile as JSON file in documents.
|
||||
struct UserProfileFileKey: StorageKey {
|
||||
typealias Value = UserProfile
|
||||
|
||||
let name = UserProfile.storageKeyName
|
||||
let domain: StorageDomain
|
||||
let security: SecurityPolicy = .none
|
||||
let serializer: Serializer<UserProfile> = .json
|
||||
let owner = "SampleApp"
|
||||
let availability: PlatformAvailability = .phoneWithWatchSync
|
||||
let syncPolicy: SyncPolicy = .automaticSmall
|
||||
|
||||
init(directory: FileDirectory = .documents) {
|
||||
self.domain = .fileSystem(directory: directory)
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores cached data files.
|
||||
struct CachedDataKey: StorageKey {
|
||||
typealias Value = Data
|
||||
|
||||
let name = "cached_data.bin"
|
||||
let domain: StorageDomain = .fileSystem(directory: .caches)
|
||||
let security: SecurityPolicy = .none
|
||||
let serializer: Serializer<Data> = .data
|
||||
let owner = "SampleApp"
|
||||
let availability: PlatformAvailability = .all
|
||||
let syncPolicy: SyncPolicy = .never
|
||||
}
|
||||
|
||||
/// Stores settings as property list.
|
||||
struct SettingsPlistKey: StorageKey {
|
||||
typealias Value = [String: AnyCodable]
|
||||
|
||||
let name = "settings.plist"
|
||||
let domain: StorageDomain = .fileSystem(directory: .documents)
|
||||
let security: SecurityPolicy = .none
|
||||
let serializer: Serializer<[String: AnyCodable]> = .plist
|
||||
let owner = "SampleApp"
|
||||
let availability: PlatformAvailability = .all
|
||||
let syncPolicy: SyncPolicy = .never
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Encrypted File System Keys
|
||||
|
||||
extension StorageKeys {
|
||||
|
||||
/// Stores session logs with full encryption.
|
||||
/// Configurable PBKDF2 iterations.
|
||||
struct SessionLogsKey: StorageKey {
|
||||
typealias Value = [String]
|
||||
|
||||
let name = "session_logs.json"
|
||||
let domain: StorageDomain = .encryptedFileSystem(directory: .caches)
|
||||
let security: SecurityPolicy
|
||||
let serializer: Serializer<[String]> = .json
|
||||
let owner = "SampleApp"
|
||||
let availability: PlatformAvailability = .phoneOnly
|
||||
let syncPolicy: SyncPolicy = .never
|
||||
|
||||
init(iterations: Int = 10_000) {
|
||||
self.security = .encrypted(.aes256(keyDerivation: .pbkdf2(iterations: iterations)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores private notes with encryption.
|
||||
struct PrivateNotesKey: StorageKey {
|
||||
typealias Value = String
|
||||
|
||||
let name = "private_notes.enc"
|
||||
let domain: StorageDomain = .encryptedFileSystem(directory: .documents)
|
||||
let security: SecurityPolicy = .encrypted(
|
||||
.aes256(keyDerivation: .pbkdf2(iterations: 50_000))
|
||||
)
|
||||
let serializer: Serializer<String> = .json
|
||||
let owner = "SampleApp"
|
||||
let availability: PlatformAvailability = .phoneOnly
|
||||
let syncPolicy: SyncPolicy = .never
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Platform-Specific Keys
|
||||
|
||||
extension StorageKeys {
|
||||
|
||||
/// Watch-only setting for vibration.
|
||||
struct WatchVibrationKey: StorageKey {
|
||||
typealias Value = Bool
|
||||
|
||||
let name = "watch_vibration_enabled"
|
||||
let domain: StorageDomain = .userDefaults(suite: nil)
|
||||
let security: SecurityPolicy = .none
|
||||
let serializer: Serializer<Bool> = .json
|
||||
let owner = "SampleApp"
|
||||
let availability: PlatformAvailability = .watchOnly
|
||||
let syncPolicy: SyncPolicy = .never
|
||||
}
|
||||
|
||||
/// Syncable setting with configurable platform and sync policy.
|
||||
struct SyncableSettingKey: StorageKey {
|
||||
typealias Value = String
|
||||
|
||||
let name = "syncable_setting"
|
||||
let domain: StorageDomain = .userDefaults(suite: nil)
|
||||
let security: SecurityPolicy = .none
|
||||
let serializer: Serializer<String> = .json
|
||||
let owner = "SampleApp"
|
||||
let availability: PlatformAvailability
|
||||
let syncPolicy: SyncPolicy
|
||||
|
||||
init(availability: PlatformAvailability = .all, syncPolicy: SyncPolicy = .never) {
|
||||
self.availability = availability
|
||||
self.syncPolicy = syncPolicy
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Custom Serializer Example
|
||||
|
||||
extension StorageKeys {
|
||||
|
||||
/// Example using custom serializer for specialized encoding.
|
||||
struct CustomEncodedKey: StorageKey {
|
||||
typealias Value = String
|
||||
|
||||
let name = "custom_encoded"
|
||||
let domain: StorageDomain = .fileSystem(directory: .documents)
|
||||
let security: SecurityPolicy = .none
|
||||
let serializer: Serializer<String> = .custom(
|
||||
encode: { value in
|
||||
// Example: Base64 encode
|
||||
Data(value.utf8).base64EncodedData()
|
||||
},
|
||||
decode: { data in
|
||||
guard let decoded = Data(base64Encoded: data),
|
||||
let string = String(data: decoded, encoding: .utf8) else {
|
||||
throw StorageError.deserializationFailed
|
||||
}
|
||||
return string
|
||||
}
|
||||
)
|
||||
let owner = "SampleApp"
|
||||
let availability: PlatformAvailability = .all
|
||||
let syncPolicy: SyncPolicy = .never
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
import Foundation
|
||||
import LocalData
|
||||
|
||||
extension StorageKeys {
|
||||
/// Stores private notes with encryption.
|
||||
struct PrivateNotesKey: StorageKey {
|
||||
typealias Value = String
|
||||
|
||||
let name = "private_notes.enc"
|
||||
let domain: StorageDomain = .encryptedFileSystem(directory: .documents)
|
||||
let security: SecurityPolicy = .encrypted(
|
||||
.aes256(keyDerivation: .pbkdf2(iterations: 50_000))
|
||||
)
|
||||
let serializer: Serializer<String> = .json
|
||||
let owner = "SampleApp"
|
||||
let availability: PlatformAvailability = .phoneOnly
|
||||
let syncPolicy: SyncPolicy = .never
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
import Foundation
|
||||
import LocalData
|
||||
|
||||
extension StorageKeys {
|
||||
/// Stores session logs with full encryption.
|
||||
/// Configurable PBKDF2 iterations.
|
||||
struct SessionLogsKey: StorageKey {
|
||||
typealias Value = [String]
|
||||
|
||||
let name = "session_logs.json"
|
||||
let domain: StorageDomain = .encryptedFileSystem(directory: .caches)
|
||||
let security: SecurityPolicy
|
||||
let serializer: Serializer<[String]> = .json
|
||||
let owner = "SampleApp"
|
||||
let availability: PlatformAvailability = .phoneOnly
|
||||
let syncPolicy: SyncPolicy = .never
|
||||
|
||||
init(iterations: Int = 10_000) {
|
||||
self.security = .encrypted(.aes256(keyDerivation: .pbkdf2(iterations: iterations)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
import Foundation
|
||||
import LocalData
|
||||
|
||||
extension StorageKeys {
|
||||
/// Stores cached data files.
|
||||
struct CachedDataKey: StorageKey {
|
||||
typealias Value = Data
|
||||
|
||||
let name = "cached_data.bin"
|
||||
let domain: StorageDomain = .fileSystem(directory: .caches)
|
||||
let security: SecurityPolicy = .none
|
||||
let serializer: Serializer<Data> = .data
|
||||
let owner = "SampleApp"
|
||||
let availability: PlatformAvailability = .all
|
||||
let syncPolicy: SyncPolicy = .never
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
import Foundation
|
||||
import LocalData
|
||||
|
||||
extension StorageKeys {
|
||||
/// Example using custom serializer for specialized encoding.
|
||||
struct CustomEncodedKey: StorageKey {
|
||||
typealias Value = String
|
||||
|
||||
let name = "custom_encoded"
|
||||
let domain: StorageDomain = .fileSystem(directory: .documents)
|
||||
let security: SecurityPolicy = .none
|
||||
let serializer: Serializer<String> = .custom(
|
||||
encode: { value in
|
||||
Data(value.utf8).base64EncodedData()
|
||||
},
|
||||
decode: { data in
|
||||
guard let decoded = Data(base64Encoded: data),
|
||||
let string = String(data: decoded, encoding: .utf8) else {
|
||||
throw StorageError.deserializationFailed
|
||||
}
|
||||
return string
|
||||
}
|
||||
)
|
||||
let owner = "SampleApp"
|
||||
let availability: PlatformAvailability = .all
|
||||
let syncPolicy: SyncPolicy = .never
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
import Foundation
|
||||
import LocalData
|
||||
|
||||
extension StorageKeys {
|
||||
/// Stores settings as property list.
|
||||
struct SettingsPlistKey: StorageKey {
|
||||
typealias Value = [String: AnyCodable]
|
||||
|
||||
let name = "settings.plist"
|
||||
let domain: StorageDomain = .fileSystem(directory: .documents)
|
||||
let security: SecurityPolicy = .none
|
||||
let serializer: Serializer<[String: AnyCodable]> = .plist
|
||||
let owner = "SampleApp"
|
||||
let availability: PlatformAvailability = .all
|
||||
let syncPolicy: SyncPolicy = .never
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
import Foundation
|
||||
import LocalData
|
||||
import SharedKit
|
||||
|
||||
extension StorageKeys {
|
||||
/// Stores user profile as JSON file in documents.
|
||||
struct UserProfileFileKey: StorageKey {
|
||||
typealias Value = UserProfile
|
||||
|
||||
let name = UserProfile.storageKeyName
|
||||
let domain: StorageDomain
|
||||
let security: SecurityPolicy = .none
|
||||
let serializer: Serializer<UserProfile> = .json
|
||||
let owner = "SampleApp"
|
||||
let availability: PlatformAvailability = .phoneWithWatchSync
|
||||
let syncPolicy: SyncPolicy = .automaticSmall
|
||||
|
||||
init(directory: FileDirectory = .documents) {
|
||||
self.domain = .fileSystem(directory: directory)
|
||||
}
|
||||
}
|
||||
}
|
||||
20
SecureStorgageSample/StorageKeys/Keychain/APITokenKey.swift
Normal file
20
SecureStorgageSample/StorageKeys/Keychain/APITokenKey.swift
Normal file
@ -0,0 +1,20 @@
|
||||
import Foundation
|
||||
import LocalData
|
||||
|
||||
extension StorageKeys {
|
||||
/// Stores API token in keychain.
|
||||
struct APITokenKey: StorageKey {
|
||||
typealias Value = String
|
||||
|
||||
let name = "api_token"
|
||||
let domain: StorageDomain = .keychain(service: "com.example.securestorage.api")
|
||||
let security: SecurityPolicy = .keychain(
|
||||
accessibility: .whenUnlockedThisDeviceOnly,
|
||||
accessControl: nil
|
||||
)
|
||||
let serializer: Serializer<String> = .json
|
||||
let owner = "SampleApp"
|
||||
let availability: PlatformAvailability = .phoneOnly
|
||||
let syncPolicy: SyncPolicy = .never
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,22 @@
|
||||
import Foundation
|
||||
import LocalData
|
||||
|
||||
extension StorageKeys {
|
||||
/// Stores user credentials securely in keychain.
|
||||
/// Configurable accessibility and access control.
|
||||
struct CredentialsKey: StorageKey {
|
||||
typealias Value = Credential
|
||||
|
||||
let name = "user_credentials"
|
||||
let domain: StorageDomain = .keychain(service: "com.example.securestorage")
|
||||
let security: SecurityPolicy
|
||||
let serializer: Serializer<Credential> = .json
|
||||
let owner = "SampleApp"
|
||||
let availability: PlatformAvailability = .phoneOnly
|
||||
let syncPolicy: SyncPolicy = .never
|
||||
|
||||
init(accessibility: KeychainAccessibility = .afterFirstUnlock, accessControl: KeychainAccessControl? = nil) {
|
||||
self.security = .keychain(accessibility: accessibility, accessControl: accessControl)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
import Foundation
|
||||
import LocalData
|
||||
|
||||
extension StorageKeys {
|
||||
/// Stores sensitive location data in keychain with biometric protection.
|
||||
struct LastLocationKey: StorageKey {
|
||||
typealias Value = SampleLocationData
|
||||
|
||||
let name = "last_known_location"
|
||||
let domain: StorageDomain = .keychain(service: "com.example.app.security")
|
||||
let security: SecurityPolicy = .keychain(
|
||||
accessibility: .afterFirstUnlock,
|
||||
accessControl: .userPresence
|
||||
)
|
||||
let serializer: Serializer<SampleLocationData> = .json
|
||||
let owner = "SampleApp"
|
||||
let availability: PlatformAvailability = .phoneOnly
|
||||
let syncPolicy: SyncPolicy = .never
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
import Foundation
|
||||
import LocalData
|
||||
|
||||
extension StorageKeys {
|
||||
/// Syncable setting with configurable platform and sync policy.
|
||||
/// Grouped under Platform to highlight availability/sync behavior.
|
||||
struct SyncableSettingKey: StorageKey {
|
||||
typealias Value = String
|
||||
|
||||
let name = "syncable_setting"
|
||||
let domain: StorageDomain = .userDefaults(suite: nil)
|
||||
let security: SecurityPolicy = .none
|
||||
let serializer: Serializer<String> = .json
|
||||
let owner = "SampleApp"
|
||||
let availability: PlatformAvailability
|
||||
let syncPolicy: SyncPolicy
|
||||
|
||||
init(availability: PlatformAvailability = .all, syncPolicy: SyncPolicy = .never) {
|
||||
self.availability = availability
|
||||
self.syncPolicy = syncPolicy
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
import Foundation
|
||||
import LocalData
|
||||
|
||||
extension StorageKeys {
|
||||
/// Watch-only setting for vibration.
|
||||
/// Grouped under Platform to highlight watch-only availability.
|
||||
struct WatchVibrationKey: StorageKey {
|
||||
typealias Value = Bool
|
||||
|
||||
let name = "watch_vibration_enabled"
|
||||
let domain: StorageDomain = .userDefaults(suite: nil)
|
||||
let security: SecurityPolicy = .none
|
||||
let serializer: Serializer<Bool> = .json
|
||||
let owner = "SampleApp"
|
||||
let availability: PlatformAvailability = .watchOnly
|
||||
let syncPolicy: SyncPolicy = .never
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
import Foundation
|
||||
import LocalData
|
||||
|
||||
extension StorageKeys {
|
||||
/// Stores the app version in standard UserDefaults.
|
||||
/// - Domain: UserDefaults (standard)
|
||||
/// - Security: None
|
||||
/// - Sync: Automatic for small data
|
||||
struct AppVersionKey: StorageKey {
|
||||
typealias Value = String
|
||||
|
||||
let name = "last_app_version"
|
||||
let domain: StorageDomain = .userDefaults(suite: nil)
|
||||
let security: SecurityPolicy = .none
|
||||
let serializer: Serializer<String> = .json
|
||||
let owner = "SampleApp"
|
||||
let availability: PlatformAvailability = .all
|
||||
let syncPolicy: SyncPolicy = .automaticSmall
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
import Foundation
|
||||
import LocalData
|
||||
|
||||
extension StorageKeys {
|
||||
/// Stores user preferences in a custom suite.
|
||||
/// - Domain: UserDefaults (custom suite)
|
||||
/// - Security: None
|
||||
/// - Sync: Never
|
||||
struct UserPreferencesKey: StorageKey {
|
||||
typealias Value = [String: AnyCodable]
|
||||
|
||||
let name = "user_preferences"
|
||||
let domain: StorageDomain = .userDefaults(suite: "group.com.example.securestorage")
|
||||
let security: SecurityPolicy = .none
|
||||
let serializer: Serializer<[String: AnyCodable]> = .json
|
||||
let owner = "SampleApp"
|
||||
let availability: PlatformAvailability = .all
|
||||
let syncPolicy: SyncPolicy = .never
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user