Difference between revisions of "Copying Objects in Objective-C"

From Techotopia
Jump to: navigation, search
m (Text replacement - "<google>BUY_OBJC</google>" to "<htmlet>objc<htmlet>")
Line 7: Line 7:
 
<hr>
 
<hr>
  
<google>BUY_OBJC</google>
+
<htmlet>objc<htmlet>
  
 
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.
 
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.

Revision as of 21:05, 1 February 2016

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

Cannot find HTML file objc<htmlet> 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: <pre> BankAccount *account1; BankAccount *account1 = [[BankAccount alloc] init]; <pre> 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: <pre> BankAccount *account2; account2 = account1; <pre> 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: <pre> NSString *myString1 = @"Hello"; NSString *myString2; myString2 = [myString1 mutableCopy]; <pre> 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: <pre> *** -[BankAccount copyWithZone:]: unrecognized selector sent to instance 0x1034f0 *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[BankAccount copyWithZone:]: unrecognized selector sent to instance 0x1034f0' <pre> 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: <tt>@interface BankAccount: NSObject <NSCopying><tt> 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: <pre> @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 <pre> 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: <pre> -(id) copyWithZone: (NSZone *) zone { BankAccount *accountCopy = [[BankAccount allocWithZone: zone] init]; [accountCopy setAccount: accountNumber andBalance: accountBalance]; return accountCopy; } <pre> If we now bring this all together we can using successfully utilize the ''copy'' method: <pre> 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; } <pre> 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: <pre> 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]; <pre> 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'': <pre> myArray2 = [myArray1 copy]; <pre> 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'': <pre> tmpStr = [myArray1 objectAtIndex: 0]; [tmpStr setString: @"Yellow"]; NSLog (@"First element of myArray2 = %@", [myArray2 objectAtIndex: 0]); <pre> When compiled and executed, the NSLog call will display the following output: <pre> First element of myArray2 = Yellow <pre> 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: <pre> 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]); <pre> 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. <pre> First element of myArray1 = Yellow First element of myArray2 = Red <pre> <google>BUY_OBJC_BOTTOM<google> <hr> <table border="0" cellspacing="0" width="100%"> <tr> <td width="20%">[[Constructing and Manipulating Paths with NSPathUtilities|Previous]]<td align="center">[[Objective-C 2.0 Essentials|Table of Contents]]<td width="20%" align="right">[[Using Objective-C Preprocessor Directives|Next]]<td> <tr> <td width="20%">Constructing and Manipulating Paths with NSPathUtilities<td align="center"><td width="20%" align="right">Using Objective-C Preprocessor Directives<td> <table>.html