This article describes two approaches to implementing the NSCopying
protocol’s copyWithZone:
method for the purpose of copying objects.
There are two basic approaches to creating copies by implementing the NSCopying
protocol’s copyWithZone:
method. You can use alloc
and init...
, or you can use NSCopyObject
. To choose the one that is right for your class, you need to consider the following questions:
Do I need a deep or shallow copy?
Do I inherit NSCopying
behavior from my superclass?
These are described in the following sections.
Deep Versus Shallow Copies
Using the “alloc, init...” Approach
Using NSCopyObject()
Copying Mutable Versus Immutable Objects
Generally, copying an object involves creating a new instance and initializing it with the values in the original object. Copying the values for non-pointer instance variables, such as booleans, integers, and floating points, is straightforward. When copying pointer instance variables there are two approaches. One approach, called a shallow copy, copies the pointer value from the original object into the copy. Thus, the original and the copy share referenced data. The other approach, called a deep copy, duplicates the data referenced by the pointer and assigns it to the copy’s instance variable.
The implementation of an instance variable’s set method should reflect the kind of copying you need to use. You should deeply copy the instance variable if the corresponding set method copies the new value as in this method:
- (void)setMyVariable:(id)newValue |
{ |
[myVariable autorelease]; |
myVariable = [newValue copy]; |
} |
You should shallowly copy the instance variable if the corresponding set method retains the new value as illustrated by this method:
- (void)setMyVariable:(id)newValue |
{ |
[myVariable autorelease]; |
myVariable = [newValue retain]; |
} |
Similarly, you should shallowly copy the instance variable if its set method simply assigns the new value to the instance variable without copying or retaining it as in the following example—although this is typically rare:
- (void)setMyVariable:(id)newValue |
{ |
myVariable = newValue; |
} |
To produce a copy of an object that is truly independent of the original, the entire object must be deeply copied. Every instance variable must be duplicated. If the instance variables themselves have instance variables, those too must be duplicated, and so on. In many cases, a mixed approach is more useful. Pointer instance variables that can be thought of as data containers are generally deeply copied, while more sophisticated instance variables like delegates are shallowly copied.
@interface Product : NSObject <NSCopying> |
{ |
NSString *productName; |
float price; |
id delegate; |
} |
@end |
For example, a Product class adopts NSCopying
. Product instances have a name, a price, and a delegate as declared in this interface.
Copying a Product instance produces a deep copy of productName
because it represents a flat data value. On the other hand, the delegate
instance variable is a more complex object capable of functioning properly for both Products. The copy and the original should therefore share the delegate. Figure 1 represents the images of a Product instance and a copy in memory.
The different pointer values for productName
illustrate that the original and the copy each have their own productName
string object. The pointer values for delegate
are the same, indicating that the two product objects share the same object as their delegate.
If the superclass does not implement NSCopying
, your class’s implementation has to copy the instance variables it inherits as well as those declared in your class. Generally, the safest way to do this is by using alloc
, init...
, and set
methods.
On the other hand, if your class inherits NSCopying
behavior and has declared additional instance variables, you need to implement copyWithZone:
, too. In this method, invoke the superclass’s implementation to copy inherited instance variables and then copy the additional instance variables. How you handle the new instance variables depends on your familiarity with the superclass’s implementation. If the superclass used or might have used NSCopyObject
, you must handle instance variables differently than you would if alloc
and init...
were used.
If a class does not inherit NSCopying
behavior, you should implement copyWithZone:
using alloc
, init...
, and set methods. For example, an implementation of copyWithZone:
for the Product class described in “Independent Copy” might be implemented in the following way:
- (id)copyWithZone:(NSZone *)zone |
{ |
Product *copy = [[[self class] allocWithZone: zone] |
initWithProductName:[self productName] |
price:[self price]]; |
[copy setDelegate:[self delegate]]; |
return copy; |
} |
Because implementation details associated with inherited instance variables are encapsulated in the superclass, it is generally better to implement NSCopying
with the alloc
, init...
approach. Doing so uses policy implemented in set methods to determine the kind of copying needed of instance variables.
When a class inherits NSCopying
behavior, you must consider the possibility that the superclass’s implementation uses the NSCopyObject
function. NSCopyObject
creates an exact shallow copy of an object by copying instance variable values but not the data they point to. For example, NSCell
's implementation of copyWithZone:
could be defined in the following way:
- (id)copyWithZone:(NSZone *)zone |
{ |
NSCell *cellCopy = NSCopyObject(self, 0, zone); |
/* Assume that other initialization takes place here. */ |
cellCopy->image = nil; |
[cellCopy setImage:[self image]]; |
return cellCopy; |
} |
In the implementation above, NSCopyObject
creates an exact shallow copy of the original cell. This behavior is desirable for copying instance variables that are not pointers or are pointers to non-retained data that is shallowly copied. Pointer instance variables for retained objects need additional treatment.
In the copyWithZone:
example above, image
is a pointer to a retained object. The policy to retain the image is reflected in the following implementation of the setImage:
accessor method.
- (void)setImage:(NSImage *)anImage |
{ |
[image autorelease]; |
image = [anImage retain]; |
} |
Notice that setImage:
autoreleases image
before it reassigns it. If the above implementation of copyWithZone:
had not explicitly set the copy’s image
instance variable to nil
before invoking setImage:
, the image referenced by the copy and the original would be released without a corresponding retain.
Even though image
points to the right object, it is conceptually uninitialized. Unlike the instance variables that are created with alloc
and init...
, these uninitialized variables are not nil
-valued. You should explicitly assign initial values to these variables before using them. In this case, cellCopy
’s image
instance variable is set to nil
, then it is set using the setImage:
method.
The effects of NSCopyObject
extend to a subclass’s implementation. For example, an implementation of NSSliderCell
could copy a new titleCell
instance variable in the following way.
- (id)copyWithZone:(NSZone *)zone |
{ |
id cellCopy = [super copyWithZone:zone]; |
/* Assume that other initialization takes place here. */ |
cellCopy->titleCell = nil; |
[cellCopy setTitleCell:[self titleCell]]; |
return cellCopy; |
} |
where it is assumed the super
’s copyWithZone
: method does something like this:
id copy = [[[self class] allocWithZone: zone] init]; |
The superclass’s copyWithZone:
method is invoked to copy inherited instance variables. When you invoke a superclass’s copyWithZone:
method, assume that new object instance variables are uninitialized if there is any chance that the superclass implementation uses NSCopyObject
. Explicitly assign a value to them before using them. In this example, titleCell
is explicitly set to nil
before setTitleCell:
is invoked.
The implementation of an object’s retain count is another consideration when using NSCopyObject
. If an object stores its retain count in an instance variable, the implementation of copyWithZone:
must correctly initialize the copy’s retain count. Figure 2 illustrates the process.
The first object in Figure 2 represents a Product instance in memory. The value in refCount
indicates that the instance has been retained three times. The second object is a copy of the Product instance produced with NSCopyObject
. Its refCount
value matches the original. The third object represents the copy returned from copyWithZone:
after refCount
is correctly initialized. After copyWithZone:
creates the copy with NSCopyObject
, it assigns the value 1 to the refCount
instance variable. The sender of copyWithZone:
implicitly retains the copy and is responsible for releasing it.
Where the concept “immutable vs. mutable” applies to an object, NSCopying
produces immutable copies whether the original is immutable or not. Immutable classes can implement NSCopying
very efficiently. Since immutable objects don’t change, there is no need to duplicate them. Instead, NSCopying
can be implemented to retain
the original. For example, copyWithZone:
for an immutable string class can be implemented in the following way.
- (id)copyWithZone:(NSZone *)zone { |
return [self retain]; |
} |
Use the NSMutableCopying
protocol to make mutable copies of an object. The object itself does not need to be mutable to support mutable copying. The protocol declares the method mutableCopyWithZone:
. Mutable copying is commonly invoked with the convenience NSObject
method mutableCopy
, which invokes mutableCopyWithZone:
with the default zone.
© 2009 Apple Inc. All Rights Reserved. (Last updated: 2009-05-06)