Best practice when implementing copyWithZone:

I am trying to clear up a few things in my head about implementing copyWithZone:, can anyone comment on the following ...

// 001: Crime is a subclass of NSObject.
- (id)copyWithZone:(NSZone *)zone {
    Crime *newCrime = [[[self class] allocWithZone:zone] init];
    if(newCrime) {
        [newCrime setMonth:[self month]];
        [newCrime setCategory:[self category]];
        [newCrime setCoordinate:[self coordinate]];
        [newCrime setLocationName:[self locationName]];
        [newCrime setTitle:[self title]];
        [newCrime setSubtitle:[self subtitle]];
    }
    return newCrime;
}

// 002: Crime is not a subclass of NSObject.
- (id)copyWithZone:(NSZone *)zone {
    Crime *newCrime = [super copyWithZone:zone];
    [newCrime setMonth:[self month]];
    [newCrime setCategory:[self category]];
    [newCrime setCoordinate:[self coordinate]];
    [newCrime setLocationName:[self locationName]];
    [newCrime setTitle:[self title]];
    [newCrime setSubtitle:[self subtitle]];
    return newCrime;
}

In 001:

  1. Is it best to write the class name directly [[Crime allocWithZone:zone] init] or should I use [[[self Class] allocWithZone:zone] init]?

  2. Is it ok to use [self month] for copying the iVars or should I be accessing the iVars directly i.e. _month?


Solution 1:

  1. You should always use [[self class] allocWithZone:zone] to make sure you are creating a copy using the appropriate class. The example you give for 002 shows exactly why: Subclasses will call [super copyWithZone:zone] and expect to get back an instance of the appropriate class, not an instance of the super class.

  2. I access the ivars directly, so I don't need to worry about any side effects I might add to the property setter (e.g., generating notifications) later on. Keep in mind, subclasses are free to override any method. In your example, you are sending two extra messages per ivar. I would implement it as follows:

Code:

- (id)copyWithZone:(NSZone *)zone {
    Crime *newCrime = [super copyWithZone:zone];
    newCrime->_month = [_month copyWithZone:zone];
    newCrime->_category = [_category copyWithZone:zone];
    // etc...
    return newCrime;
}

Of course, whether you copy the ivars, retain them, or just assign them should mirror what the setters do.

Solution 2:

The default copy behavior of copyWithZone: method with SDK provided objects is "shallow copy". That means if you call copyWithZone: on NSString object, it will create a shallow copy but not deep copy. Difference between shallow and deep copy are :

A shallow copy of an object will only copy the references to the objects of the original array and place them into the new array.

A deep copy will actually copy the individual objects contained in the object. This done by sending each individual object the copyWithZone: message in your custom class method.

INSHORT : To get shallow copy you call retain or strong on all instance variables. To get deep copy you call copyWithZone: on all instance variables in your custom class copyWithZone: implementation. Now it's your choice to choose.

Solution 3:

How about this one that implement deep copy:

/// Class Foo has two properties: month and category
- (id)copyWithZone:(NSZone *zone) {
    Foo *newFoo;
    if ([self.superclass instancesRespondToSelector:@selector(copyWithZone:)]) {
        newFoo = [super copyWithZone:zone];
    } else {
        newFoo = [[self.class allocWithZone:zone] init];
    }
    newFoo->_month = [_month copyWithZone:zone];
    newFoo->_category = [_category copyWithZone:zone];
    return newFoo;
}