Isuru
Isuru

Reputation: 31323

Save a tuple in NSUserDefaults

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.

Upvotes: 7

Views: 6642

Answers (6)

Fattie
Fattie

Reputation: 12272

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.

Upvotes: 0

4oby
4oby

Reputation: 607

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

Upvotes: 0

Igor Tupitsyn
Igor Tupitsyn

Reputation: 1193

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])
}

Upvotes: 1

Lucas Huang
Lucas Huang

Reputation: 4016

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.

Upvotes: -1

fearmint
fearmint

Reputation: 5334

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)

Upvotes: 8

Leo Dabus
Leo Dabus

Reputation: 236498

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)
    }
}

Upvotes: 1

Related Questions