Upcoming and OnDemand Webinars View full list

About Mutability

Mark Dalrymple

Cocoa has a number of classes that can hold arbitrary amounts of Stuff. Things like arrays, dictionaries, sets, index sets, character sets, strings, data, strings, and so on. These classes come in two flavors, mutable and immutable.

A mutable stuff container can have its contents changed after creation. Make a stuff container, put some stuff in there, take some stuff out, and maybe iterate over the stuff to do some work. (Hat tip to George Carlin. Some language might not be safe for work. )

An immutable stuff container cannot have its contents changed after creation. Make a stuff holder with an initial collection of stuff, and maybe iterate over the stuff to do some work. If you try to put some new stuff in there, or take some stuff out, you’ll get a smackdown. If you try it:

    NSArray *immutable = @[];
    [immutable addObject: @""];

You’ll get a warning:

smacky.m:7:6: warning: 'NSArray' may not respond to 'addObject:'
    [immutable addObject: @""];

Hopefully you’re fixing your warnings. If you decide to ignore the warnings and forge ahead, the toolkit will smack you down at run time:

-[__NSArrayI addObject:]: unrecognized selector sent to instance 0x1044112c0
*** Terminating app due to uncaught exception 'NSInvalidArgumentException' blah blah

The Mutable Danger

Mutable stuff containers are very convenient. You can can alter the stuff at any time without having to create a new container. That can make them dangerous to hold on to. If your object is pointing to a mutable string (say a person’s name), and that string is visible outside of your object, some other unrelated code could change that string. All of the sudden, Arthur Dent’s name has changed to Prostetnic Vogon Jeltz.

You have to be paranoid when you accept a stuff container that you’ll subsequently to be hanging on to. Even though your API may say “Give me an NSString”, someone can create an NSMutableString and give it to you without complaint. NSMutableString is a subclass of NSString, so anywhere you can use an NSString, you can use an NSMutableString.

Once you’ve retained the string (or assigned it to a strong reference), anyone with a reference to that mutable string can change it. That’s bad. That actually happened in older versions of Cocoa: the string value from a text field would return the mutable string that the text field was displaying. If you just stashed that value away, and then re-used the text field, you’d find your original string value changed.

Similarly, if you have a mutable stuff container as an implementation detail, be careful about returning it from a method or function. Say your Cat object has a list of objects in the house that the cat claims as theirs. You know that collection will keep on growing, so it makes sense for it to be mutable.

NSMutableArray *_catToys;
...
- (NSArray *) houseThingsOfMine {
    return _catToys;
} // houseThingsOfMine

Even though the return type of this method is NSArray, you’re actually returning your mutable array. If someone is aware of this implementation detail (say, the family Dog), they can change the array:

- (void) stealCatToys {
    NSMutableArray *catToys = (NSMutableArray *)[_cat houseThingsOfMine];
    [_myToys addObjectsFromArray: catToys];
    [catToys removeAllObjects];
} // stealCatToys

One of the problems when playing fast and loose with mutable stuff containers is that you can get bugs if the container is modified unexpectedly. (You can track these down by using KVO on the container, but still a pain.) But I try to never expose my objects to mutable containers they didn’t create.

(OBTW, I’m not talking about thread safety and mutable / immutable implications – that’s for another day.)

Mutable Stripping

You may notice that a lot of code uses the copy attribute for string properties:

@property (nonatomic, copy) NSString *title;

This means that the object passed to setTitle: will be copied. -copy makes an immutable container-of-stuff. If title comes from a mutable string, the object with this @property declaration will end up pointing to an immutable copy. If you want a copy you can modify, call -mutableCopy.

This is really nice behavior. You can use copy to “strip off mutability.” If there’s any question about receiving an object that might be mutable, make a copy of it for safety reasons. That way, nobody can accidentally (or maliciously) pass you a mutable object that’ll be changed later.

Similarly, if you’re returning a mutable implementation-detail object, you can copy it before return to strip of the mutability:

- (NSArray *) houseThingsOfMine {
    return [_catToys copy];  // or [[_catToys copy] autorelease];
} // houseThingsOfMine

Now the _catToys mutable array is safe from the Dog chewing on it. If someone tries to cast the return value to an NSMutableArray and call a mutating method, they’ll get a run time error. Remember that a cast from one object type to another in Objective-C doesn’t actually change anything. It just tells the compiler to trust you that you know what you’re doing.

“But aren’t copies expensive?” They can be for very large containers. There is a memory allocation for the new object, copying the characters, bytes, or object pointers. And then for objects there will be retains happening as they’re added to the new collections. The container objects from Apple do all sorts of tricks to make copies efficient. Rather than looping over every object and inserting it into a new array, the implementation of -copyWithZone can just duplicate its internal data structures. Also, a copy of an immutable object can just turn into a retain. The collection can’t change, so there’s no benefit in peeling off a new copy. I tend not to worry about things like this until measurement (say with Instruments) shows that it’s an actual problem.

Down the Rabbit Hole

If you ever have a need, given an arbitrary stuff container, to decide if it’s mutable or not, please reconsider your design. If you want a mutable container to be passed in so you can fill it with stuff, require it in your API. If you need to muck around with a container after it’s passed in to a call, make a local mutable copy.

A friend of mine asked me “Hey! Given an NSDictionary pointer, how do I tell if it’s mutable?” After the appropriate look of disapproval (ಠ_ಠ), it turned out he was trying to work around some weird behavior in a library he was using. I recommend “just copy whatever you’re given, and then use it.”

But that does bring up an interesting question. The obvious ways to ask “Hey! Are you an NSDictionary or an NSMutableDictionary” just don’t work. You can find the code for this posting in this gist.

Start out with two dictionaries:

    NSMutableDictionary *mutable = [[NSMutableDictionary alloc] init];
    NSDictionary *immutable = [[NSDictionary alloc] init];

The first thing we tried was respondsToSelector:

    NSLog (@"mutable responds to set object for key: %d",
           [mutable respondsToSelector: @selector(setObject:forKey:)]);
    NSLog (@"immutable responds to set object for key: %d",
           [immutable respondsToSelector: @selector(setObject:forKey:)]);

You’d expect NSDictionary would return a false value for this query. That’s not the case:

mutable responds to set object for key: 1
immutable responds to set object for key: 1

Huh. (Interesting side-node. 10.6.8 behaves as you’d expect, but 10.7 and beyond don’t. Thanks to Andre Berg for checking that out.)

How about isKindOfClass? Check for being a kind of either class:

    NSLog (@"mutable is kind of class NSDictionary: %d",
           [mutable isKindOfClass: [NSDictionary class]]);
    NSLog (@"immutable is kind of class NSDictionary: %d",
           [immutable isKindOfClass: [NSDictionary class]]);

    NSLog (@"mutable is kind of class NSMutableDictionary: %d",
           [mutable isKindOfClass: [NSMutableDictionary class]]);
    NSLog (@"immutable is kind of class NSMutableDictionary: %d",
           [immutable isKindOfClass: [NSMutableDictionary class]]);

As unexpected, they are both the kinds of both classes.


mutable is kind of class NSDictionary: 1
immutable is kind of class NSDictionary: 1
mutable is kind of class NSMutableDictionary: 1
immutable is kind of class NSMutableDictionary: 1

Huh.

OK, how about being a member of one of these classes? Recall that isKindOfClass answers “is this object an instance of this class, or one of its subclasses?”, and isMemberOfClass answers “is this object an instance of just this class?”

    NSLog (@"mutable is member of class NSDictionary: %d",
           [mutable isMemberOfClass: [NSDictionary class]]);
    NSLog (@"immutable is member of class NSDictionary: %d",
           [immutable isMemberOfClass: [NSDictionary class]]);

    NSLog (@"mutable is member of class NSMutableDictionary: %d",
           [mutable isMemberOfClass: [NSMutableDictionary class]]);
    NSLog (@"immutable is member of class NSMutableDictionary: %d",
           [immutable isMemberOfClass: [NSMutableDictionary class]]);

And, guess what? Not members.

mutable is member of class NSDictionary: 0
immutable is member of class NSDictionary: 0
mutable is member of class NSMutableDictionary: 0
immutable is member of class NSMutableDictionary: 0

So, what class are they actually?

    NSLog (@"mutable is of class %@", [mutable class]);
    NSLog (@"immutable is of class %@", [immutable class]);

With the result of:

mutable is of class __NSCFDictionary
immutable is of class __NSCFDictionary

So they’re both __NSCFDictionary. They’re both members of the same class, which is a subclass of NSDictionary (hence the success for the isKindOfClass), but they are definitely not a direct instance of NSDictionary or NSMutableDictionary.

OMGWTFBBQ(steve)

I guess that explains why my buddy couldn’t figure out whether a dictionary was mutable or immutable. All the usual tricks don’t work. So, what’s actually going on?

Recall that there are many classes that are actually implemented as class clusters. A class cluster has a façade class, such as NSString. When you make a new NSString, you actually get back an object of another type, tailored for the particular type of string. It might be one that handles very large strings more efficiently. Or maybe it’s one that stores the content in one character encoding vs. another. This is why it’s notoriously difficult to subclass things like NSStrings. You have to muck around in the class cluster world. Luckily Objective-C has categories, which make it easy to add functionality to NSString, so there’s rarely any need to have to subclass NSString or any other class clustery kind of objects.

NSArray and NSDictionary work the same way. When you make a new dictionary, you’re not actually getting a new NSDictionary. In the case of the exploratory code above, it’s getting back an __NSCFDictionary.

Take a second to pull apart that name:

  • __ – double underscores. That means “Apple’s territory. Implementation detail. Hands off”

  • CF – Core Foundation. That’s the C library layer that sits below Cocoa’s Foundation

  • Dictionary – a dictionary object.

So, we know that this is an implementation detail, where the dictionary is actually just a Core Foundation object.

Now for some Evil

Kids, you can try this at home. Please Please Please don’t ship any code doing this kind of stuff. It’s fragile and ties you to a particular implementation. But it’s kind of fun to play around with.

You can get the source code for chunks of Core Foundation from http://opensource.apple.com – look for CF-XXX in the listing and download it.

There’s CFDictionary.c – it looks promising. The code is kind of hard to read, with it being the implementation for both dictionaries and sets/bags. Look for the word “mutable” in there, and you’ll find something like this:

CFAssert2(CFBasicHashIsMutable((CFBasicHashRef)hc) ... 
    "immutable collection passed to mutating operation"

It looks like a mutable and immutable dictionaries are the same, except one has a mutable property set. CFBasicHash.h (a private header, purely implementation details) has the definition:

// Bit 6 in the CF_INFO_BITS of the CFRuntimeBase inside the CFBasicHashRef
// is the "is immutable" bit
CF_INLINE Boolean CFBasicHashIsMutable(CFConstBasicHashRef ht) {
    return __CFBitfieldGetValue(((CFRuntimeBase *)ht)->_cfinfo[CF_INFO_BITS], 6, 6)
                                ? false : true;
}

So the only thing that stands between an immutable and mutable collection is “Bit 6.” In fact, there’s a private inline function that can turn a mutable hash collection immutable:

CF_INLINE void CFBasicHashMakeImmutable(CFBasicHashRef ht) {
    __CFBitfieldSetValue(((CFRuntimeBase *)ht)->_cfinfo[CF_INFO_BITS], 6, 6, 1);
}

Because I’m curious, I want to verify that yes, this Bit 6 is set on an immutable NSDictionary, and not on a NSMutableDictionary.

The interesting part of both of those CF inlines is the cast to CFRuntimeBase, and indexing into its _cfinfo array. You can find these definitions in CFRuntime.h. I extracted out just the interesting bits so I could make a struct and then cast the dictionary pointer to it. After the cast is made I can dig into the bits. Here is the struct and a couple of convenience macros:

    typedef struct RuntimeBaseHackBlorf {
        uintptr_t _cfisa;
        uint8_t _cfinfo[4];
    } RuntimeBaseHackBlorf;

#define BIT_SIX (1 << 6)
#define BIT_SIX_SET(x)  (((RuntimeBaseHackBlorf *)(x))->_cfinfo[0] & BIT_SIX)

And then use it:


    NSLog (@"mutable has bit 6 set: %d", BIT_SIX_SET(mutable));
    NSLog (@"immutable has bit 6 set: %d", BIT_SIX_SET(immutable));

Resulting in:


2012-08-12 23:06:06.488 mutant[60714:303] mutable has bit 6 set: 0
2012-08-12 23:06:06.525 mutant[60714:303] immutable has bit 6 set: 64

YES! We finally have an answer! And we only had to do some fragile disgusting stuff to get at it. But it is an interesting spelunk into CF code.

The Takeaway

So, 2000 words later, what is the takeaway?

  • Be aware of the benefits and dangers of mutable containers of stuff

  • Strip off mutability when accepting a stuff container

  • Strip off mutability when returning a mutable stuff container

  • If you think you need to figure out if a given stuff container is mutable or not, you’re probably Doing It Wrong

Not Happy with Your Current App, or Digital Product?

Submit your event

Let's Discuss Your Project

Let's Discuss Your Project