18. Copying Objects

This chapter discusses some of the subtleties involved in copying objects. We will introduce the concept of shallow versus deep copying and how to make copies under the Foundation framework.

Chapter 8, “Inheritance,” discussed what happens when you assign one object to another with a simple assignment statement, such as

origin = pt;

In that example, origin and pt are both Point objects, that we defined like this:

@interface Point: Object
{
    int x;
    int y;
};
  ...
@end

You will recall that the effect of the assignment is to simply copy the address of the object pt into origin. At the end of the assignment operation, both variables point to the same location in memory. Making changes to the instance variables with a message such as

[origin setX: 100 andY: 200];

changes the x, y coordinate of the Point object referenced by both origin and pt variables because they both reference the same object in memory.

The same applies to Foundation objects: Assigning one variable to another simply creates another reference to the object (but it does not increase the reference count as discussed in Chapter 17, “Memory Management”). So, if dataArray and dataArray2 are both NSMutableArray objects, the statements

dataArray2 = dataArray;
[dataArray2 removeObjectAtIndex: 0];

remove the first element from the same array that is referenced by both variables.

The copy and mutableCopy Methods

The Foundation classes implement methods known as copy and mutableCopy, which you can use to create a copy of an object. This is done by implementing a method in conformance with the <NSCopying> protocol for making copies. If your class needs to distinguish between making mutable and immutable copies of an object, you need to implement a method according to the <NSMutableCopying> protocol as well. You learn how to do that later in this section.

Getting back to the copy methods for the Foundation classes, given the two NSMutableArray objects dataArray2 and dataArray as described in the previous section, the statement

dataArray2 = [dataArray mutableCopy];

creates a new copy of dataArray in memory, duplicating all its elements. Subsequently, executing the statement

[dataArray2 removeObjectAtIndex: 0];

removes the first element from dataArray2 but not from dataArray. This is illustrated in Program 18.1.

Program 18.1.


#import <Foundation/NSObject.h>
#import <Foundation/NSArray.h>
#import <Foundation/NSString.h>
#import <Foundation/NSAutoreleasePool.h>

int main (int argc, char *argv[])
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  NSMutableArray  *dataArray = [NSMutableArray arrayWithObjects:
    @"one", @"two", @"three", @"four", nil];
  NSMutableArray  *dataArray2;
  int        i, n;

  // simple assignment

  dataArray2 = dataArray;
  [dataArray2 removeObjectAtIndex: 0];

  printf ("dataArray: ");
  n= [dataArray count];
  for (i = 0; i < n; ++i)
    printf ("%s ", [[dataArray objectAtIndex: i] cString]);

  printf (" dataArray2: ");
  n= [dataArray2 count];
  for (i = 0; i < n; ++i)
    printf ("%s ", [[dataArray2 objectAtIndex: i] cString]);

  // try a Copy, then remove the first element from the copy

  dataArray2 = [dataArray mutableCopy];
  [dataArray2 removeObjectAtIndex: 0];

  printf (" dataArray: ");
  n = [dataArray count];
  for (i = 0; i < n; ++i)
         printf ("%s ", [[dataArray objectAtIndex: i] cString]);

  printf (" dataArray2: ");
  n = [dataArray2 count];
  for (i = 0; i < n; ++i)
         printf ("%s ", [[dataArray2 objectAtIndex: i] cString]);


  printf (" ");
  [dataArray2 release];
  [pool release];
  return 0;
}


Program 18.1. Output


dataArray: two three four
dataArray2: two three four
dataArray: two three four
dataArray2: three four


The program defines the mutable array object dataArray and sets its elements to the string objects @"one", @"two", @"three", and @"four", respectively.

As we've discussed, the assignment

dataArray2 = dataArray;

simply creates another reference to the same array object in memory. When you remove the first object from dataArray2 and subsequently print the elements from both array objects, it's no surprise that the first element (the string @"one") is gone from both references.

Next, you create a mutable copy of dataArray and assign the resulting copy to dataArray2. This creates two distinct mutable arrays in memory, both containing three elements. Now, when you remove the first element from dataArray2, it has no effect on the contents of dataArray, as verified by the last two lines of the program's output.

Note that making a mutable copy of an object does not require that the object being copied be mutable. The same thing applies to immutable copies: You can make an immutable copy of a mutable object.

Also note when making a copy of an array that the retain count for each element in the array is automatically incremented by the copy operation. Therefore, if you make a copy of an array and subsequently release the original array, the copy still contains valid elements.

Because a copy of dataArray was made in the program using the mutableCopy method, you are responsible for releasing its memory. The rule that says you are responsible for releasing objects you create with one of the copy methods was covered at the end of the last chapter. This explains the inclusion of the line

[dataArray2 release];

toward the end of Program 18.1.

Shallow Versus Deep Copying

Program 18.1 fills the elements of dataArray with immutable strings (recall that constant string objects are immutable). In Program 18.2, you'll fill it with mutable strings instead so that you can change one of the strings in the array. Take a look at Program 18.2 and see whether you understand its output.

Program 18.2.


#import <Foundation/NSObject.h>
#import <Foundation/NSArray.h>
#import <Foundation/NSString.h>
#import <Foundation/NSAutoreleasePool.h>

int main (int argc, char *argv[])
{
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
  NSMutableArray    *dataArray = [NSMutableArray arrayWithObjects:
        [NSMutableString stringWithString: @"one"],
        [NSMutableString stringWithString: @"two"],
        [NSMutableString stringWithString: @"three"],
        nil
  ];
  NSMutableArray    *dataArray2;
  NSMutableString   *mStr;
  int               i, n;

  printf ("dataArray: ");
  n = [dataArray count];
  for (i = 0; i < n; ++i)
         printf ("%s ", [[dataArray objectAtIndex: i] cString]);

  // make a copy, then change one of the strings

  dataArray2 = [dataArray mutableCopy];

  mStr = [dataArray objectAtIndex: 0];
  [mStr appendString: @"ONE"];

  printf (" dataArray: ");
  n = [dataArray count];
  for (i = 0; i < n; ++i)
         printf ("%s ", [[dataArray objectAtIndex: i] cString]);

  printf (" dataArray2: ");
  n = [dataArray2 count];
  for (i = 0; i < n; ++i)
         printf ("%s ", [[dataArray2 objectAtIndex: i] cString]);


  printf (" ");
  [dataArray2 release];
  [pool release];
  return 0;
}


Program 18.2. Output


dataArray:  one two three
dataArray:  oneONE two three
dataArray2: oneONE two three


You retrieved the first element of dataArray2 with the following statement:

mStr = [dataArray2 objectAtIndex: 0];

Then you appended the string @"ONE" to it with this statement:

[mStr appendString: @"ONE"];

Notice the value of the first element of both the original array and its copy: They both were modified. Perhaps you can understand why the first element of dataArray was changed but not why its copy was as well. When you get an element from a collection, you get a new reference to that element, but not a new copy. So, when the objectAtIndex: method is invoked on dataArray, the returned object is pointing to the same object in memory as the first element in dataArray. Subsequently modifying the string object mStr has the side effect of also changing the first element of dataArray, as you can see from the output.

But what about the copy you made? Why is its first element changed as well? This has to do with the fact that copies by default are shallow copies. That means when the array was copied with the mutableCopy method, space was allocated for a new array object in memory and the individual elements were copied into the new array. But copying each element in the array from the original to new location meant just copying the reference from one element of the array to another. The net result was that the elements of both arrays referenced the same strings in memory. This is no different from the assignment of one object to another that we covered at the start of this chapter.

To make distinct copies of each element of the array, you need to perform what is known as a deep copy. This means making copies of the contents of each object in the array, not just copies of the references to the objects (and think about what that implies if an element of an array is itself an array object). But deep copies are not performed by default when you use the copy or mutableCopy methods with the Foundation classes. This is simply a fact that you should be aware of. In Chapter 19, “Archiving,” we'll show you how to use the Foundation's archiving capabilities to create a deep copy of an object.

When you copy an array, a dictionary, or a set, for example, you get a new copy of those collections. However, you might need to make your own copies of individual elements if you want to make changes to one collection but not to its copy. For example, assuming you wanted to change the first element of dataArray2 but not dataArray in Program 18.2, you could have made a new string (using a method such as stringWithString:) and stored it into the first location of dataArray2, as follows:

mStr = [NSMutableString stringWithString:
[dataArray2 objectAtIndex: 0]];

Then you could have made the changes to mStr and added it to the array using the replaceObject:atIndex:withObject: method as follows:

[mStr appendString @"ONE"];
[dataArray2 replaceObjectAtIndex: 0 withObject: mStr];

Hopefully you realize that even after replacing the object in the array, mStr and the first element of dataArray2 refer to the same object in memory. That means subsequent changes to mStr in your program will also change the first element of the array. If that's not what you want, you can always release mStr and allocate a new instance because an object is automatically retained by the replaceObject:atIndex:withObject: method.

Implementing the <NSCopying> Protocol

If you try to use the copy method on one of your own classes—for example, on your address book as follows:

NewBook = [myBook mutableCopy];

you'll get an error message that looks something like this:

2003-07-28 12:53:29.224 a.out[2337] *** -[AddressBook copyWithZone:]:
  selector not recognized
2003-07-28 12:53:29.254 a.out[2337] *** Uncaught exception:
<NSInvalidArgumentException> *** -[AddressBook copyWithZone:]:
  selector not recognized

As noted, to implement copying with your own classes, you have to implement one or two methods according to the <NSCopying> protocol.

Copying Fractions

We're going to show how you can add a copy method to your Fraction class, which you used extensively in Part I, “The Objective-C Language.” Note that the techniques we will describe here for copying strategies will work fine for your own classes. If those classes are subclasses of any of the Foundation classes, you might need to implement a more sophisticated copying strategy that we won't describe here. You'll have to account for the fact that the superclass might have already implemented its own copying strategy.

You'll recall that your Fraction class contains two integer instance variables called numerator and denominator. To make a copy of one of these objects, you need to allocate space for a new fraction and then simply copy the values of the two integers into the new fraction.

When you implement the <NSCopying> protocol, your class must implement the copyWithZone: method to respond to a copy message. (The copy message just sends a copyWithZone: message to your class with an argument of nil.) If you want to make a distinction between mutable and immutable copies, as we noted, you'll also need to implement the mutableCopyWithZone: method according to the <NSMutableCopying> protocol. If you implement both methods, copyWithZone: should return an immutable copy and mutableCopyWithZone: a mutable one. Making a mutable copy of an object does not require that the object being copied also be mutable (and vice versa); it's perfectly reasonable to want to make a mutable copy of an immutable object (consider a string object, for example).

Go back to your Fraction class, which is listed in Appendix D, “Fraction and Address Book Examples.” That class was written with the root object Object, so you must make it suitable for use with Foundation.1 To do this, in the interface file change the line

#import <objc/Object.h>

to

#import <Foundation/NSObject.h>

Also, here's what the @interface directive should look like:

@interface Fraction: NSObject <NSCopying>

Fraction is a subclass of NSObject and conforms to the NSCopying protocol.

In the implementation file fraction.m add the following definition for your new method:

-(Fraction *) copyWithZone: (NSZone *) zone
{
  Fraction *newFract = [[Fraction allocWithZone: zone] init];

  [newFract setTo: numerator over: denominator];
  return newFract;
}

The zone argument has to do with different memory zones that you can allocate and work with in your program. You need to deal with them only if you're writing applications that allocate a lot of memory and you want to optimize the allocation by grouping them into these zones. You can take the value passed to copyWithZone: and hand it off to a memory allocation method called allocWithZone:. This method allocates memory in a specified zone.

After allocating a new Fraction object, you copy the receiver's numerator and denominator variables into it. The copyWithZone: method is supposed to return the new copy of the object, which is what you do in your method.

Program 18.3 tests your new method.

Program 18.3.


// Copying fractions

#import "Fraction.h"
#import <Foundation/NSAutoreleasePool.h>


int main (int argc, char *argv[])
{
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        Fraction *f1 = [[Fraction alloc] init];
        Fraction *f2;

        [f1 setTo: 2 over: 5];
        f2 = [f1 copy];

        [f2 setTo: 1 over: 3];

        [f1 print];
        printf (" ");
        [f2 print];
        printf (" ");

     [f1 release];
     [f2 release];
     [pool release];
     return 0;
}


Program 18.3. Output


2/5
1/3


The program creates a Fraction object called f1 and sets it to 2/5. It then invokes the copy method to make a copy, which sends the copyWithZone: message to your object. That method makes a new Fraction, copies the values from f1 into it, and returns the result. Back in main, you assign that result to f2. Subsequently setting the value in f2 to the fraction 1/3 verifies that it had no effect on the original fraction f1. Change the line in the program that reads

f2 = [f1 copy];

to simply

f2 = f1;

and remove the release of f2 at the end of the program to see the different results you will obtain.

If your class might be subclassed, your copyWithZone: method will be inherited. In that case, the line in the method that reads

Fraction *newFract = [[Fraction allocWithZone: zone] init];

should be changed to read

Fraction *newFract = [[[self class] allocWithZone: zone] init];

That way, you'll allocate a new object from the class that is the receiver of the copy. (For example, if it has been subclassed to a class named NewFraction, you should be sure you allocate a new NewFraction object in the inherited method, instead of a Fraction object.)

If you are writing a copyWithZone: method for a class whose superclass also implements the <NSCopying> protocol, you should first call the copy method on the superclass to copy the inherited instance variables and then include your own code to copy whatever additional instance variables (if any) you might have added to the class.

It's up to you to decide whether you want to implement a shallow or deep copy in your class. Just document it for other users of your class so they know.

Copying Objects in Setter and Getter Methods

Whenever you implement a setter or getter method, you should think about what you're storing in the instance variables, what you're retrieving, and whether you need to protect these values. For example, consider when you set the name of one of your AddressCard objects using the setName: method:

[newCard setName: newName];

Assume that newName is a string object containing the name for your new card. Assume that inside the setter routine you simply assigned the parameter to the corresponding instance variable:

-(void) setName: (NSString *) theName
{
    name = theName;

}

Now, what do you think would happen if the program later changed some of the characters contained in newName in the program? Right, it would also unintentionally change the corresponding field in your address card because they would both reference the same string object.

As you have already seen, a safer approach is to make a copy of the object in the setter routine to prevent this inadvertent effect. We did this by using the alloc method to create a new string object and then initWithString: to set it to the value of the parameter provided to the method.

You can also write a version of the setName: method to use copy, like so:

-(void) setName: (NSString *) theName
{
    name = [theName copy];

}

Of course, to make this setter routine memory-management friendly, you should autorelease the old value first, like so:

-(void) setName: (NSString *) theName
{
    [name autorelease];
    name = [theName copy];
}

The same discussion about protecting the value of your instance variables applies to the getter routines, if you think about it for a moment. If you return an object, you must ensure that changes to the returned value will not affect the value of your instance variables. In such a case, you can make a copy of the instance variable and return that instead.

Getting back to the implementation of a copy method, if you are copying instance variables that contain immutable objects (for example, immutable string objects), you might not need to make a new copy of the object's contents. It might suffice to simply make a new reference to the object by retaining it. For example, when implementing a copy method for the AddressCard class, which contains name and email members, the following implementation for copyWithZone: would suffice:

-(AddresssCard *) copyWithZone: (NSZone *) zone
{
AddressCard *newCard = [[AddressCard allocWithZone: zone] init];
[newCard retainName: name andEmail: email];
return newCard;
}

-(void) retainName: (NSString *) theName andEmail: (NSString *) theEmail
{
   name = [theName retain];
   email = [theEmail retain];
}

The setName:andEmail: method isn't used here to copy the instance variables over because that method makes new copies of its arguments, which would defeat the whole purpose of this exercise. Instead, you just retained the two variables using a new method called retainName:andEmail:. (You could have set the two instance variables in newCard directly in the copyWithZone: method, but it involves pointer operations, which we've been able to avoid up to this point. Of course, the pointer operations would be more efficient and would not expose the user of this class to a method [retainName:andEmail:] that was not intended for public consumption, so at some point you might need to learn how to do that—just not right now!)

Realize that you can get away with retaining the instance variables here (instead of making complete copies of them) because the owner of the copied card can't affect the name and email members of the original. You might want to think about that for a second to verify that's the case (hint: it has to do with the setter methods).

Exercises

  1. Implement a copy method for the AddressBook class according to the NSCopying protocol. Would it make sense to also implement a mutableCopy method? Why or why not?
  2. Modify the Rectangle and Point classes defined in Chapter 8 to run under Foundation. Then add a copyWithZone: method to both classes. Make sure that the Rectangle copies its Point member origin using the Point's copy method. Does it make sense to implement both mutable and immutable copies for these classes? Explain.
  3. Create an NSDictionary dictionary object and fill it with some key/object pairs. Then make both mutable and immutable copies. Are these deep copies or shallow copies that are made? Verify your answer.
  4. Who is responsible for releasing the memory allocated for the new AddressCard in the copyWithZone: method as implemented in this chapter? Why?
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.109.165