Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

matolah/LeezyPersistence

Repository files navigation

LeezyPersistence

Type-safe data persistence in Swift made simple - with built-in support for UserDefaults, Keychain, in-memory, and file-based storage.
Report Bug Β· Request Feature

What is LeezyPersistence?

LeezyPersistence is a lightweight, type-safe persistence library for Swift.
It provides intuitive property wrappers for saving and retrieving values from:

  • 🧠 In-memory storage - fast and temporary
  • πŸ—‚οΈ UserDefaults - for lightweight app preferences
  • πŸ” Keychain - for securely storing sensitive values like tokens
  • πŸ“ File-based storage - for custom JSON-encoded persistence

No need to manage different APIs - just use one unified, clean interface.

Inspired by this article on AppStorage.

Installation

Add LeezyPersistence to your project using Swift Package Manager:

dependencies: [
 .package(url: "https://github.com/matolah/LeezyPersistence.git", .upToNextMajor(from: "1.0.0"))
]

Usage

  1. Create your preferences class
final class SharedPreferences: BasePreferences, KeychainPreferences, UserDefaultPreferences {
 let keychainManager: KeychainManagerProtocol
 let userDefaults: UserDefaults
 @UserDefault<Bool, SharedPreferences>("hasSeenTutorial") var hasSeenTutorial: Bool?
 @Keychain<String, SharedPreferences>("accessToken") var accessToken: String?
 @InMemory<Int, SharedPreferences> var cachedPage: Int?
 @File<[String], SharedPreferences>("history.json") var savedHistory: [String]?
 init(keychainManager: KeychainManagerProtocol, userDefaults: UserDefaults) {
 self.keychainManager = keychainManager
 self.userDefaults = userDefaults
 super.init()
 }
 override func handle(error: Error) {
 print("Error: \(error)")
 }
}
  1. Register it
@main
struct MyApp: App {
 init() {
 sharedPreferences = SharedPreferences(
 keychainManager: MyKeychainManager(),
 userDefaults: .standard
 )
 }
 var body: some Scene {
 WindowGroup {
 ContentView()
 }
 }
}
  1. Access preferences with @Preference
final class ContentViewModel: ObservableObject {
 private let userID: String
 @Published var isInHomePage = false
 @Preference(\SharedPreferences.hasSeenTutorial) var hasSeenTutorial
 @Preference(\SharedPreferences.accessToken) var accessToken
 private var cancellables = Set<AnyCancellable>()
 init(userID: String) {
 self.userID = userID
 _accessToken.subscribe(storingTo: &cancellables) { [weak self] _ in
 guard let self, self.hasSeenTutorial == true else { 
 return 
 }
 self.isInHomePage = true
 }
 }
 func markTutorialAsSeen() {
 // Access user-specific `Preference` using a dynamic key
 hasSeenTutorial[userID] = true
 }
}
  1. Create as many Preferences classes with context-specific preferences as you'd like:
final class OnboardingPreferences: BasePreferences {
 let keychainManager: KeychainManagerProtocol
 @Keychain<String, OnboardingPreferences>("accessToken") var accessToken: String?
 init(keychainManager: KeychainManagerProtocol) {
 self.keychainManager = keychainManager
 super.init()
 }
 override func handle(error: Error) {
 print("Error: \(error)")
 }
}

License

Distributed under the MIT License. See LICENSE.txt for more information.

🀝 Contributing

Contributions are welcome! If you have suggestions for improvements, bug fixes, or new features, feel free to open an issue or submit a pull request.

πŸ’¬ Contact

Maintained by @_matolah

About

Type-safe data persistence in Swift made simple.

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

AltStyle γ«γ‚ˆγ£γ¦ε€‰ζ›γ•γ‚ŒγŸγƒšγƒΌγ‚Έ (->γ‚ͺγƒͺγ‚ΈγƒŠγƒ«) /