Object Mutability
Cocoa objects are either mutable or immutable. You cannot change the encapsulated values of immutable objects; once such an object is created, the value it represents remains the same throughout the object’s life. But you can change the encapsulated value of a mutable object at any time. The following sections explain the reasons for having mutable and immutable variants of an object type, describe the characteristics and side-effects of object mutability, and recommend how best to handle objects when their mutability is an issue.
Why Mutable and Immutable Object Variants?
Objects by default are mutable. Most objects allow you to change their encapsulated data through setter accessor methods. For example, you can change the size, positioning, title, buffering behavior, and other characteristics of an NSWindow
object. A well-designed model object—say, an object representing a customer record—requires setter methods to change its instance data.
The Foundation framework adds some nuance to this picture by introducing classes that have mutable and immutable variants. The mutable subclasses are typically subclasses of their immutable superclass and have “Mutable” embedded in the class name. These classes include the following:
Although these classes have atypical names, they are closer to the mutable norm than their immutable counterparts. Why this complexity? What purpose does having an immutable variant of a mutable object serve?
Consider a scenario where all objects are capable of being mutated. In your application you invoke a method and are handed back a reference to an object representing a string. You use this string in your user interface to identify a particular piece of data. Now another subsystem in your application gets its own reference to that same string and decides to mutate it. Suddenly your label has changed out from under you. Things can become even more dire if, for instance, you get a reference to an array that you use to populate a table view. The user selects a row corresponding to an object in the array that has been removed by some code elsewhere in the program, and problems ensue. Immutability is a guarantee that an object won’t unexpectedly change in value while you’re using it.
Objects that are good candidates for immutability are ones that encapsulate collections of discrete values or contain values that are stored in buffers (which are themselves kinds of collections, either of characters or bytes). But not all such value objects necessarily benefit from having mutable versions. Objects that contain a single simple value, such as instances of NSNumber
or NSDate
, are not good candidates for mutability. When the represented value changes in these cases, it makes more sense to replace the old instance with a new instance.
Performance is also a reason for immutable versions of objects representing things such as strings and dictionaries. Mutable objects for basic entities such as strings and dictionaries bring some overhead with them. Because they must dynamically manage a changeable backing store—allocating and deallocating chunks of memory as needed—mutable objects can be less efficient than their immutable counterparts.
Although in theory immutability guarantees that an object’s value is stable, in practice this guarantee isn’t always assured. A method may choose to hand out a mutable object under the return type of its immutable variant; later, it may decide to mutate the object, possibly violating assumptions and choices the recipient has made based on the earlier value. The mutability of an object itself may change as it undergoes various transformations. For example, serializing a property list (using the NSPropertyListSerialization
class) does not preserve the mutability aspect of objects, only their general kind—a dictionary, an array, and so on. Thus, when you deserialize this property list, the resulting objects might not be of the same class as the original objects. For instance, what was once an NSMutableDictionary
object might now be a NSDictionary
object.
Programming with Mutable Objects
When the mutability of objects is an issue, it’s best to adopt some defensive programming practices. Here are a few general rules or guidelines:
Use a mutable variant of an object when you need to modify its contents frequently and incrementally after it has been created.
Sometimes it’s preferable to replace one immutable object with another; for example, most instance variables that hold string values should be assigned immutable
NSString
objects that are replaced with setter methods.Rely on the return type for indications of mutability.
If you have any doubts about whether an object is, or should be, mutable, go with immutable.
This section explores the gray areas in these guidelines, discussing typical choices you have to make when programming with mutable objects. It also gives an overview of methods in the Foundation framework for creating mutable objects and for converting between mutable and immutable object variants.
Creating and Converting Mutable Objects
You can create a mutable object through the standard nested alloc
-init
message—for example:
NSMutableDictionary *mutDict = [[NSMutableDictionary alloc] init]; |
However, many mutable classes offer initializers and factory methods that let you specify the initial or probable capacity of the object, such as the arrayWithCapacity:
class method of NSMutableArray
:
NSMutableArray *mutArray = [NSMutableArray arrayWithCapacity:[timeZones count]]; |
The capacity hint enables more efficient storage of the mutable object’s data. (Because the convention for class factory methods is to return autoreleased instances, be sure to retain the object if you wish to keep it viable in your code.)
You can also create a mutable object by making a mutable copy of an existing object of that general type. To do so, invoke the mutableCopy
method that each immutable super class of a Foundation mutable class implements:
NSMutableSet *mutSet = [aSet mutableCopy]; |
In the other direction, you can send copy
to a mutable object to make an immutable copy of the object.
Many Foundation classes with immutable and mutable variants include methods for converting between the variants, including:
type
With
Type:
—for example,arrayWithArray:
set
Type:
—for example,setString:
(mutable classes only)initWith
Type:copyItems:
—for example,initWithDictionary:copyItems:
Storing and Returning Mutable Instance Variables
In Cocoa development you often have to decide whether to make an instance variable mutable or immutable. For an instance variable whose value can change, such as a dictionary or string, when is it appropriate to make the object mutable? And when is it better to make the object immutable and replace it with another object when its represented value changes?
Generally, when you have an object whose contents change wholesale, it’s better to use an immutable object. Strings (NSString
) and data objects (NSData
) usually fall into this category. If an object is likely to change incrementally, it is a reasonable approach to make it mutable. Collections such as arrays and dictionaries fall into this category. However, the frequency of changes and the size of the collection should be factors in this decision. For example, if you have a small array that seldom changes, it’s better to make it immutable.
There are a couple of other considerations when deciding on the mutability of a collection held as an instance variable:
If you have a mutable collection that is frequently changed and that you frequently hand out to clients (that is, you return it directly in a getter accessor method), you run the risk of mutating something that your clients might have a reference to. If this risk is probable, the instance variable should be immutable.
If the value of the instance variable frequently changes but you rarely return it to clients in getter methods, you can make the instance variable mutable but return an immutable copy of it in your accessor method; in memory-managed programs, this object would be autoreleased (Listing 9-1).
Listing 9-1 Returning an immutable copy of a mutable instance variable
@interface MyClass : NSObject { |
// ... |
NSMutableSet *widgets; |
} |
// ... |
@end |
@implementation MyClass |
- (NSSet *)widgets { |
return (NSSet *)[[widgets copy] autorelease]; |
} |
One sophisticated approach for handling mutable collections that are returned to clients is to maintain a flag that records whether the object is currently mutable or immutable. If there is a change, make the object mutable and apply the change. When handing out the collection, make the object immutable (if necessary) before returning it.
Receiving Mutable Objects
The invoker of a method is interested in the mutability of a returned object for two reasons:
It wants to know if it can change the object’s value.
It wants to know if the object’s value will change unexpectedly while it has a reference to it.
Use Return Type, Not Introspection
To determine whether it can change a received object, the receiver of a message must rely on the formal type of the return value. If it receives, for example, an array object typed as immutable, it should not attempt to mutate it. It is not an acceptable programming practice to determine if an object is mutable based on its class membership—for example:
if ( [anArray isKindOfClass:[NSMutableArray class]] ) { |
// add, remove objects from anArray |
} |
For reasons related to implementation, what isKindOfClass:
returns in this case may not be accurate. But for reasons other than this, you should not make assumptions about whether an object is mutable based on class membership. Your decision should be guided solely by what the signature of the method vending the object says about its mutability. If you are not sure whether an object is mutable or immutable, assume it’s immutable.
A couple of examples might help clarify why this guideline is important:
You read a property list from a file. When the Foundation framework processes the list, it notices that various subsets of the property list are identical, so it creates a set of objects that it shares among all those subsets. Afterward you look at the created property list objects and decide to mutate one subset. Suddenly, and without being aware of it, you’ve changed the tree in multiple places.
You ask
NSView
for its subviews (with thesubviews
method) and it returns an object that is declared to be anNSArray
but which could be anNSMutableArray
internally. Then you pass that array to some other code that, through introspection, determines it to be mutable and changes it. By changing this array, the code is mutating internal data structures of theNSView
class.
So don’t make an assumption about object mutability based on what introspection tells you about an object. Treat objects as mutable or not based on what you are handed at the API boundaries (that is, based on the return type). If you need to unambiguously mark an object as mutable or immutable when you pass it to clients, pass that information as a flag along with the object.
Make Snapshots of Received Objects
If you want to ensure that a supposedly immutable object received from a method does not mutate without your knowing about it, you can make snapshots of the object by copying it locally. Then occasionally compare the stored version of the object with the most recent version. If the object has mutated, you can adjust anything in your program that is dependent on the previous version of the object. Listing 9-2 shows a possible implementation of this technique.
Listing 9-2 Making a snapshot of a potentially mutable object
static NSArray *snapshot = nil; |
- (void)myFunction { |
NSArray *thingArray = [otherObj things]; |
if (snapshot) { |
if ( ![thingArray isEqualToArray:snapshot] ) { |
[self updateStateWith:thingArray]; |
} |
} |
snapshot = [thingArray copy]; |
} |
A problem with making snapshots of objects for later comparison is that it is expensive. You’re required to make multiple copies of the same object. A more efficient alternative is to use key-value observing. See Key-Value Observing Programming Guide for a description of this protocol.
Mutable Objects in Collections
Storing mutable objects in collection objects can cause problems. Certain collections can become invalid or even corrupt if objects they contain mutate because, by mutating, these objects can affect the way they are placed in the collection. First, the properties of objects that are keys in hashing collections such as NSDictionary
objects or NSSet
objects will, if changed, corrupt the collection if the changed properties affect the results of the object’s hash
or isEqual:
methods. (If the hash
method of the objects in the collection does not depend on their internal state, corruption is less likely.) Second, if an object in an ordered collection such as a sorted array has its properties changed, this might affect how the object compares to other objects in the array, thus rendering the ordering invalid.
Copyright © 2012 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2012-01-09