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
  •  | 
  •  

문제

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;
    }
}
도움이 되었습니까?

해결책

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.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top