Save a tuple in NSUserDefaults
Asked Answered
K

6

7

I'm using a tuple to store something like this.

var accessLavels: (hasInventoryAccess: Bool, hasPayrolAccess: Bool)
accessLavels = (hasInventoryAccess: true, hasPayrolAccess: false)

Now I want to save it in NSUserDefaults.

NSUserDefaults.standardUserDefaults().setValue(accessLavels, forKey: "AccessLevelKey")
NSUserDefaults.standardUserDefaults().synchronize()

But I get the following error.

Type '(hasInventoryAccess: Bool, hasPayrolAccess: Bool)' does not conform to protocol 'AnyObject'

How can I resolve this issue? If its impossible, then any other suggestions to save a tuple is welcome.

Thank you.

Kilimanjaro answered 12/1, 2015 at 6:44 Comment(2)
As an aside, calling synchronize on NSUserDefaults is almost never necessary and calling it "just in case" is not recommended by Apple.Bearish
NSUserDefaults only supports property list types: NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. If you want to store any other type of object, you can archive it to create an instance of NSData.Cartilaginous
R
8

I encountered a similar scenario trying to encode a tuple with NSCoder. The way I am solving it is by manually converting the tuple to a Dictionary. This is not a great solution as the keys need to be changed in several places if the tuple ever changes.

I had a nested enum in my tuple and gave it a base type (String) from which I converted the raw value. It was a little extra work but thankfully yours is only primitives.

# SerializeableTuple.swift

typealias AccessTuple = (hasInventoryAccess: Bool, hasPayrolAccess: Bool)
typealias AccessDictionary = [String: Bool]

let InventoryKey = "hasInventoryAccess"
let PayrollKey = "hasPayrollAccess"

func serializeTuple(tuple: AccessTuple) -> AccessDictionary {
    return [
        InventoryKey : tuple.hasInventoryAccess,
        PayrollKey : tuple.hasPayrolAccess
    ]
}

func deserializeDictionary(dictionary: AccessDictionary) -> AccessTuple {
    return AccessTuple(
        dictionary[InventoryKey] as Bool!,
        dictionary[PayrollKey] as Bool!
    )
}

# Encoding / Decoding

var accessLavels: AccessTuple = (hasInventoryAccess: true, hasPayrolAccess: false)

// Writing to defaults
let accessLevelDictionary = serializeTuple(accessLavels)
NSUserDefaults.standardUserDefaults().setObject(accessLevelDictionary, forKey: "AccessLevelKey")

// Reading from defaults
let accessDic = NSUserDefaults.standardUserDefaults().dictionaryForKey("AccessLevelKey") as AccessDictionary
let accessLev = deserializeDictionary(accessDic)
Respectful answered 19/1, 2015 at 2:23 Comment(4)
Neat trick! It works well. Although I had to make some slight changes like casting and I think you ought to pass in the AccessDictionary to the deserializeDictionary() method. I updated your code to reflect the changes. Hope that's okay. Anyway thanks for the aweseome solution. I hope Apple brings support for this in the coming versions of Swift.Kilimanjaro
By the way, is there any specific reason for using setObject instead of setValue and dictionaryForKey instead of objectForKey in saving and retrieving values from NSUserDefaults?Kilimanjaro
As @LucasHuang notes setValue is from NSKeyValueCoding. setValue uses id/AnyObject as its type and so doesn’t offer as much type safety at compile time as available. Dictionary is a class (and an instance of Dictionary is an object) and is the most appropriate type-methods. You could use the dataForKey methods and convert the Dictionary to/from NSData but that is just extra work.Respectful
nowadays it is trivial with arrays, which are just toughened tuples. until apple simply adds tuples in preferences, just use arrays in the obvious way. this 10-year old approach is unmaintainable as FM mentions, endless changes in multiple placesAbrahamabrahams
S
1

You can store Bool, Float, Int, Object, Double or URL but not a Tuple. So you have two options, save two only hasPayrolAccess and hasPayrolAccess Bool values:

NSUserDefaults.standardUserDefaults().setBool(true, forKey: "hasInventoryAccess")
NSUserDefaults.standardUserDefaults().setBool(false, forKey: "hasPayrolAccess")
let hasInventoryAccess  = NSUserDefaults.standardUserDefaults().boolForKey("hasInventoryAccess")
println(hasInventoryAccess)

let hasPayrolAccess  = NSUserDefaults.standardUserDefaults().boolForKey("hasPayrolAccess")
println(hasPayrolAccess)

Or save it using an Array of Bool:

var accessLavels = [true,false]
println(accessLavels)
NSUserDefaults.standardUserDefaults().setValue(accessLavels, forKey: "accessLavels")
if let loadAccessLavels = NSUserDefaults.standardUserDefaults().arrayForKey("accessLavels") as? [Bool] {
    if let hasInventoryAccess = loadAccessLavels.first {
        println(hasInventoryAccess)
    }
    if let hasPayrolAccess = loadAccessLavels.last {
        println(hasPayrolAccess)
    }
}
Sect answered 12/1, 2015 at 22:54 Comment(3)
Storing as two separate bools works but doesn't take advantage of the power of tuples and separates the data. Storing as an array is a little better because there is only one item stored, but dictionaries are more clear because of the keys (especially for arbitrary tuples).Respectful
in re this very old answer, we never do option 1 in production code LeoD as it would be 100s of lines of code to handle cleanup, orphans, ongoing type changes, etc. arrays are the way to go!Abrahamabrahams
Yeah buddy. I didn’t know anything about development 10 years ago but it would suffice my need at least. Swift 1 😂Sect
C
1

I had a tuple with 3 values.

The following code was used for saving the tuple. Basically, I created a string from tuple (with components separated by a comma).

let defaultsLoad = NSUserDefaults.standardUserDefaults()
if appSingleton.runwayDestShared != nil
{
   // Creating a String from the tuple
   let runwayDestString = appSingleton.runwayDestShared!.0 + "," + appSingleton.runwayDestShared!.1  + "," + appSingleton.runwayDestShared!.2
   defaultsLoad.setObject(runwayDestString, forKey: "RunwayDest")
}

To retrieve the tuple, I retrieved the string, broke it into array and used the array to re-create a tuple.

let runwayDestString = defaultsLoad.stringForKey("RunwayDest")
if let runwayDestString = runwayDestString
{
    let runwayDestArray = runwayDestString.componentsSeparatedByString(",")
    appSingleton.runwayDestShared = (runwayDestArray[0],runwayDestArray[1],runwayDestArray[2])
}
Coheir answered 11/10, 2015 at 23:25 Comment(1)
Nowadays simply use an array - see my answer ten yrs later :)Abrahamabrahams
T
0

I'ts an year old question but still:

 let accesLvl : [String:AnyObject] = ["hasInventoryAcces":true, "hasPayrolAccess":false]
 NSUserDefaults.standardUserDefaults().setObject(accesLvl, forKey: "accesLevel")

In case you save only bools, let accesLvl : [String:Bool] is a better option.

In case I don't get something (I'm fairly new to Swift and programming altogether), what would be the benefit of using a "tuple" over a Dictionary, or even a struct

Tyrannous answered 18/5, 2016 at 13:21 Comment(1)
Tuple is easier to make and use. No more keys.Ostiary
A
0

The most "tuple -like" way to handle tuples in preferences:

Saving tuples to prefs is a very common thing.

  • The nature of tuples is that they are simple, sloppy, untyped, unnamed, fast and light versions of arrays. They're a "shoddy array".

  • Hence the best idiom and most natural way to save tuples is as a shoddy array.

If the tuple types are all the same and simple, definitely use a shoddy array.

If the tuple types are mixed but easily "saved as" a type, definitely use a shoddy array.

Here's an example of the completely common thing of saving a tuple of "sort column and sort direction", which every app uses constantly.

///For a given screen name save the recent sort col and sense.
func set(forScreen: String, sortColumnSense: (Int, Bool)) {
    UserDefaults.standard.set(
        [sortColumnSense.0, (sortColumnSense.1 ? 1 : 0)],
        forKey: "sortTup_\(forScreen.suffix(64))")
}

///For a given screen name recover the recent sort col sense
func getSortColumnSense(forScreen: String) -> (Int, Bool)? {
    let quasi = UserDefaults.standard.array(forKey: "sortTup_\(forScreen.suffix(64))")
    guard let quasi,
          quasi.count == 2,
          let tup = quasi as? [Int]
    else { return nil }
    return (tup[0], (tup[1] == 0 ? false : true))
}

This completely avoids the complicated issue of consistency & cleanup if you use N different prefs keys, and the simple ad-hoc nature of storing/checking centralizes all the checking logic to SSOT so that it's impossible for any team member to be on a different page and is completely type migration safe, which is otherwise a huge amount of code if you use one of the other three approaches.

Abrahamabrahams answered 6/8 at 14:14 Comment(0)
I
-1

In the documentation, I don't see any method -setValue. Based on the docs, NSUserDefaults is able to save NSString, NSNumber, NSData, NSDictionary, NSArray, NSData and NSDate only. Here is the link: NSUserDefaults Class Reference.

So, you are not able to save tuple here. And -setValue is from NSKeyValueCodingprotocol.

Injustice answered 12/1, 2015 at 6:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.