< Previous PageNext Page > Hide TOC

Implementing Object Copy

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:

These are described in the following sections.

Contents:

Deep Versus Shallow Copies
Using the “alloc, init...” Approach
Using NSCopyObject()
Copying Mutable Versus Immutable Objects


Deep Versus Shallow Copies

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;
}

Independent Copy

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.


Figure 1  Copying instance variables both shallowly and deeply

Copying instance variables both shallowly and deeply

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.

Inheriting NSCopying from the Superclass

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.

Using the “alloc, init...” Approach

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.

Using NSCopyObject()

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.


Figure 2  Initialization of the reference count during a copy

Initialization of the reference count during a copy

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.

Copying Mutable Versus Immutable Objects

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.



< Previous PageNext Page > Hide TOC


© 2009 Apple Inc. All Rights Reserved. (Last updated: 2009-05-06)


Did this document help you?
Yes: Tell us what works for you.
It’s good, but: Report typos, inaccuracies, and so forth.
It wasn’t helpful: Tell us what would have helped.