Why not use UserDefaults to store Codable objects 😉

Overview

Build Status Test Coverage Platforms Cocoapods Carthage compatible Swift Package Manager compatible Swift Xcode MIT

tl;dr

You love Swift's Codable protocol and use it everywhere, who doesn't! Here is an easy and very light way to store and retrieve -reasonable amount 😅 - of Codable objects, in a couple lines of code!


Introducing v2.0

  • Removed the Identifiable protocol in favor of Swift's Identifiable.
  • Increased deployment targets to iOS 13.0, tvOS 13.0, macOS 10.15, and watchOS 6.0.
  • Objects defined as non-final classes can now be used as well.
  • Added new generateSnapshot() and restoreSnapshot(_:) methods to generate and restore a Snapshot object that can be saved (e.g. to iCloud) and restored later.
  • Fixed a bug where objectsCount might run out of sync with the actual count of objects in store.

Installation

Swift Package Manager (Recommended)

You can use The Swift Package Manager to install UserDefaultsStore by adding the proper description to your Package.swift file:

import PackageDescription

let package = Package(
    name: "YOUR_PROJECT_NAME",
    targets: [],
    dependencies: [
        .package(url: "https://github.com/omaralbeik/UserDefaultsStore.git", from: "2.0.0")
    ]
)

Next, add UserDefaultsStore to your targets dependencies like so:

.target(
    name: "YOUR_TARGET_NAME",
    dependencies: [
        "UserDefaultsStore",
    ]
),

Then run swift package update.

CocoaPods

To integrate UserDefaultsStore into your Xcode project using CocoaPods, specify it in your Podfile:

pod 'UserDefaultsStore'
Carthage

To integrate UserDefaultsStore into your Xcode project using Carthage, specify it in your Cartfile:

github "omaralbeik/UserDefaultsStore" ~> 2.0.0
Manually

Add the Sources folder to your Xcode project.

Usage

Let's say you have 2 structs; User and Laptop defined as bellow:

struct User: Codable {
    var id: Int
    var firstName: String
    var lastName: String
    var laptop: Laptop?
}
struct Laptop: Codable {
    var model: String
    var name: String
}

Here is how you store them in UserDefaultsStore:

1. Conform to the Identifiable protocol and set the id property

The Identifiable protocol lets UserDefaultsStore knows what is the unique id for each object.

struct User: Codable, Identifiable {
    ...
}
struct Laptop: Codable, Identifiable {
    var id: String { model }
    ...
}

2. Create UserDefaults Stores

let usersStore = UserDefaultsStore<User>(uniqueIdentifier: "users")
let laptopsStore = UserDefaultsStore<Laptop>(uniqueIdentifier: "laptops")

3. Voilà, you're all set!

let macbook = Laptop(model: "A1278", name: "MacBook Pro")
let john = User(id: 1, firstName: "John", lastName: "Appleseed", laptop: macbook)

// Save an object to a store
try! usersStore.save(john)

// Save an array of objects to a store
try! usersStore.save([jane, steve, jessica])

// Get an object from store
let user = store.object(withId: 1)
let laptop = store.object(withId: "A1278")

// Get all objects in a store
let laptops = laptopsStore.allObjects()

// Check if store has an object
print(usersStore.hasObject(withId: 10)) // false

// Iterate over all objects in a store
laptopsStore.forEach { laptop in
    print(laptop.name)
}

// Delete an object from a store
usersStore.delete(withId: 1)

// Delete all objects in a store
laptops.deleteAll()

// Know how many objects are stored in a store
let usersCount = usersStore.objectsCount

// Create a snapshot
let snapshot = usersStore.generateSnapshot()

// Restore a pre-generated snapshot
try? usersStore.restoreSnapshot(snapshot)

Looking to store a single item only?

Use SingleUserDefaultsStore, it enables storing and retrieving a single value of Int, Double, String, or any Codable type.

Requirements

  • iOS 13.0+ / macOS 10.15+ / tvOS 13.0+ / watchOS 6.0+
  • Swift 5.0+

Thanks

Special thanks to:

Credits

Icon made by freepik from flaticon.com.

License

UserDefaultsStore is released under the MIT license. See LICENSE for more information.

Comments
  • Use hash value as key?

    Use hash value as key?

    Looks dangerous.

    Hash values are not guaranteed to be equal across different executions of your program. Do not save hash values to use during a future execution.

    help wanted question 
    opened by ddddxxx 5
  • Fix Codecov

    Fix Codecov

    The project has a 100% test coverage, however codecov is not working for some reason 😭 If you have an idea how to fix it, your help is highly appriciated

    help wanted 
    opened by omaralbeik 4
  • Fixed Package.swift

    Fixed Package.swift

    SPM was complaining that the directory structure didn't match up, it's usually in the format Sources/{Name}/* instead of the current Sources/*. Updated the Package.swift file to target the right directory.

    opened by terwanerik 2
  • Doesn't work with classes

    Doesn't work with classes

    After porting a bunch of code to work with UserDefaultsStore, I discovered that the "objects" you store can only be structs. It would be good to document this.

    opened by mark-anders 2
  • Release 2.1.0

    Release 2.1.0

    • Fix a bug where updating item was increasing objects count in UserDefaultsStore
    • Deprecate snapshot and custom encoder/decoder
    • Remove Jazzy in favor of DocC
    opened by omaralbeik 1
  • Not working with duplicate object

    Not working with duplicate object

    let cartSaved = UserDefaultsStore<CartItem>(uniqueIdentifier: "cart")

    When I add the same product with the same id I want to increment the quantity but the saved cart (::allObject) method return me the same product

      var cart  = getCart()
            
            var targetItem  = cart.first(where: {$0.product.id == cartItem.product.id}) // <-- targetItem is a product in the [CartItem]
                    
            
            if targetItem == nil  {
            
                print("prodct added ! ")
                cartItem.quantity += 1
                cart.append(cartItem)
                
                
            }else{
                
                targetItem?.quantity += 1
                
            }
            
            saveCart(cart: cart) // <--- here save again it should save the targetItem += 1
    
    opened by Dave181295 1
  • Bump redcarpet from 3.5.0 to 3.5.1

    Bump redcarpet from 3.5.0 to 3.5.1

    Bumps redcarpet from 3.5.0 to 3.5.1.

    Release notes

    Sourced from redcarpet's releases.

    Redcarpet v3.5.1

    Fix a security vulnerability using :quote in combination with the :escape_html option.

    Reported by Johan Smits.

    Changelog

    Sourced from redcarpet's changelog.

    Version 3.5.1 (Security)

    • Fix a security vulnerability using :quote in combination with the :escape_html option.

      Reported by Johan Smits.

    Commits

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    dependencies 
    opened by dependabot[bot] 1
  • Bump json from 2.2.0 to 2.3.1

    Bump json from 2.2.0 to 2.3.1

    Bumps json from 2.2.0 to 2.3.1.

    Changelog

    Sourced from json's changelog.

    2020-06-30 (2.3.1)

    • Spelling and grammar fixes for comments. Pull request #191 by Josh Kline.
    • Enhance generic JSON and #generate docs. Pull request #347 by Victor Shepelev.
    • Add :nodoc: for GeneratorMethods. Pull request #349 by Victor Shepelev.
    • Baseline changes to help (JRuby) development. Pull request #371 by Karol Bucek.
    • Add metadata for rubygems.org. Pull request #379 by Alexandre ZANNI.
    • Remove invalid JSON.generate description from JSON module rdoc. Pull request #384 by Jeremy Evans.
    • Test with TruffleRuby in CI. Pull request #402 by Benoit Daloze.
    • Rdoc enhancements. Pull request #413 by Burdette Lamar.
    • Fixtures/ are not being tested... Pull request #416 by Marc-André Lafortune.
    • Use frozen string for hash key. Pull request #420 by Marc-André Lafortune.
    • Added :call-seq: to RDoc for some methods. Pull request #422 by Burdette Lamar.
    • Small typo fix. Pull request #423 by Marc-André Lafortune.

    2019-12-11 (2.3.0)

    • Fix default of create_additions to always be false for JSON(user_input) and JSON.parse(user_input, nil). Note that JSON.load remains with default true and is meant for internal serialization of trusted data. [CVE-2020-10663]
    • Fix passing args all #to_json in json/add/*.
    • Fix encoding issues
    • Fix issues of keyword vs positional parameter
    • Fix JSON::Parser against bigdecimal updates
    • Bug fixes to JRuby port
    Commits
    • 0951d77 Bump version to 2.3.1
    • ddc29e2 Merge pull request #429 from flori/remove-generate-task-for-gemspec
    • cee8020 Removed gemspec task from default task on Rakefile
    • 9fd6371 Use VERSION file instead of hard-coded value
    • dc90bcf Removed explicitly date field in gemspec, it will assign by rubygems.org
    • 4c11a40 Removed task for json_pure.gemspec
    • e794ec9 Merge pull request #426 from marcandre/indent
    • 7cc9301 Merge pull request #428 from marcandre/change_fix
    • 9e2a1fb Make changes more precise #424
    • f8fa987 Merge pull request #424 from marcandre/update_changes
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    dependencies 
    opened by dependabot[bot] 1
  • Swift PM: cannot load package

    Swift PM: cannot load package

    Cannot load package by SPM: I have used:

    ".package(url: "https://github.com/omaralbeik/UserDefaultsStore.git", from: "1.4.0")

    I have gotten: `error: dependency graph is unresolvable; found these conflicting requirements:

    Dependencies: https://github.com/omaralbeik/UserDefaultsStore.git @ 1.4.0..<2.0.0`

    opened by kalavski 1
  • Misleading docs

    Misleading docs

    Based on the documentation in the readme, I got the impression that a laptop owned by a user came from the laptop store. Meaning that what was serialized to the store was the key to the laptop and that when I retrieved a user, it would also retrieve the laptop from the store.

    What's actually happening is that the laptop is serialized into the data for the user. So if I were to say that John owned a MacBook and then changed the description of the MacBook in the laptop store, when I retrieved john's record, I would get the updated Macbook data. Instead, I get whatever was serialized when I saved john to the user store.

    Does this make sense? I think it would be good to be explicit about what's happening, because I was getting inconsistency errors based on what I thought the docs were implying.

    opened by mark-anders 1
  • JSON Encoding doesn't store JSON

    JSON Encoding doesn't store JSON

    In inspecting the plist files that are created, I was surprised to see that they're not human readable, even though a JSONEncoder is being used. The issue is that you're taking the encoding and storing that directly, rather than converting it to a string.

    I think it would be nice to have that at least as an option, to aid in debugging. To do so, you'd change save to look more like:

    	public func save(_ object: T) throws {
    		let data = try encoder.encode(object)
                    if let str = String(data: json, encoding: .utf8) {
    		     store.set(str, forKey: key(for: object))
    		     increaseCounter()
                    } else {
                         // throw appropriate error.
                    }
    	}
    
    opened by mark-anders 1
Releases(3.0.0)
Owner
Omar Albeik
It’s not a bug – it’s an undocumented feature!
Omar Albeik
Modern interface to UserDefaults + Codable support

Default Modern interface to UserDefaults + Codable support What is Default? Default is a library that extends what UserDefaults can do by providing ex

Nicholas Maccharoli 475 Dec 20, 2022
CodableFiles - Save and load Codable objects from DocumentDirectory on iOS Devices.

Welcome to CodableFiles, a simple library that provides an easier way to save, load or delete Codable objects in Documents directory. It’s primarily a

Egzon Pllana 36 Dec 20, 2022
CodableCloudKit allows you to easily save and retrieve Codable objects to iCloud Database (CloudKit)

CodableCloudKit CodableCloudKit allows you to easily save and retrieve Codable objects to iCloud Database (CloudKit) Features ℹ️ Add CodableCloudKit f

Laurent Grondin 65 Oct 23, 2022
Typed key-value storage solution to store Codable types in various persistence layers with few lines of code!

?? Stores A typed key-value storage solution to store Codable types in various persistence layers like User Defaults, File System, Core Data, Keychain

Omar Albeik 94 Dec 31, 2022
Effortlessly synchronize UserDefaults over iCloud.

Zephyr ??️ Effortlessly sync UserDefaults over iCloud About Zephyr synchronizes specific keys and/or all of your UserDefaults over iCloud using NSUbiq

Arthur Ariel Sabintsev 841 Dec 23, 2022
Prephirences is a Swift library that provides useful protocols and convenience methods to manage application preferences, configurations and app-state. UserDefaults

Prephirences - Preϕrences Prephirences is a Swift library that provides useful protocols and convenience methods to manage application preferences, co

Eric Marchand 557 Nov 22, 2022
Simple, Strongly Typed UserDefaults for iOS, macOS and tvOS

简体中文 DefaultsKit leverages Swift 4's powerful Codable capabilities to provide a Simple and Strongly Typed wrapper on top of UserDefaults. It uses less

Nuno Dias 1.4k Dec 26, 2022
Swifty and modern UserDefaults

Defaults Swifty and modern UserDefaults Store key-value pairs persistently across launches of your app. It uses NSUserDefaults underneath but exposes

Sindre Sorhus 1.3k Dec 31, 2022
A lightweight wrapper over UserDefaults/NSUserDefaults with an additional layer of AES-256 encryption

SecureDefaults for iOS, macOS Requirements • Usage • Installation • Contributing • Acknowledgments • Contributing • Author • License SecureDefaults is

Victor Peschenkov 216 Dec 22, 2022
🔍 Browse and edit UserDefaults on your app

UserDefaults-Browser Browse and edit UserDefaults on your app. (SwiftUI or UIKit) Browse Edit (as JSON) Edit (Date) Export Note: We recommend to use S

Yusuke Hosonuma 25 Nov 3, 2022
⚙️ A tiny property wrapper for UserDefaults. Only 60 lines of code.

⚙️ A tiny property wrapper for UserDefaults. Only 60 lines of code. import Persistent extension UserDefaults { // Optional property @Per

Mezhevikin Alexey 6 Sep 28, 2022
AppCodableStorage - Extends `@AppStorage` in SwiftUI to support any Codable object

AppCodableStorage Extends @AppStorage in SwiftUI to support any Codable object.

Andrew Pouliot 19 Nov 25, 2022
MoreCodable expands the possibilities of `Codable`.

MoreCodable MoreCodable expands the possibilities of "Codable". Installation Carthage github "tattn/MoreCodable" CocoaPods pod 'MoreCodable' Feature D

Tatsuya Tanaka 372 Dec 7, 2022
A MongoDB interface for Swift [Not under active development]

MongoDB #This library is no longer under active development. I highly recommend using the robust, pure-swift alternative MongoKitten. A Swift MongoDB

Dan Appel 266 Jan 29, 2022
A PostgreSQL client library for Swift. Does not require libpq.

PostgresClientKit PostgresClientKit provides a friendly Swift API for operating against a PostgreSQL database. Features Doesn't require libpq. Postgre

codewins.com 107 Dec 1, 2022
A Generic CoreData Manager to accept any type of objects. Fastest way for adding a Database to your project.

QuickDB FileManager + CoreData ❗️ Save and Retrieve any thing in JUST ONE line of code ❗️ Fast usage dataBase to avoid struggling with dataBase comple

Behrad Kazemi 17 Sep 24, 2022
ObjectBox Swift - persisting your Swift objects superfast and simple

ObjectBox Swift ObjectBox is a superfast, light-weight object persistence framework. This Swift API seamlessly persists objects on-device for iOS and

ObjectBox 380 Dec 19, 2022
StorageManager - FileManager framework that handels Store, fetch, delete and update files in local storage

StorageManager - FileManager framework that handels Store, fetch, delete and update files in local storage. Requirements iOS 8.0+ / macOS 10.10+ / tvOS

Amr Salman 47 Nov 3, 2022
YapDB is a collection/key/value store with a plugin architecture. It's built atop sqlite, for Swift & objective-c developers.

YapDatabase is a collection/key/value store and so much more. It's built atop sqlite, for Swift & Objective-C developers, targeting macOS, iOS, tvOS &

Yap Studios 3.3k Dec 29, 2022