objective-cnscopyingnsmutablecopying

mutableCopyWithZone confusing requirement?


Clarification: the question is not about mutable vs immutable but about a call to super creating an instance of the correct class. I hadn't thought of using [[self class] alloc] in the base class, and this seems to solve my issues. I'll accept that anser in the next few hours if nothing better comes up :)


The Apple spec says:

If a subclass inherits NSMutableCopying from its superclass and declares
additional instance variables, the subclass has to override mutableCopyWithZone:
to properly handle its own instance variables, invoking the superclass’s
implementation first.

That's very confusing. Consider

@interface Base : NSObject<NSMutableCopying>
@property (nonatomic, assign) NSInteger value ;
@end
@implementation Base
...
@end


@interface Derived : Base<NSMutableCopying>
@property (nonatomic, assign) NSInteger value2 ;
@end

@implementation Derived

- (id) mutableCopyWithZone: (NSZone *) zone {
    // Huh ???
    Derived * derived = [super mutableCopyWithZone: zone] ;
    ...
    // Huh ??????
    derived.value2 = self.value2 ;
    return derived ;
}
...
@end

I just do not understand how this code could possibly be correct if I follow the spec.

When the call to [super mutableCopyWithZone: zone] returns, I am expecting that the base class has only allocated enough room for its own ivars. There's no way it can tell that Derived instances need more room for its own ivars.

What is it that the documentation means, really? How should I implement this?


Solution

  • super only changes the way of dispatching (static instead of dynamic). But it does not change the receiver, esp. it does no upcast. In -mutableCopyWithZone: (super) self still points to an instance object of the derived class.

    Trouble is possible, if the super method (or super super method and so on) does not implement the object creation in a proper way:

    copy = [[BaseClass alloc] init]; // Wrong, an instance of the base class is created
    
    copy = [[[self class] alloc] init]; // Correct, an instance of [self class] == DerivedClass is created
    

    Using the second approach you get an instance of the derived class with full memory for its ivars.

    Conclusion: Implement it in this way, if BaseClass implements its -mutableCopyWithZone: properly. Otherwise you have no other change then creating your own copy and init it.

    In my books I always write, that there are little reason to use the class name inside a class. Using [self class] (in an instance method) and self (in a class method) are in 99 % the better approaches.