Here is some example code that I used to test my understanding of how to copy collection objects (NSArray, NSDictionary) as either a shallow copy or a deep copy. Also, how to implement proper copying in your own class for deep and shallow copying:
int DEEP_COPY = 1;
// This class has no distinction between mutable and immutable so I do not need to implement
@interface MyClass : NSObject
{
NSMutableDictionary *myDict;
int myID;
}
@property (nonatomic, retain) NSMutableDictionary *myDict;
@propertyintmyID;
-(id) initWithID: (int) _ID;
- (id)copyWithZone:(NSZone *)zone;
@end
@implementation MyClass
@synthesize myDict, myID;
-(id) initWithID: (int) ID {
if (self = [super init]) {
self.myDict = [NSMutableDictionary dictionaryWithObjectsAndKeys:@"one",@"first",@"two",@"second",nil];
myID = _ID;
}
returnself;
}
- (id)copyWithZone:(NSZone *)zone {
MyClass *copy;
if (DEEP_COPY) {
// Deep copy
NSLog(@"Performing deep copy in copyWithZone (MyClass)");
copy = [[MyClass allocWithZone:zone] initWithID:myID]; // This sets the value field
// Continue the copy chain on dictionary elements
copy.myDict = [[NSMutableDictionary allocWithZone:zone] initWithDictionary:myDict copyItems:YES];
} else {
// Shallow copy
NSLog(@"Performing shallow copy in copyWithZone (MyClass)");
copy = NSCopyObject(self, 0, zone);
}
return [copy autorelease];
}
-(void) dealloc {
[myDict release];
[super dealloc];
}
@end
#import
@interface MemoryTestAppDelegate : NSObject
UIWindow *window;
NSArray *firstArray;
NSMutableArray *secondArray;
}
@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) NSArray *firstArray;
@property (nonatomic, retain) NSMutableArray *secondArray;
@end
@implementation MemoryTestAppDelegate
@synthesize window, firstArray, secondArray;
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// * Convenience method used, so should use self-dot accessor to retain.
// * Each element is created, then retained by the array, then should be autoreleased so their retain count is balanced.
self.firstArray = [NSArray arrayWithObjects:[[[MyClass alloc] initWithID:1] autorelease], [[[MyClass alloc] initWithID: 2] autorelease], nil];
[[[firstArray objectAtIndex:0] myDict] setValue:@"Initialized" forKey:@"first"];
NSMutableArray *outerArray = [[NSMutableArray alloc] init];
[outerArray addObject:firstArray]; //firstArray retain count incremented
// Test 1 - using copy
// * New memory to hold a second array is allocated, but each object in the array points to the same elements
// as firstArray. Each element's retain count in the first array is incremented
// * Should use mutableCopy if copying into a mutable array
// * If copy is used here, the following line would cause an exception
// * Since a copy function is called, do not use self dot, which would cause an additional retain
secondArray = [firstArray mutableCopy];
[secondArray addObject :[[[MyClassalloc] initWithID: 2] autorelease]];
// * New memory to hold a second array is allocated, but no new elements created. firstArray retain count incremented.
// * Elements of firstArray are not affected.
NSArray *secondOuterArray = [outerArray copy];
// * After changing the value in the original array, I expect to see the same change in the copied arrays
// because only shallow copies have been made so far.
NSLog(@"Changing first array element to 3");
[[firstArray objectAtIndex:0] setMyID:3];
NSLog(@"Second array element ID is %0d (Expected value: 3)", [[secondArray objectAtIndex:0] myID]);
NSLog(@"Second outer array element ID is %0d (Expected value: 3)", [[[secondOuterArray objectAtIndex:0] objectAtIndex:0 ] myID]);
// ***** test 2 - using initWithArray:copyItems
[secondArray release]; // Sends a release to each element, balancing the retain from the copy
[secondOuterArray release]; // Sends a release to firstArray, firstArray's elements are unaffected.
// * This creates memory for secondArray and sends a copy message to each element in the first array.
// * The copy message then calls copyWithZone on each element, so the elements must have that method defined.
// * This will create a new MyClass object for each element in secondArray, but how pointers in that object are
// treated will depend on the copyWithZone implementation.
// * Value fields will be independent copies.
secondArray = [[NSMutableArray alloc] initWithArray: firstArray copyItems: YES];
[secondArray addObject :[[[MyClassalloc] initWithID: 2] autorelease]];
// * The only array element in this case is a NSMutableArray, so the copy message is sent to NSMutableArray.
// * It creates a new NSArray element, but the child elements are not copied. No new MyClass objects are created.
// * NSMutableArrays and NSArrays always perform a shallow copy.
secondOuterArray = [[NSArray alloc] initWithArray: outerArray copyItems: YES];
NSLog(@"Changing first array element to 4");
[[firstArray objectAtIndex:0] setMyID: 4];
// ***** Test the value object
// * In the case of secondArray, new MyClass objects were created by the copy, and the myID field is a value field.
// * So, I expect to see no effect from the change to the original array.
NSLog(@"Second array element ID after initWithArray:copyItems is %0d (Expected value: 3)", [[secondArray objectAtIndex:0] myID]);
// * Here, no new MyClass objects were created in secondOuterArray, so I expect to see the change reflected here
NSLog(@"Second outer array element ID after initWithArray:copyItems is %0d (Expected value:4)", [[[secondOuterArray objectAtIndex:0] objectAtIndex:0 ] myID]);
// ***** Test a pointer object
// * The results here depend on how copyWithZone is implemented in MyClass.
[[[firstArray objectAtIndex:0] myDict] setValue:@"Changed" forKey:@"first"];
if (DEEP_COPY) {
NSLog(@"Second array element dict value after initWithArray:copyItems is %@ (Expected value: \"Initialized\")", [[[secondArray objectAtIndex:0] myDict] objectForKey:@"first"]);
} else {
NSLog(@"Second array element dict value after initWithArray:copyItems is %@ (Expected value: \"Changed\")", [[[secondArray objectAtIndex:0] myDict] objectForKey:@"first"]);
}
// * Here, as in the value object case, no new MyClass elements are in the copy, so the result will always
// match the firstArray
NSLog(@"Second outer array element ID after initWithArray:copyItems is %@ (Expected value: \"Changed\")", [[[[secondOuterArray objectAtIndex:0] objectAtIndex:0 ] myDict] objectForKey: @"first"]);
[secondOuterArray release];
[outerArray release];
[window makeKeyAndVisible];
}
- (void)dealloc {
[firstArray release];
[secondArray release];
[window release];
[super dealloc];
}
@end