6
votes

I am building a model for a MVC and I am experiencing an anomaly where contrary to the Apple Documentation "Values returned from NSUserDefaults are immutable, even if you set a mutable object as the value.", the [[NSUserDefaults standardUserDefaults] objectForKey:@"key"] is returning a mutable array.

I created an empty application for iOS in Xcode 4D199 to re-create the condition and confirm that it isn't accountable to other factors on my project.

I am setting the NSMutableArray as shown:

- (void)setupTest
{
    NSMutableArray *mutableArray = [[NSMutableArray alloc] init];
    [mutableArray addObject:@"one"];
    [mutableArray addObject:@"two"];

    [[NSUserDefaults standardUserDefaults] setObject:mutableArray forKey:@"mutableArray_01"];
    [[NSUserDefaults standardUserDefaults] synchronize];    
}

and, I am getting the object as shown:

- (void)checkTest
{
    NSMutableArray *mutableArrayInCheck = nil;
    id whatIsThis = [[NSUserDefaults standardUserDefaults] objectForKey:@"mutableArray_01"];
    if([whatIsThis isKindOfClass:[NSMutableArray class]])
    {
        NSLog(@"The array is mutable.");
        mutableArrayInCheck = (NSMutableArray *)whatIsThis;
    }
    if([whatIsThis isKindOfClass:[NSArray class]])
    {
        NSLog(@"The array is immutable.");
    }
    if ([whatIsThis isMemberOfClass:[NSMutableArray class]])
    {
        NSLog(@"The array is a member of NSMutableArray class");
    }

    if (mutableArrayInCheck) 
    {
        [mutableArrayInCheck addObject:@"three"];
        NSLog([mutableArrayInCheck description]);
    }
}

Now, according to the apple documentation, one would expect the console to only display the immutable line. But when I execute the code, the following lines are displayed in the console:

2012-01-05 18:47:33.328 Tester_01[78533:f803] The array is mutable.
2012-01-05 18:47:33.348 Tester_01[78533:f803] The array is immutable.
2012-01-05 18:47:33.349 Tester_01[78533:f803] (
    one,
    two,
    three
)

So, I am wondering if there is something that I am missing here.

For additional information, the code that executes the tests is as shown:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    BOOL setupKey = NO;
    if (setupKey) 
    {
        [self setupTest];
    }   
    BOOL checkKey = YES;
    if (checkKey)
    {
        [self checkTest];
    }

    self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
    // Override point for customization after application launch.
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

Initially, I ran the project with setupKey set to YES. After the first run, I changed the setupKey to NO. The console log is an outcome whilst the setupKey was set to NO. Let me know what you think.

2
I guess they just mean to say you should not count on getting a mutable array. In my projects I often also just assign an NSMutableArray to an NSArray pointer, because doing [NSArray arrayWithArray:mutableArray] would only be adding overhead, with very little benefit. - mvds
just a guess: did you restart the app after adding the mutable array to the user defaults and than read it without setting it again. I can imaging, that the object stays the same during one session until it was serialized and written to disk and read back again. - vikingosegundo
Yes, like I explained in the end of the question, with setupKey set to YES, I set the mutable array the first time I ran the application. After that, I set the setupKey to NO, then restarted the application to get the console output described above. - Jin

2 Answers

2
votes

Strictly speaking you are right. The statement "Values returned are immutable" is confusing, because this makes it look like getting a mutable object should be impossible. However, the statement should be read as "Values returned cannot be guaranteed to be mutable". So, even if you store a mutable array, when you read the value back, you will get a NSArray object (which might be an object of the mutable descendent class NSMutableArray, but this cannot be guaranteed and may vary between runs or depend on the array content).

1
votes

isKindOfClass the method is not appropriate for the class cluster

the next paragraph is in the documentation

Be careful when using this method on objects represented by a class cluster. Because of the nature of class clusters, the object you get back may not always be the type you expected. If you call a method that returns a class cluster, the exact type returned by the method is the best indicator of what you can do with that object. For example, if a method returns a pointer to an NSArray object, you should not use this method to see if the array is mutable, as shown in the following code:

 // DO NOT DO THIS!
 //if ([myArray isKindOfClass:[NSMutableArray class]])
 //{
 // Modify the object
 //}