Copying Objects in Objective-C

From Techotopia
Revision as of 19:33, 23 April 2012 by Neil (Talk | contribs)

Jump to: navigation, search
PreviousTable of ContentsNext
Constructing and Manipulating Paths with NSPathUtilitiesUsing Objective-C Preprocessor Directives

<google>BUY_OBJC</google>

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.


Contents


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 return 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 An Overview of Objective-C Object Oriented Programming 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 these 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 and copyWithZone Method Implementation

The first step in implementing the <NSCopying> protocol is to declare that the class conforms to the protocol. 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[])
{
    @autoreleasepool {

        BankAccount *account1;
        BankAccount *account2;

        account1 = [BankAccount alloc];

        account1 = [account1 init];

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

        [account1 displayAccountInfo];

        account2 = [account1 copy];

        [account2 displayAccountInfo];

    }
    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 a Deep Copy

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:

NSArray *myArray1;
NSArray *myArray2;
NSMutableString *tmpStr;
NSMutableString *string1;
NSMutableString *string2;
NSMutableString *string3;

string1 = [NSMutableString stringWithString: @"Red"];
string2 = [NSMutableString stringWithString: @"Green"];
string3 = [NSMutableString stringWithString: @"Blue"];

myArray1 = [NSMutableArray arrayWithObjects: string1, string2, string3, nil];

We now have an array name myArray1 that contains as elements three variables that each point to a 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 elements it contains still point to the same three string objects. We can prove this by modifying the string contained in element 0 of myArray1 from "Red" to "Yellow" and then displaying the contents of the object referenced by the first element of myArray2:

tmpStr = [myArray1 objectAtIndex: 0];

[tmpStr setString: @"Yellow"];

NSLog (@"First element of myArray2 = %@", [myArray2 objectAtIndex: 0]);

When compiled and executed, the NSLog call will display the following output:

First element of myArray2 = Yellow

Clearly when we change the object pointed to by element 0 of myArray1 we were also changing the object pointed to by element 0 or myArray2. This proves that even though we created a copy of myArray1 to create myArray2 the pointers contained in the array stayed the same.

In order to create entirely new instance objects we need to perform a deep copy. This can be achieved by writing the object and its constituent elements to an archive and then reading back into the new object. Our example would, therefore, be rewritten as follows:

NSArray *myArray1;
NSArray *myArray2;
NSMutableString *tmpStr;
NSMutableString *string1;
NSMutableString *string2;
NSMutableString *string3;
NSData *buffer;


string1 = [NSMutableString stringWithString: @"Red"];
string2 = [NSMutableString stringWithString: @"Green"];
string3 = [NSMutableString stringWithString: @"Blue"];

myArray1 = [NSMutableArray arrayWithObjects: string1, string2, string3, nil];

buffer = [NSKeyedArchiver archivedDataWithRootObject: myArray1];
myArray2 = [NSKeyedUnarchiver unarchiveObjectWithData: buffer];

tmpStr = [myArray1 objectAtIndex: 0];

[tmpStr setString: @"Yellow"];

NSLog (@"First element of myArray1 = %@", [myArray1 objectAtIndex: 0]);
NSLog (@"First element of myArray2 = %@", [myArray2 objectAtIndex: 0]);

When executed, the following output will be displayed clearly indicating that the objects referenced in myArray2 are entirely different to those referenced by myArray1. We have, therefore performed a deep copy.

First element of myArray1 = Yellow
First element of myArray2 = Red

<google>BUY_OBJC_BOTTOM</google>


PreviousTable of ContentsNext
Constructing and Manipulating Paths with NSPathUtilitiesUsing Objective-C Preprocessor Directives