Skip to content

Instantly share code, notes, and snippets.

@andyyhope
Last active November 15, 2020 10:27
Show Gist options
  • Save andyyhope/b2fbd9cbbc48a7d9d56ca3feb55bc1f5 to your computer and use it in GitHub Desktop.
Save andyyhope/b2fbd9cbbc48a7d9d56ca3feb55bc1f5 to your computer and use it in GitHub Desktop.
//:
//: UserDefaultable.swift
//:
//: Created by Andyy Hope on 18/08/2016.
//: Twitter: @andyyhope
//: Medium: Andyy Hope, https://medium.com/@AndyyHope
import Foundation
// MARK: - Key Namespaceable
protocol KeyNamespaceable { }
extension KeyNamespaceable {
private static func namespace(_ key: String) -> String {
return "\(Self.self).\(key)"
}
static func namespace<T: RawRepresentable>(_ key: T) -> String where T.RawValue == String {
return namespace(key.rawValue)
}
}
// MARK: - Bool Defaults
protocol BoolUserDefaultable : KeyNamespaceable {
associatedtype BoolDefaultKey : RawRepresentable
}
extension BoolUserDefaultable where BoolDefaultKey.RawValue == String {
// Set
static func set(_ bool: Bool, forKey key: BoolDefaultKey) {
let key = namespace(key)
UserDefaults.standard.set(bool, forKey: key)
}
// Get
static func bool(forKey key: BoolDefaultKey) -> Bool {
let key = namespace(key)
return UserDefaults.standard.bool(forKey: key)
}
}
// MARK: - Float Defaults
protocol FloatUserDefaultable : KeyNamespaceable {
associatedtype FloatDefaultKey : RawRepresentable
}
extension FloatUserDefaultable where FloatDefaultKey.RawValue == String {
// Set
static func set(_ float: Float, forKey key: FloatDefaultKey) {
let key = namespace(key)
UserDefaults.standard.set(float, forKey: key)
}
// Get
static func float(forKey key: FloatDefaultKey) -> Float {
let key = namespace(key)
return UserDefaults.standard.float(forKey: key)
}
}
// MARK: - Integer Defaults
protocol IntegerUserDefaultable : KeyNamespaceable {
associatedtype IntegerDefaultKey : RawRepresentable
}
extension IntegerUserDefaultable where IntegerDefaultKey.RawValue == String {
// Set
static func set(_ integer: Int, forKey key: IntegerDefaultKey) {
let key = namespace(key)
UserDefaults.standard.set(integer, forKey: key)
}
// Get
static func integer(forKey key: IntegerDefaultKey) -> Int {
let key = namespace(key)
return UserDefaults.standard.integer(forKey: key)
}
}
// MARK: - Object Defaults
protocol ObjectUserDefaultable : KeyNamespaceable {
associatedtype ObjectDefaultKey : RawRepresentable
}
extension ObjectUserDefaultable where ObjectDefaultKey.RawValue == String {
// Set
static func set(_ object: AnyObject, forKey key: ObjectDefaultKey) {
let key = namespace(key)
UserDefaults.standard.set(object, forKey: key)
}
// Get
static func object(forKey key: ObjectDefaultKey) -> Any? {
let key = namespace(key)
return UserDefaults.standard.object(forKey: key)
}
}
// MARK: - Double Defaults
protocol DoubleUserDefaultable : KeyNamespaceable {
associatedtype DoubleDefaultKey : RawRepresentable
}
extension DoubleUserDefaultable where DoubleDefaultKey.RawValue == String {
// Set
static func set(_ double: Double, forKey key: DoubleDefaultKey) {
let key = namespace(key)
UserDefaults.standard.set(double, forKey: key)
}
// Get
static func double(forKey key: DoubleDefaultKey) -> Double {
let key = namespace(key)
return UserDefaults.standard.double(forKey: key)
}
}
// MARK: - URL Defaults
protocol URLUserDefaultable : KeyNamespaceable {
associatedtype URLDefaultKey : RawRepresentable
}
extension URLUserDefaultable where URLDefaultKey.RawValue == String {
// Set
static func set(_ url: URL, forKey key: URLDefaultKey) {
let key = namespace(key)
UserDefaults.standard.set(url, forKey: key)
}
// Get
static func url(forKey key: URLDefaultKey) -> URL? {
let key = namespace(key)
return UserDefaults.standard.url(forKey: key)
}
}
// MARK: - Use
// Preparation
extension UserDefaults {
struct Account : BoolUserDefaultable {
private init() { }
enum BoolDefaultKey : String {
case isUserLoggedIn
}
}
}
// Set
UserDefaults.Account.set(true, forKey: .isUserLoggedIn)
// Get
let isUserLoggedIn = UserDefaults.Account.bool(forKey: .isUserLoggedIn)
@Vkt0r
Copy link

Vkt0r commented Nov 2, 2016

@ServusJon, you can do it easily the only thing you have to remember is that the enum name inside your struct need to be the same as you define for your associatedtype to conform the any of the protocols defined above, for example something like this:

extension UserDefaults {

    struct BankAccount : IntegerUserDefaultable {
    
       private init() { }
    
       enum IntegerDefaultKey : String {
           case moneyInTheAccount
       }
   }
}

And then you can call it like the following example:

// Set
UserDefaults.BankAccount.set(3, forKey: .moneyInTheAccount)

// Get
let moneyInTheAccount = UserDefaults.BankAccount.integer(forKey: .moneyInTheAccount) // 3

@CodeLinersTech
Copy link

can you help me out in setting string variable in the UserDefault

@alfian0
Copy link

alfian0 commented Aug 27, 2017

Hi, nice gist ...just wanna ask why you separate each type to different protocol ?? we can just put it together like this

                protocol KeyNamespaceable { }

                extension KeyNamespaceable {
                    private static func namespace(_ key: String) -> String {
                        return "\(Self.self).\(key)"
                    }
                    
                    static func namespace<T: RawRepresentable>(_ key: T) -> String where T.RawValue == String {
                        return namespace(key.rawValue)
                    }
                }

                protocol AccountDefaultable: KeyNamespaceable {
                    associatedtype AccountDefaultKey: RawRepresentable
                }

                extension AccountDefaultable where AccountDefaultKey.RawValue == String {
                    
                    static func set(_ string: String, forKey key: AccountDefaultKey) {
                        UserDefaults.standard.set(string, forKey: namespace(key))
                    }
                    
                    static func string(forKey key: AccountDefaultKey) -> String? {
                        return UserDefaults.standard.string(forKey: namespace(key))
                    }
                    
                    static func set(_ integer: Int, forKey key: AccountDefaultKey) {
                        UserDefaults.standard.set(integer, forKey: namespace(key))
                    }
                    
                    static func integer(forKey key: AccountDefaultKey) -> Int {
                        return UserDefaults.standard.integer(forKey: namespace(key))
                    }
                }

                extension UserDefaults {
                    
                    struct Account: AccountDefaultable {
                        private init() { }
                        
                        enum AccountDefaultKey: String {
                            case firstName
                            case lastName
                        }
                    }
                }

if we separate with type, we cant set user default data with different type in one struct that have the enum ?? like age where age is integer not same with firstName and lastName

              extension UserDefaults {
                    
                    struct Account: AccountDefaultable {
                        private init() { }
                        
                        enum AccountDefaultKey: String {
                            case firstName
                            case lastName
                            case age
                        }
                    }
                }

thanks for the explaination

@zboralski
Copy link

This is really cool!

@andyyhope how do you get the keypath to use in UserDefaults.standard.observe?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment