Is returning [self retain] in copyWithZone for immutable classes with mutable subclasses really safe / a good idea?

StackOverflow https://stackoverflow.com/questions/19714985

  •  02-07-2022
  •  | 
  •  

Pergunta

One often reads, that immutable classes can implement copyWithZone very efficiently in the following way:

- (id) copyWithZone:(NSZone*)zone
{
    return [self retain];
}

The idea behind that implementation is obvious: The original and the copy are both immutable instances and they will always have exactly the same content, so why not let both point to the same storage by retaining the original and avoid the overhead of copying.

However, what will happen if there is a mutable subclass? With a clean architecture, where a subclass does not have to care about implementation details of its base class, the mutable subclass should be fine to implement copyWithZone in this way:

- (id) copyWithZone:(NSZone*)zone
{
    MyClass* myCopy = [super copyWithZone:zone];
    myCopy->myMember = [myMember copyWithZone:zone];
    return myCopy;
}

But what does this mean with the above superclass implementation of copyWithZone? The subclass is mutable, so although the copy still is immutable, the original now is mutable, but the subclass copyWithZone thanks to the superclass implementation operates on a retained instance of itself: self and myCopy both point the the same instance, so if I later change the value of mutableOriginal.myMember, then that will also change immutableCopy.myMember, which is just plain wrong.

So shouldn't immutable classes better implement copyWithZone in the following way?

- (id) copyWithZone:(NSZone*)zone
{
    if([[self class] isMemberOfClass:[MyBaseClass class]])
        return [self retain];
    else
    {
        MyBaseClass* myCopy = [[self alloc] init];
        myCopy->myBaseMember = [myBaseMember copyWithZone:zone];
        return myCopy;
    }
}
Foi útil?

Solução

Your best option would be to have an initWithMyImmutableObject initialiser in your immutable superclass. Your subclass can then just implement NSCopying with

- (id) copyWithZone:(NSZone*)zone {
    return [[[self superclass] alloc] initWithMyImmutableObject:self]
}

That way the actual copying of properties is done in a method of your superclass, which has access to all private members that need to be copied.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top