# LocalData LocalData provides a typed, discoverable namespace for persisted app data across UserDefaults, Keychain, and file storage with optional encryption. ## Architecture The package uses a clean, modular architecture with isolated actors for thread safety: ``` StorageRouter (main entry point) ├── UserDefaultsHelper ├── KeychainHelper ├── FileStorageHelper ├── EncryptionHelper └── SyncHelper ``` ## What Ships in the Package ### Protocols - **StorageKey** - Define storage configuration for each data type - **StorageProviding** - Abstraction for storage operations ### Services (Actors) - **StorageRouter** - Main entry point for all storage operations - **KeychainHelper** - Secure keychain storage - **EncryptionHelper** - AES-256-GCM encryption with PBKDF2 - **FileStorageHelper** - File system operations - **UserDefaultsHelper** - UserDefaults with suite support - **SyncHelper** - WatchConnectivity sync ### Models - **StorageDomain** - userDefaults, keychain, fileSystem, encryptedFileSystem - **SecurityPolicy** - none, keychain, encrypted (AES-256) - **Serializer** - JSON, plist, Data, or custom - **PlatformAvailability** - all, phoneOnly, watchOnly, phoneWithWatchSync - **SyncPolicy** - never, manual, automaticSmall - **KeychainAccessibility** - All 7 iOS accessibility options - **KeychainAccessControl** - All 6 access control options (biometry, passcode, etc.) - **FileDirectory** - documents, caches, custom URL - **StorageError** - Comprehensive error types - **AnyCodable** - Type-erased Codable for mixed-type payloads ## Usage ### 1. Define Keys Extend `StorageKeys` with your own key types: ```swift import LocalData extension StorageKeys { struct UserTokenKey: StorageKey { typealias Value = String let name = "user_token" let domain: StorageDomain = .keychain(service: "com.myapp") let security: SecurityPolicy = .keychain( accessibility: .afterFirstUnlock, accessControl: .biometryAny ) let serializer: Serializer = .json let owner = "AuthService" let availability: PlatformAvailability = .phoneOnly let syncPolicy: SyncPolicy = .never } } ``` ### 2. Use StorageRouter ```swift // Save let key = StorageKeys.UserTokenKey() try await StorageRouter.shared.set("token123", for: key) // Retrieve let token = try await StorageRouter.shared.get(key) // Remove try await StorageRouter.shared.remove(key) ``` ## Storage Domains | Domain | Use Case | |--------|----------| | `userDefaults` | Preferences, small settings | | `keychain` | Credentials, tokens, sensitive data | | `fileSystem` | Documents, cached data, large files | | `encryptedFileSystem` | Sensitive files with AES-256 encryption | ## Security Options ### Keychain Accessibility - `whenUnlocked` - Only when device unlocked - `afterFirstUnlock` - After first unlock until restart - `whenUnlockedThisDeviceOnly` - No migration to new device - `afterFirstUnlockThisDeviceOnly` - No migration - `always` - Always accessible (least secure) - `alwaysThisDeviceOnly` - Always, no migration - `whenPasscodeSetThisDeviceOnly` - Requires passcode ### Access Control - `userPresence` - Any authentication - `biometryAny` - Face ID or Touch ID - `biometryCurrentSet` - Current enrolled biometric only - `devicePasscode` - Passcode only - `biometryAnyOrDevicePasscode` - Biometric preferred, passcode fallback - `biometryCurrentSetOrDevicePasscode` - Current biometric or passcode ### Encryption - AES-256-GCM with PBKDF2-SHA256 key derivation - Configurable iteration count - Master key stored securely in keychain ## Sync Behavior StorageRouter can sync data to Apple Watch via WCSession when: - `availability` is `.all` or `.phoneWithWatchSync` - `syncPolicy` is `.manual` or `.automaticSmall` (≤100KB) - WCSession is activated and watch is paired The app owns WCSession activation and handling incoming updates. ## Platforms - iOS 17+ - watchOS 10+ ## Sample App See `SecureStorgageSample` for working examples of all storage domains and security options.