0

What am I missing...?

elsewhere in my project there is code like this:

let allUsers = ["userName":["difficulty": 1, "highscore": 50],"userName2":["difficulty": 2, "highscore: 75]]          
defaults.setObject(allUsers, forKey: "allUsers")

I want to change a value for one user in that array of users:

var allUsers = defaults.objectForKey("allUsers") as! [String:NSMutableDictionary]

let changingUser = allUsers["userName"]! as NSMutableDictionary

Neither of these will work:

changingUser.setObject(3, forKey: "difficulty")
changingUser["difficulty"] = 3

with the error:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[__NSCFDictionary setObject:forKey:]: mutating method sent to immutable object'

rmaddy
  • 314,917
  • 42
  • 532
  • 579
RanLearns
  • 4,086
  • 5
  • 44
  • 81
  • 5
    The objects returned from `objectForKey` will be immutable. You can't simply downcast an immutable dictionary to a mutable dictionary. You need to create a new NSMutableArray from the array returned from user defaults, modify that and then write it back. – Paulw11 Jun 03 '16 at 21:58
  • You could also type your allUsers dictionary initially, such as `let allUsers: [String:NSMutableDictionary] = ...`. Your cast to NSMutableDictionary would be correct at that point. – Wyatt Jun 03 '16 at 22:12

1 Answers1

1

You can't just cast a NSDictionary to a NSMutableDictionary. You can initialize one by passing in an immutable counterpart, but unfortunately that isn't a deep mutable structure.

However, you can cast it to a Swift Dictionary and assign it to a var:

var allUsers = defaults.objectForKey("allUsers") as? [String: [String: Int]] ?? [:]
allUsers2["userName"]?["difficulty"] = 3

But note that Swift dictionaries are value objects and have value semantics. That is, if you do this in steps like so:

var allUsers = defaults.objectForKey("allUsers") as? [String: [String: Int]] ?? [:]
changingUser = allUsers["userName"]
changingUser?["difficulty"] = 3 //mutates a copy

This will not work, since changingUser is a copy and while writing to it, does mutate it, it doesn't mutate the allUsers dictionary. So you have to either write the changed inner dict back into the outer dict, or do as I did above.

Svein Halvor Halvorsen
  • 2,474
  • 1
  • 16
  • 14
  • So it checks whether allUsers is a [String: [String: Int]] and if not then [:] does what? just sets it to an array of AnyObjects? In my case, it will always be [String: [String: Int]]. Thank you! – RanLearns Jun 04 '16 at 03:26
  • No, the type system guarantees that a given variable (or constant) always has s consistent type. So the ?? part is a default value of the cast fails, that returns an empty dictionary, but still of type `[String: [String: Int]]` – Svein Halvor Halvorsen Jun 04 '16 at 07:57