NSUserDefaults with Wings!

Overview

Build Status CocoaPods Compatible Carthage Compatible Platform swiftyness @elmkretzer @madhavajay

#Palau: NSUserDefaults with Wings!


Features | Included Types | Installation | Validators and Defaults | Custom Types | DidSet Callback |

-------

Features

  • Easily store your Custom Types in NSUserDefaults
  • Most Standard Types Out-of-the-box
  • Per-property based Chainable Rules
  • Supports NSCoding and RawRepresentable
  • 300% Type Safe :P
  • 100% Unit Test Coverage
  • Swift 3 features coming!

Already Included Types

Swift

  • Bool
  • Int
  • UInt
  • Float
  • Double
  • String
  • Array
  • Dictionary

Foundation

  • NSNumber
  • NSString
  • NSArray
  • NSDictionary
  • NSDate
  • NSData
  • UIColor

Requirements

  • Swift 2.2
  • iOS 8.0+ / tvOS 9.0+ / watchOS 2.0+
  • Xcode 7.3+

Installation

Carthage

To integrate Palau into your project using Carthage, add to your Cartfile:

github "symentis/Palau" ~> 1.0

Run carthage update to build the framework and drag the built Palau.framework into your Xcode project. See more instructions on the Carthage page.

CocoaPods

To integrate Palau into your project using CocoaPods, add to your Podfile:

use_frameworks!

pod 'Palau', '~> 1.0'

Run pod install to install the framework into your Xcode workspace.

Usage

Import Palau

import Palau

Once you import the framework you can setup PalauDefaults like:

/// Your defaults are defined as extension of PalauDefaults
///
/// - The generic type of your PalauDefaultsEntry must conform 
///   to the protocol PalauDefaultable.
/// - We provide support for the most common types. 
/// - `value` is a helper function defined in PalauDefaults
/// - The String "backingName" is the key used in NSUserDefaults
/// - The empty `set` is used to please the compiler
extension PalauDefaults {
  /// a NSUserDefaults Entry of Type String with the key "backingName"
  public static var name: PalauDefaultsEntry<String> {
    get { return value("backingName") }
    set { }
  }
}

Set

Every value of a PalauDefaultsEntry will always be optional. If you want to set a value you call:

PalauDefaults.name.value = "I am a great String value!"

Get

Getting your value back is as easy as:

/// name is an Optional<String>
let name = PalauDefaults.name.value

Delete

You can delete a property by setting it to nil:

PalauDefaults.name.value = nil

Or

/// skip custom rules and delete
PalauDefaults.name.clear()

Custom Rules

Providing a Default Value

If you want to provide a default, when there is no value set, you can write a custom rule. This allows fine granular control on your values.

We include two rule types by default: whenNil and ensure

import Palau
extension PalauDefaults {
  public static var color: PalauDefaultsEntry<UIColor> {
    /// whenNil provides a value that will be returned
    /// when the related NSUserDefaults value is nil 
    /// (e.g. the 1st time, or after clear)
    get { return value("color").whenNil(use: UIColor.redColor())  }
    set { }
  }
}

/// is UIColor.redColor() 
let color: UIColor? = PalauDefaults.color.value

Providing a Validator

You can also build up arbitrary rules for your value like:

/// Custom Validator Closure
let lessThan10: Int? -> Bool = {
  return $0.map { $0 < 10 } ?? false
}

/// the PalauDefaultsEntry with the key "intValueMin10" has 2 rules
/// - 1. when the value is nil - we will get or set 10
/// - 2. when the value is less than 10 (see lessThan10 closure) - we will also get or set 10
/// - Add as many chainable rules as you like
public static var intValueMin10: PalauDefaultsEntry<Int> {
  get { return value("intValue")
    .whenNil(use: 10)
    .ensure(when: lessThan10, use: 10) }
  set { }
}

/// try setting the property to 8
PalauDefaults.intValueMin10.value = 8
/// property ensured to be >= 10
assert(PalauDefaults.intValueMin10.value == 10)
/// try setting the property to 11
PalauDefaults.intValueMin10.value = 11
/// property changed to 11
assert(PalauDefaults.intValueMin10.value == 11)

Custom Types

In Swift 2.2 Classes and Protocols can be used to constrain the ValueType. For example this is how Palau adds support for RawRepresentable via an Extension:

/// Extension for RawRepresentable types aka enums
extension PalauDefaultable where ValueType: RawRepresentable {

  public static func get(key: String, from defaults: NSUD) -> ValueType? {
    guard let val = defaults.objectForKey(key) as? ValueType.RawValue else { return nil }
    return ValueType(rawValue: val)
  }

  public static func set(value: ValueType?, forKey key: String, in defaults: NSUD) -> Void {
    guard let value = value?.rawValue as? AnyObject else { return defaults.removeObjectForKey(key) }
    defaults.setObject(value, forKey: key)
  }
}

Generally for Types which conform to NSCoding you can usually just provide an extension like so:

/// Make UIColor PalauDefaultable
extension UIColor: PalauDefaultable {
  public typealias ValueType = UIColor
}

Look Mum, even Structs!

For custom types you can provide an extension on your type for PalauDefaultable, to implement a get and a get function.

// example Struct called Structy for demonstrating we can save a Struct with Palau
public struct Structy {
  let tuple: (String, String)
}

// our Structy PalauDefaultable extension allowing the mapping between PalauDefaults and the Type
// here we just map the two values to two keys named "1" and "2"
extension Structy: PalauDefaultable {
  public static func get(key: String, from defaults: NSUD) -> Structy? {
    guard let d = defaults.objectForKey(key) as? [String: AnyObject] ,
      let t1 = d["1"] as? String,
      let t2 = d["2"] as? String else { return nil }
    return Structy(tuple: (t1, t2))
  }

  public static func set(value: Structy?, forKey key: String, in defaults: NSUD) -> Void {
    guard let value = value else { return defaults.setObject(nil, forKey: key) }
    defaults.setObject(["1": value.tuple.0, "2": value.tuple.1], forKey: key)
  }
}

// now create a property on PalauDefaults
extension PalauDefaults {
  public static var structWithTuple: PalauDefaultsEntry<Structy> {
    get { return value("structy") }
    set { }
  }
}

DidSet Callback

You can easily register a didSet callback, which gets fired when the value has changed.

extension PalauDefaults {
  public static var strings: PalauDefaultsEntry<[String]> {
    get { return value("strings").didSet({ print("changed to:", $0, "from:", $1) }) }
    set { }
  }
}

Limitations for Swift 2.2

We are waiting for more Swift 3 generics features like extensions on Generic types.... yay!

Then we get even more type saftey on arrays and dictionaries. Plus we might be able to make generic types conform to PalauDefaultable.

FAQ

What's the origin of the name Palau?

Palau is named after the Palau swiftlet, a species of swift, endemic to the island of Palau.

Btw - if you really don`t like the name, you can use a typealias

typealias Defaults = PalauDefaults
typealias Defaultable = PalauDefaultable
/// for Swift 3 even:
/// typealias DefaultsEntry<T> = PalauDefaultsEntry<T>

Credits

Palau is owned and maintained by Symentis GmbH.

Developed by: Elmar Kretzer & Madhava Jay

Follow for more Swift Goodness: Twitter Twitter

##Logo

Awesome Logo by: 4th motion

String Test Fixtures

Markus Kuhn

License

Palau is released under the Apache 2.0 license. See LICENSE for details.

Comments
  • Array of Struct object

    Array of Struct object

    Any idea how I can make PalauDefaultable for an array of the following struct?

    public struct Person { var name: String? var address: String? var state: String? var zipcode: String? }

    Thanks

    enhancement 
    opened by xie-samuel 7
  • Segmentation fault with PalauDefaultable

    Segmentation fault with PalauDefaultable

    Currently running into Segmentation fault: 11 on the swift-3 branch with Xcode 8 and Swift 3. Whenever I try to extend a class or struct with PalauDefaultable I get segmentation errors.

    Example:

    class SomeModel: PalauDefaultable {
        let aValue: String
        let bValue: String
    
        init(_ json: JSON) {
            aValue = json["a"].stringValue
            bValue = json["b"].stringValue
        }
    
        init(dictionary: [String: AnyObject]) {
            aValue = dictionary["a"] as? String ?? ""
            bValue = dictionary["b"] as? String ?? ""
        }
    
        func toDictionary() -> [String: AnyObject] {
            let dict = [
                aValue: aValue,
                bValue: bValue
            ]
    
            return dict as [String: AnyObject]
        }
    
        static func get(key: String, from defaults: NSUD) -> SomeModel? {
            guard let dict = defaults.object(forKey: key) as? [String: AnyObject] else {
                return nil
            }
    
            return SomeModel(dictionary: dict)
        }
    
        static func set(value: SomeModel?, forKey key: String, in defaults: NSUD) -> Void {
            guard let value = value else { return defaults.set(nil, forKey: key) }
            defaults.set(value.toDictionary(), forKey: key)
        }
    }
    
    opened by ksmandersen 2
  • PalauDefaultable mapping simplification?

    PalauDefaultable mapping simplification?

    Wouldn`t it be sufficient when all we need to define is:

    public protocol PalauDefaultable {
    
      /// The associatedtype for the Value
      associatedType ValueType
    
      public static func from(any: AnyObject?) -> ValueType?
      public static func to(any:ValueType?) -> AnyObject?
    }
    

    Is it worth going down that path or should we live with the current implementation until Swift 3 is available?

    See: #13

    question 
    opened by elm4ward 2
  • Support for Swift Package Manager

    Support for Swift Package Manager

    Please provide a Package.swift

    import PackageDescription
    let package = Package(
       /// plist files ... 
        exclude: ["....."],
        dependencies: []
    )
    
    enhancement 
    opened by elm4ward 1
  • Support for Arrays of Structs

    Support for Arrays of Structs

    In order to provide support for an array of structs we have 2 options.

      1. wait for swift 3 and have an easy solution hopefully
      1. use a custom type of entry ... a.k.a PalauDefaultsArrayEntry

    This PR provides a PalauDefaultsArrayEntry that adds support for an Array of T. In order to not duplicate the code a new protocol PalauEntry was added that handles most of the stuff for PalauDefaultsEntry and PalauDefaultsArrayEntry.

    As the protocol PalauDefaultable now needs 2 more functions:

    static func get(key: String, from defaults: NSUD) -> [ValueType]?
    static func set(value: [ValueType]?, forKey key: String, in defaults: NSUD) -> Void
    

    the PR also contains a new PalauCustomDefaultable protocol which makes conversion for custom structs easier. You only need to define the mapping to an intermediate type like [String: AnyObject]. After that you can use PalauDefaultsArrayEntry and PalauDefaultsEntry.

    didSet and ensure still work.

    Tests were duplicated: PalauArrayTests.

    Any thoughts on this? What do we do with the namings?

    enhancement 
    opened by elm4ward 0
  • Added: Feature did set

    Added: Feature did set

    You can add a callback by calling didSet on the PalauDefaultsEntry. The callback will be invoked after set and clear. The callback will provide the new value and the old value.

    opened by elm4ward 0
Owner
symentis GmbH
We build enterprise applications and love to share.
symentis GmbH
TypedDefaults is a utility library to type-safely use NSUserDefaults.

TypedDefaults TypedDefaults is a utility library to type-safely use NSUserDefaults. Motivation The talk Keep Calm and Type Erase On by Gwendolyn Westo

Kazunobu Tasaka 110 Feb 6, 2022
📕A single value proxy for NSUserDefaults, with clean API.

OneStore A single value proxy for NSUserDefaults, with clean API. With OneStore… Create one proxy(an OneStore object) for each NSUserDefaults value. M

Muukii 27 May 12, 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
Modern Swift API for NSUserDefaults

SwiftyUserDefaults Modern Swift API for NSUserDefaults SwiftyUserDefaults makes user defaults enjoyable to use by combining expressive Swifty API with

Luke 4.7k Jan 9, 2023
Save NSObject into NSUserDefaults in one-line, auto class mapping

Akaibu What is it ? Archive any class in just ONE-LINE of code. Automatically map class's properties under the hood. Drop in replacement of NSObject S

Roy Tang 16 Jun 21, 2021
SecureDefaults is a wrapper over UserDefaults/NSUserDefaults with an extra AES-256 encryption layer

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

Victor Peschenkov 216 Dec 22, 2022
SwiftyUserDefaults - Modern Swift API for NSUserDefaults

SwiftyUserDefaults makes user defaults enjoyable to use by combining expressive Swifty API with the benefits of static typing. Define your keys in one place, use value types easily, and get extra safety and convenient compile-time checks for free.

Luke 4.7k Dec 27, 2022
UICKeyChainStore is a simple wrapper for Keychain on iOS, watchOS, tvOS and macOS. Makes using Keychain APIs as easy as NSUserDefaults.

UICKeyChainStore UICKeyChainStore is a simple wrapper for Keychain that works on iOS and OS X. Makes using Keychain APIs as easy as NSUserDefaults. Lo

Kishikawa Katsumi 3.1k Dec 28, 2022
Modern Swift API for NSUserDefaults

SwiftyUserDefaults Modern Swift API for NSUserDefaults SwiftyUserDefaults makes user defaults enjoyable to use by combining expressive Swifty API with

Luke 4.7k Jan 9, 2023
TypedDefaults is a utility library to type-safely use NSUserDefaults.

TypedDefaults TypedDefaults is a utility library to type-safely use NSUserDefaults. Motivation The talk Keep Calm and Type Erase On by Gwendolyn Westo

Kazunobu Tasaka 110 Feb 6, 2022
📕A single value proxy for NSUserDefaults, with clean API.

OneStore A single value proxy for NSUserDefaults, with clean API. With OneStore… Create one proxy(an OneStore object) for each NSUserDefaults value. M

Muukii 27 May 12, 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
Modern Swift API for NSUserDefaults

SwiftyUserDefaults Modern Swift API for NSUserDefaults SwiftyUserDefaults makes user defaults enjoyable to use by combining expressive Swifty API with

Luke 4.7k Jan 9, 2023
Save NSObject into NSUserDefaults in one-line, auto class mapping

Akaibu What is it ? Archive any class in just ONE-LINE of code. Automatically map class's properties under the hood. Drop in replacement of NSObject S

Roy Tang 16 Jun 21, 2021