LocalData/README.md
Matt Bruce 312b2592dc Update Models, Protocols, Services + tests + docs + config
Summary:
- Sources: Models, Protocols, Services
- Tests: EncryptionHelperTests.swift, LocalDataTests.swift
- Docs: README
- Config: Package
- Added symbols: extension StorageKey, func deriveKeyMaterial, func encryptWithKey, func encryptWithAESGCM, func decryptWithKey, func decryptWithAESGCM (+6 more)
- Removed symbols: func extractDerivationParams, func encryptWithKey, func decryptWithKey, class LocalDataTests, func testUserDefaultsRoundTrip, func testFileSystemRoundTrip

Stats:
- 8 files changed, 210 insertions(+), 64 deletions(-)
2026-01-18 14:53:25 -06:00

4.3 KiB

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 or ChaCha20-Poly1305 with PBKDF2/HKDF
  • FileStorageHelper - File system operations
  • UserDefaultsHelper - UserDefaults with suite support
  • SyncHelper - WatchConnectivity sync

Models

  • StorageDomain - userDefaults, keychain, fileSystem, encryptedFileSystem
  • SecurityPolicy - none, keychain, encrypted (AES-256 or ChaCha20-Poly1305)
  • 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:

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<String> = .json
        let owner = "AuthService"
        let availability: PlatformAvailability = .phoneOnly
        let syncPolicy: SyncPolicy = .never
    }
}

If you omit security, it defaults to SecurityPolicy.recommended.

2. Use StorageRouter

// 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 encryption policies

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 or ChaCha20-Poly1305
  • PBKDF2-SHA256 or HKDF-SHA256 key derivation
  • Configurable PBKDF2 iteration count
  • Master key stored securely in keychain
  • Default security policy: SecurityPolicy.recommended (ChaCha20-Poly1305 + HKDF)

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.