Deep Copy

copymutableCopy를 사용하면 객체의 사본을 만들 수 있다. 이는 같은 데이터를 가리키지 않고 별도의 할당된 메모리에 데이터를 복사하여 또 하나의 할당된 객체를 만들어주는 메소드이다. 통상 NSCopying 프로토콜을 따르면 복사가 가능하고, 그 중 변경이 가능한 mutable한 객체들은 mutableCopy를 제공한다.

문제는 어떤 객체가 다른 객체를 가리키고 있는 경우에는 포인터만 복사된다는 것이다. 예를 들어서 NSArrayNSDictionary의 경우, 배열이나 사전 객체를 복사하면 하나의 사전, 배열이 추가로 생성되는 것은 맞다. 하지만 배열의 모든 원소는 원본 배열의 원소와 같은 포인터가 복사되는 것으로, 배열 그 자체는 사본이지만, 그 속에 담긴 객체들은 모두 원본인 셈이다. 이 경우 사본에서 특정 원소를 파괴하면 원본의 배열은 해제된 메모리를 참조하므로 문제가 발생한다.

NSObject 자체는 NSCopying 프로토콜을 따르지 않는다. 따라서 NSCopying을 따르지 않는 원소(커스텀 클래스)가 배열에 포함될 가능성은 매우 높으므로 배열은 원소의 포인터만을 복사한다.

따라서 만약 배열의 원소가 (배열을 root object로 하는 객체 그래프 상의 모든 객체가) 복사가 가능하다면 깊은 복사를 통해서 완전한 사본을 만들 수 있다. 다음은 카테고리를 사용해서 NSArray에 깊은 복사를 하고 있다.

  1. 각각의 원소가 깊은 복사가 가능하면 깊은 복사를 한다.
  2. 각각의 원소가 얕은 복사가 가능하면 얕은 복사를 한다.
  3. 복사가 되지 않는 원소는 NSNull 객체로 대체한다.

코드는 다음과 같다.

@interface NSArray (DeepCopy)
-(id)DeepCopy;
@end

@implementation NSArray (DeepCopy)
-(id)DeepCopy
{
    NSMutableArray *copiedArray = [[NSMutableArray alloc] initWithCapacity:[self count]];
    NSArray *result = nil;
    @autoreleasepool {
        for(id item in self) {
            id copiedItem = nil;
            if([item repondsToSelector:@selecotor(deepCopy)]) {
                copiedItem = [item deepCopy];
            } else if ([item repondsToSelector:@selector(copy)]) {
                copiedItem = [item copy];
            }    
            if (copiedItem == nil) copiedItem = [NSNull null];
            [copiedArray addObject:copiedItem];
        }
        result = [copiedArray copy];
        [copiedArray autorelease]
    }
    return result;
}

만약 원소들이 모두 인코딩될 수 있는 객체(NSCoding을 지원)라면 다음 방법도 가능하다. 아카이빙하여 데이터로 만든 후, 이 데이터로부터 깔끔하게 복원한 객체(완전한 사본이 된다)를 반환하면 된다.

-(id)deepCopy {
    NSArray *copiedArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:self]];    
    return copiedArray;
}

Deep Mutable Copy

깊은 뮤터블 복사를 하려면 절차를 하나 더 넣으면 된다. 다음 순서대로 복사를 시도해서 가능한 원소들을 깊은 뮤터블 복사한 NSMutableArray를 만들 수 있다.

  1. 깊은 뮤터블 복사가 가능한 원소는 깊은 뮤터블 복사를 한다.
  2. 깊은 복사가 가능한 원소는 깊은 복사를 한다.
  3. 얕은 복사를 시도한다.
  4. Null로 대체한다.