Implementing NSCopying in Subclass of Subclass

I have a small class hierarchy that I’m having trouble implementing copyWithZone: for. I’ve read the NSCopying documentation, and I can’t find the correct answer.

Take two classes: Shape and Square. Square is defined as:

  • iOS Singletons and Memory Management
  • With ARC, what's better: alloc or autorelease initializers?
  • Objective C: ARC errors (Automatic release problems)
  • Why doesn't this crash?
  • NSMutableArray Access Issue
  • How is release handled for @synthesized retain properties?
  • @interface Square : Shape
    

    No surprise there. Each class has one property, Shape has a “sides” int, and Square has a “width” int. The copyWithZone: methods are seen below:

    Shape

    - (id)copyWithZone:(NSZone *)zone {
        Shape *s = [[Shape alloc] init];
        s.sides = self.sides;
        return s;
    }
    

    Square

    - (id)copyWithZone:(NSZone *)zone {
        Square *s = (Square *)[super copyWithZone:zone];
        s.width = self.width;
        return s;
    }
    

    Looking at the documentation, this seems to be the “right” way to do things.

    It is not.

    If you were to try to set/access the width property of a Square returned by the copyWithZone: method, it would fail with an error similar to the one below:

    2010-12-17 11:55:35.441 Hierarchy[22617:a0f] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Shape setWidth:]: unrecognized selector sent to instance 0x10010c970'
    

    Calling [super copyWithZone:zone]; in the Square method actually returns a Shape. It’s a miracle you’re even allowed to set the width property in that method.

    That having been said, how does one implement NSCopying for subclasses in a way that does not make them responsible for copying the variables of its superclass?

    Solutions Collect From Internet About “Implementing NSCopying in Subclass of Subclass”

    One of those things you realize right after asking…

    The implementation of copyWithZone: in the superclass (Shape) shouldn’t be assuming it’s a Shape. So instead of the wrong way, as I mentioned above:

    - (id)copyWithZone:(NSZone *)zone {
        Shape *s = [[Shape allocWithZone:zone] init];
        s.sides = self.sides;
        return s;
    }
    

    You should instead use:

    - (id)copyWithZone:(NSZone *)zone {
        Shape *s = [[[self class] allocWithZone:zone] init]; // <-- NOTE CHANGE
        s.sides = self.sides;
        return s;
    }