Summary: - Sources: update LocalData.swift - Docs: update docs for Proposal, README Stats: - 3 files changed, 61 insertions(+), 50 deletions(-)
4.6 KiB
LocalData Package Proposal
Goal
Create a single, typed, discoverable namespace for persisted app data with consistent security guarantees and clear ownership. This makes it obvious what is stored, where it is stored, how it is secured, how it is serialized, who owns it, and which platforms it belongs to or should sync to.
Package Placement
- localPackages/LocalData/
- Sources/LocalData/
- Tests/LocalDataTests/
Dependencies
- Foundation
- Security (Keychain)
- CryptoKit (encryption)
- WatchConnectivity (sync helpers)
Architecture
Core Components
- StorageKey (generic struct) - Defines storage configuration for each data type
- StorageKey.migration - Optional migration attached to a key
- StorageRouter actor - Main entry point coordinating all storage operations
- StorageProviding protocol - Abstraction for storage operations
- StorageKeyCatalog protocol - Catalog of keys for auditing/validation
- StorageMigration protocol - Protocol-based migration workflows attached to keys
Isolated Helper Classes (Actors)
Each helper is a dedicated actor providing thread-safe access to a specific storage domain:
- KeychainHelper - All keychain operations (set, get, delete, exists, deleteAll)
- EncryptionHelper - AES-256-GCM or ChaCha20-Poly1305 with PBKDF2/HKDF
- FileStorageHelper - File system operations (read, write, delete, list, size), including App Group containers
- UserDefaultsHelper - UserDefaults operations with suite and App Group support
- SyncHelper - WatchConnectivity sync operations
Models
- StorageDomain - userDefaults, appGroupUserDefaults, keychain, fileSystem, encryptedFileSystem, appGroupFileSystem
- SecurityPolicy - none, keychain (with accessibility/accessControl), encrypted (AES-256 or ChaCha20-Poly1305)
- Serializer - JSON, property list, raw Data, or custom encode/decode with named serializer metadata
- KeyMaterialSource - Identifier for external key material providers
- PlatformAvailability - all, phoneOnly, watchOnly, phoneWithWatchSync
- SyncPolicy - never, manual, automaticSmall
- KeychainAccessibility - All 7 iOS options (whenUnlocked, afterFirstUnlock, etc.)
- KeychainAccessControl - All 6 options (userPresence, biometryAny, devicePasscode, etc.)
- FileDirectory - documents, caches, custom URL
- StorageError - Comprehensive error types
- StorageKeyDescriptor - Audit snapshot of a key’s storage metadata
- AnyStorageKey - Type-erased storage key for catalogs
- AnyStorageMigration - Type-erased migration for protocol-based workflows
- AnyCodable - Type-erased Codable for mixed-type payloads
- MigrationContext - Context for conditional migrations (app version, device, system info)
- MigrationResult - Migration outcome with errors and metadata
- MigrationError - Error types for migration failures
- DeviceInfo / SystemInfo - Device and system metrics used by migrations
Usage Pattern
Apps extend StorageKey with typed static keys (e.g., extension StorageKey where Value == String) and use StorageRouter.shared. This follows the Notification.Name pattern for discoverable keys while preserving value-type inference.
Audit & Validation
Apps can register multiple StorageKeyCatalogs (e.g., one per module) to generate audit reports and enforce key registration. Registration is additive and validates:
- Duplicate key names (across all registered catalogs)
- Missing descriptions
- Unregistered keys at runtime (debug assertions)
The StorageRouter provides discovery APIs to retrieve all registered keys for global audit reporting.
Sync Behavior
StorageRouter can call WCSession.updateApplicationContext for manual or automaticSmall sync policies when availability allows it. Session activation and receiving data are owned by the app.
Bootstrapping and Watch Requests
LocalData exposes helper APIs to make watch sync reliable after relaunch or reconnection:
StorageRouter.syncRegisteredKeysIfNeeded()re-sends stored values for eligible keys.StorageRouter.syncSnapshot()returns a payload snapshot for replying to watch requests.
Apps should call syncRegisteredKeysIfNeeded() on launch and on WatchConnectivity reachability changes, and reply to watch-initiated requests with syncSnapshot() to avoid launch-order issues.
Platforms
- iOS 17+
- watchOS 10+
Future Ideas (Not Implemented)
- Key rotation strategies for encrypted data
- Watch-optimized data representations
- Persistent storage for sync policy overrides
Any future changes should keep LocalData documentation in sync with code changes.