Copying Objects in Objective-C

Revision as of 15:41, 16 November 2009 by Neil (Talk | contribs) (New page: In Pointers and Indirection in Objective-C we discussed the fact that when working with objects in Objective-C we are essentially using variables that contain pointers to the memory ad...)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Revision as of 15:41, 16 November 2009 by Neil (Talk | contribs) (New page: In Pointers and Indirection in Objective-C we discussed the fact that when working with objects in Objective-C we are essentially using variables that contain pointers to the memory ad...)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

In Pointers and Indirection in Objective-C we discussed the fact that when working with objects in Objective-C we are essentially using variables that contain pointers to the memory addresses where the objects are stored. We also mentioned the problems this presents when we want to copy an object. In this chapter of Objective-C 2.0 Essentials we will look at the steps necessary to copy an object.

Objects and Pointers

Before we look at copying objects we should first recap the issue of objects and indirection. When we create an object in Objective-C we assign the object to a variable. Consider, for example, the following Objective-C code excerpt:

BankAccount *account1;

BankAccount *account1 = [[BankAccount alloc] init];

The above code creates a variable named account1 and declares it as being of type pointer to object of type BankAccount. The alloc and init method calls create the object in memory and reurn the address of that object which is then assigned to account1. Clearly, therefore, account1 is not storing the actual object but rather holding a pointer to the memory location of the object.

If we tried to copy the object using the assignment operator, therefore, all we would be doing is copying the address value from one variable to another:

BankAccount *account2;

account2 = account1;

The above code will provide us with two variables, both of which point to the same object. If we really want a separate copy of an object, therefore, we must specifically perform a copy.

Copying an Object in Objective-C using the <NSCopying> Protocol

In [[]] the topic of object-oriented programming was covered. In particular the fact that most classes are derived from the NSObject base class was discussed. The advantage of deriving new classes from NSObject is that those classes inherit a number of useful methods designed specifically for creating, managing and manipulating objects. Two such methods are the copy and mutableCopy methods. These methods use something called the <NSCopying> Protocol. This protocol defines what must be implemented in an object in order for it to be copyable using the copy and mutableCopy methods. Classes from the Foundation Framework will typically already be compliant with the <NSCopying> Protocol. We can, therefore, simply call the copy or mutableCopy methods to create a copy of an object:

NSString *myString1 = @"Hello";
NSString *myString2;

myString2 = [myString1 mutableCopy];

On execution of the mutableCopy method in the above example we will have two independent string objects both of which contain the same string. Because we used the mutable version of the copy method we will be able to modify the contents of myString2. In doing so no change will occur to myString1 because that is an entirely different object.

If we attempted to use either of tehse copying methods on our own classes without implementing the <NSCopying> protocol the code will fail to run. Take, for example, the BankAccount class created in An Overview of Objective-C Object Oriented Programming. If we were to create an instance of the class and then try to call the copy methods we would be presented with a runtime error similar to the following:

*** -[BankAccount copyWithZone:]: unrecognized selector sent to instance 0x1034f0

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[BankAccount copyWithZone:]: unrecognized selector sent to instance 0x1034f0'

The reason for this error is that the copy and mutableCopy methods inherited from the NSObject class are trying to call a method called ''copyWithZone. Unfortunately we have not yet implmented this object in our BankAccount class. The next step, therefore, is to learn how to write such a class.


<NSCopying> Protocol Implementation

The first step in implementing the <NSCopying> protocol is to declare that the class conforms. This is achieved in the @interface section of the class. For example:

@interface BankAccount: NSObject <NSCopying>

Also in the implementation we need to declare that the class includes a method named copyWithZone that returns a new object and accepts the zone of the source object as an argument. The entire @interface section of our class will now read as follows:

@interface BankAccount: NSObject <NSCopying>
{
        double accountBalance;
        long accountNumber;
}
-(void) setAccount: (long) y andBalance: (double) x;
-(double) getAccountBalance;
-(long) getAccountNumber;
-(void) setAccountBalance: (double) x;
-(void) setAccountNumber: (long) y;
-(void) displayAccountInfo;
-(id) copyWithZone: (NSZone *) zone;
@end

In our @implementation section we now need to write the code for our copyWithZone method. This method creates a new BankAccount object, copies the values of the instance variables (in this case accountBalance and accountNumber) and returns a pointer to the new object:

-(id) copyWithZone: (NSZone *) zone
{
        BankAccount *accountCopy = [[BankAccount allocWithZone: zone] init];

        [accountCopy setAccount: accountNumber andBalance: accountBalance];
        return accountCopy;
}

If we now bring this all together we can using successfully utilize the copy method:

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

        BankAccount *account1;
        BankAccount *account2;

        account1 = [BankAccount alloc];

        account1 = [account1 init];

        [account1 setAccountBalance: 1500.53];
        [account1 setAccountNumber: 34543212];

        [account1 displayAccountInfo];

        account2 = [account1 copy];

        [account2 displayAccountInfo];

        [pool drain];

        return 0;


}

Now when executed, the above code creates a copy of the object referenced by account1 and assigns a pointer to the new object to variable account2.

Performing Deep Object Copies

The copying techniques we have looked at so far in this chapter are referred to as shallow copies. This means that if the copy or mutableCopy methods are used to copy an object that itself contains instance variables that are themselves pointers to objects, the copy will also contain pointers to the same objects. To better understand this concept consider an NSArray object that contains as its elements pointers to three string objects:

NSMutableString *myString1 = @"Red";
NSMutableString *myString2 = @"Green";
NSMutableString *myString3 = @"Blue";

NSArray *myArray1;
NSArray *myArray2;

myArray1 = [NSArray arrayWithObjects: myString1, myString2, myString3, nil];

We now have an array name myArray1 that contains as it elements three variables that each point to string object. We could now create a copy of that array and assign it to variable pointer myArray2:

myArray2 = [myArray1 copy];

The myArray2 object is a separate object, but the lements it contains still point to the same three string objects. We can prove this by modifying the string contained in myString1 from "Red" to "Yello" and then displaying the contents of the object referenced by the first element of myArray2: