3

I am trying to implement In App Purchases, and I am tracking which purchases a user has purchased via NSUserDefaults. I have a function that sets the values of each purchase, but when it runs, I get an error saying that I am mutating the dictionary of purchase values even though the dictionary is declared with a var instead of a let and is an NSMutableDictionary. Sometimes it does work, but most of the time it doesn't. I get a few warnings about declaring my variables with let instead of var, but I ignore them to give my variables maximum mutability. Why does this not work?

The error I get is:

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

Code:

static func setPurchased(purchase:PurchaseID, value:Bool)
{
    let defaults = NSUserDefaults.standardUserDefaults()

    if (defaults.objectForKey(PURCHASES_KEY) == nil)
    {
        initializePurchases()       // go set some initial values
    }

    if var purchases = (defaults.objectForKey(PURCHASES_KEY) as? NSMutableDictionary)   // WARNING: should be declared with let because not mutated
    {
        print("setting purchase \(purchase.id()) to \(value)")
        var key = purchase.id()   // WARNING: should be declared with let because not mutated

        purchases[key] = value    // CRASH HERE
        defaults.setObject(purchases, forKey:PURCHASES_KEY)
        defaults.synchronize()
    }
}
Adam Evans
  • 2,072
  • 1
  • 20
  • 29
  • @ozgur Since I am setting a key value pair, it does have to be of `NSMutableDictionary` type. Regular old `AnyObject` type won't let me do that as far as I know. – Adam Evans May 13 '16 at 23:22
  • @ozgur Make your comment an answer. It seems to have worked, but I have no idea why. – Adam Evans May 13 '16 at 23:40

1 Answers1

3

This is not the right way of converting an immutable dictionary into its mutable counterpart.

var already ensures that whatever is returned from defaults.objectForKey(PURCHASES_KEY) will be copied as a mutable type so all you need is specify the type of the mutable object which in our case can safely be Dictionary<String: AnyObject> if you are sure all keys are String type:

if var purchases = defaults.objectForKey(PURCHASES_KEY) as? Dictionary<String: AnyObject> {
  ...
  purchases[key] = value
}

Please see this SO question for more information about mutability/immutability in collection types.

Community
  • 1
  • 1
Ozgur Vatansever
  • 49,246
  • 17
  • 84
  • 119