DEV Community

Maksim Ponomarev
Maksim Ponomarev

Posted on

Modern Swift Storage

ModernSwiftStorage

Image description

ModernSwiftStorage is a comprehensive, type-safe storage solution for iOS, macOS, watchOS, and tvOS applications. It provides seamless integration between UserDefaults and Keychain with automatic security routing, SwiftUI property wrappers, and built-in analytics.

Why ModernSwiftStorage?

Traditional iOS storage solutions often require:

  • Manual decision-making between UserDefaults and Keychain
  • Boilerplate code for encoding/decoding
  • Separate implementations for different data types
  • Manual security considerations

ModernSwiftStorage solves these problems by providing:

Automatic Security Routing - Sensitive data automatically goes to Keychain

Type Safety - Generic methods with compile-time checking

SwiftUI Integration - Native property wrappers

Cross-Platform Support - Works on all Apple platforms

Built-in Analytics - Automatic usage statistics

Easy Testing - Protocol-based design with mock support

Installation

Add ModernSwiftStorage to your project using Swift Package Manager:

dependencies: [
    .package(url: "https://github.com/Maxnxi/ModernSwiftStorage.git", from: "1.0.0")
]
Enter fullscreen mode Exit fullscreen mode

Quick Start

1. Basic App Setup

import SwiftUI
import ModernSwiftStorage

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .withSimpleStorageService()
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Property Wrapper Usage

The most convenient way to use ModernSwiftStorage is through property wrappers:

struct SettingsView: View {
    @Storage("user.name") private var userName = "Guest"
    @Storage("notifications.enabled") private var notificationsEnabled = true
    @Storage("theme.isDark") private var isDarkTheme = false

    // Sensitive data automatically routed to Keychain
    @SecureStorage("auth.token") private var authToken = ""
    @SecureStorage("user.password") private var password = ""

    var body: some View {
        Form {
            TextField("Name", text: $userName)
            Toggle("Notifications", isOn: $notificationsEnabled)
            Toggle("Dark Theme", isOn: $isDarkTheme)
            SecureField("Auth Token", text: $authToken)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Storage Types and Automatic Security

Automatic Storage Selection (Recommended)

ModernSwiftStorage automatically determines the appropriate storage based on the key name:

@Storage("user.name") private var userName = "Guest"        // → UserDefaults
@Storage("auth.token") private var token = ""               // → Keychain (auto-detected)
@Storage("user.password") private var password = ""         // → Keychain (auto-detected)
@Storage("api.secret") private var apiSecret = ""           // → Keychain (auto-detected)
Enter fullscreen mode Exit fullscreen mode

Sensitive Keywords that trigger automatic Keychain storage:

  • password
  • token
  • secret
  • key
  • credential
  • auth

Explicit Storage Types

You can also explicitly specify the storage type:

@UserDefaultsStorage("app.version") private var version = "1.0"
@SecureStorage("biometric.data") private var biometricData = ""

// Manual specification
@Storage("data", storageType: .userDefaults) private var data = ""
@Storage("secret", storageType: .keychain()) private var secret = ""
Enter fullscreen mode Exit fullscreen mode

Keychain Accessibility Options

Control when Keychain data is accessible:

@Storage("token", storageType: .keychain(accessibility: .whenUnlocked)) 
private var token = ""

@Storage("biometric", storageType: .keychain(accessibility: .whenPasscodeSetThisDeviceOnly)) 
private var biometric = ""
Enter fullscreen mode Exit fullscreen mode

Available accessibility options:

  • .whenUnlocked - Default, accessible when device is unlocked
  • .afterFirstUnlock - Accessible after first unlock since boot
  • .whenPasscodeSetThisDeviceOnly - Requires passcode, device-specific
  • .whenUnlockedThisDeviceOnly - Device-specific, when unlocked

Type-Safe Storage Keys

For better organization and type safety, define custom storage keys:

extension ModernStorage.Keys {
    struct UserProfile: StorageKey {
        typealias Value = UserProfileModel
        let key = "user.profile.v2"
        let defaultValue = UserProfileModel()
        let storageType = StorageType.userDefaults
    }

    struct APICredentials: StorageKey {
        typealias Value = APICredentialsModel
        let key = "api.credentials"
        let defaultValue = APICredentialsModel()
        let isSensitive = true // Automatically uses Keychain
    }
}

// Usage
struct MyView: View {
    @Storage(ModernStorage.Keys.UserProfile()) private var profile
    @Storage(ModernStorage.Keys.APICredentials()) private var credentials

    var body: some View {
        VStack {
            TextField("Name", text: $profile.fullName)
            SecureField("API Key", text: $credentials.apiKey)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Direct Storage Service Access

Access the storage service directly for programmatic operations:

struct DataManager {
    @Environment(\.simpleStorageService) private var storage

    func saveUserData(_ data: UserData) {
        storage.set(data, forKey: "user.data")
    }

    func loadUserData() -> UserData {
        return storage.get(key: "user.data", defaultValue: UserData())
    }

    func clearSensitiveData() {
        storage.remove(key: "auth.token", storageType: .keychain())
    }

    func bulkUpdate() {
        storage.setBool(true, forKey: "feature.enabled")
        storage.setInt(42, forKey: "user.score")
        storage.setString("premium", forKey: "subscription.tier")
    }
}
Enter fullscreen mode Exit fullscreen mode

Working with Complex Data Types

Any Codable type is automatically supported:

struct UserProfile: Codable {
    var name: String
    var email: String
    var preferences: [String: String]
    var lastLogin: Date
    var settings: UserSettings
}

struct UserSettings: Codable {
    var theme: String
    var language: String
    var notifications: Bool
}

// Usage
@Storage("user.profile") private var profile = UserProfile(
    name: "",
    email: "",
    preferences: [:],
    lastLogin: Date(),
    settings: UserSettings(theme: "system", language: "en", notifications: true)
)
Enter fullscreen mode Exit fullscreen mode

Built-in Analytics and Statistics

ModernSwiftStorage automatically tracks app usage without additional setup:

struct StatisticsView: View {
    @Environment(\.simpleStorageService) private var storage

    var body: some View {
        VStack(alignment: .leading, spacing: 12) {
            Text("Days using app: \(storage.statistics.statistics.daysUsingApp)")
            Text("Total app opens: \(storage.statistics.statistics.timesUsingApp)")
            Text("Today's sessions: \(storage.statistics.statistics.timesDailyUsingApp)")
            Text("App installs: \(storage.statistics.statistics.timesAppInstalled)")

            Button("Update Statistics") {
                storage.statistics.updateStatistics()
            }

            Button("Reset Statistics") {
                storage.statistics.resetStatistics()
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Data Migration

Migrate data between storage types when needed:

let migrationManager = StorageMigrationManager()

// Migrate sensitive data from UserDefaults to Keychain
await migrationManager.migrateFromUserDefaultsToKeychain(
    key: "old.token",
    accessibility: .whenUnlocked
)

// Migrate data from Keychain to UserDefaults (if needed)
await migrationManager.migrateFromKeychainToUserDefaults(
    key: "non.sensitive.data"
)
Enter fullscreen mode Exit fullscreen mode

Testing and Mocking

ModernSwiftStorage is designed with testing in mind:

#if DEBUG
struct TestView: View {
    var body: some View {
        MyView()
            .withSimpleStorageService(
                userDefaultsManager: MockUserDefaultsManager(),
                keychainManager: MockKeychainManager()
            )
    }
}
#endif

// Unit Testing
class MyViewModelTests: XCTestCase {
    var storage: ModernStorage!

    override func setUp() {
        storage = ModernStorage(
            userDefaultsManager: MockUserDefaultsManager(),
            keychainManager: MockKeychainManager()
        )
    }

    func testDataPersistence() {
        storage.set("test", forKey: "key")
        XCTAssertEqual(storage.get(key: "key", defaultValue: ""), "test")
    }
}
Enter fullscreen mode Exit fullscreen mode

Advanced Configuration

Customize ModernSwiftStorage for specific needs:

let config = StorageConfiguration(
    serviceName: "com.myapp.storage",
    userDefaultsSuiteName: "group.myapp.shared",
    defaultKeychainAccessibility: .afterFirstUnlock,
    enableStatistics: true,
    enableMigration: true
)

let customStorage = SimpleStorageService(configuration: config)

// Use in your app
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .withSimpleStorageService(customStorage)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Error Handling and Validation

Validate storage operations and handle errors:

do {
    // Validate key before use
    guard StorageValidator.validateKey("my.key") else {
        throw StorageError.invalidKey("my.key")
    }

    // Validate storage type appropriateness
    guard StorageValidator.validateStorageType(SensitiveData.self, storageType: .userDefaults) else {
        throw StorageError.unsupportedType
    }

    storage.set(data, forKey: "my.key")
} catch {
    print("Storage error: \(error.localizedDescription)")
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

1. Use Automatic Storage Selection

Let ModernSwiftStorage decide the appropriate storage type:

// Good - automatic selection
@Storage("user.preferences") private var preferences = UserPreferences()
@Storage("auth.token") private var token = ""

// Only specify explicitly when needed
@Storage("temp.data", storageType: .userDefaults) private var tempData = ""
Enter fullscreen mode Exit fullscreen mode

2. Define Storage Keys for Type Safety

extension ModernStorage.Keys {
    struct AppSettings: StorageKey {
        typealias Value = AppSettingsModel
        let key = "app.settings.v2"
        let defaultValue = AppSettingsModel()
        let storageType = StorageType.userDefaults
    }
}
Enter fullscreen mode Exit fullscreen mode

3. Use Descriptive Key Names

// Good
@Storage("user.profile.display.name") private var displayName = ""
@Storage("auth.session.token") private var sessionToken = ""

// Avoid
@Storage("name") private var displayName = ""
@Storage("token") private var sessionToken = ""
Enter fullscreen mode Exit fullscreen mode

4. Group Related Settings

struct NotificationSettings: Codable {
    var pushEnabled: Bool = true
    var emailEnabled: Bool = false
    var frequency: String = "daily"
}

@Storage("notifications.settings") private var notificationSettings = NotificationSettings()
Enter fullscreen mode Exit fullscreen mode

Platform Support and Requirements

  • iOS 13.0+
  • macOS 10.15+
  • watchOS 6.0+
  • tvOS 13.0+

Thread Safety

All storage operations are marked with @MainActor for thread safety in SwiftUI contexts. For background operations:

Task { @MainActor in
    storage.set(data, forKey: "background.key")
}
Enter fullscreen mode Exit fullscreen mode

Performance Considerations

  • UserDefaults operations are synchronous and fast for small data
  • Keychain operations have slightly more overhead but remain performant
  • Complex objects are automatically JSON encoded/decoded
  • Statistics tracking has minimal performance impact
  • Bulk operations are optimized internally

Conclusion

ModernSwiftStorage
https://github.com/Maxnxi/ModernSwiftStorage.git
provides a powerful, type-safe, and secure storage solution that eliminates the complexity of managing UserDefaults and Keychain manually. With its automatic security routing, SwiftUI integration, and built-in analytics, it's the perfect storage solution for modern iOS applications.

Whether you're building a simple settings screen or a complex app with sensitive data, ModernSwiftStorage adapts to your needs while maintaining security best practices automatically.

Top comments (0)