iOS 中的浅拷贝(Shallow Copy)和深拷贝(Deep Copy)

178 阅读4分钟

好的,让我们更详细地探讨 iOS 中的浅拷贝和深拷贝,包括底层实现和源码分析。这里我们将分别探讨不可变对象和可变对象的拷贝,以及如何实现自定义对象的深拷贝。

浅拷贝(Shallow Copy)

浅拷贝只是复制对象的引用,而不复制其内容。这意味着新对象和原对象实际上指向同一个内存地址。对于不可变对象(如 NSStringNSArray),浅拷贝和深拷贝可能表现得一样,因为不可变对象的内容不会改变。

NSMutableArray 的浅拷贝

NSMutableArray *originalArray = [NSMutableArray arrayWithObjects:@"A", @"B", @"C", nil];
NSMutableArray *shallowCopyArray = [originalArray copy];

这里 copy 方法实际上调用了 copyWithZone: 方法。

- (id)copyWithZone:(NSZone *)zone {
    return NSCopyObject(self, 0, zone);
}

NSCopyObject 是一个低级别的函数,用于创建对象的浅拷贝。对于不可变对象,如 NSArrayNSString 等,copy 方法返回的是同一个对象的引用。

id NSCopyObject(id obj, size_t extraBytes, NSZone *zone) {
    if (obj == nil) return nil;
    
    // Perform a shallow copy of the object
    id result = objc_duplicate(obj, extraBytes, zone);
    
    return result;
}

objc_duplicate 是一个底层函数,用于创建对象的浅拷贝。在更高层次上,Objective-C 的对象拷贝依赖于这个或类似的底层机制。由于 objc_duplicate 是一个私有函数,Apple 的公开文档并没有详细说明它的实现,但我们可以根据已知的 Objective-C 运行时库来推测其实现逻辑。

objc_duplicate 的假设实现

objc_duplicate 是一个底层函数,用于复制对象的内存。这个函数可能涉及以下几个步骤:

  1. 分配内存:为新对象分配内存。
  2. 复制内容:将原对象的内容复制到新对象中。
  3. 设置引用计数:初始化新对象的引用计数。

假设的 objc_duplicate 实现可能如下:

c
id objc_duplicate(id obj, size_t extraBytes, NSZone *zone) {
    // 获取对象的类信息
    Class cls = object_getClass(obj);
    
    // 计算对象的大小
    size_t size = class_getInstanceSize(cls) + extraBytes;
    
    // 分配内存
    id newObj = (id)calloc(1, size);
    
    // 复制对象的内容
    memcpy(newObj, obj, size);
    
    // 设置对象的类
    object_setClass(newObj, cls);
    
    // 初始化引用计数(假设引用计数在对象头部)
    if (newObj) {
        objc_retain(newObj);
    }
    
    return newObj;
}

深拷贝(Deep Copy)

深拷贝会复制对象及其包含的所有子对象,还需要递归地复制其包含的所有子对象,生成一个全新的对象。深拷贝通常通过归档(Archiving)和解档(Unarchiving)实现。

NSMutableArray 的深拷贝

NSMutableArray *originalArray = [NSMutableArray arrayWithObjects:@"A", @"B", @"C", nil];
NSMutableArray *deepCopyArray = [[NSMutableArray alloc] initWithArray:originalArray copyItems:YES];

这里 initWithArray:copyItems: 方法的实现涉及到对每个元素进行深拷贝。

- (instancetype)initWithArray:(NSArray *)array copyItems:(BOOL)flag {
    self = [self initWithCapacity:[array count]];
    if (self) {
        for (id obj in array) {
            id copyObj = flag ? [obj copy] : obj;
            [self addObject:copyObj];
            if (flag) [copyObj release];
        }
    }
    return self;
}

归档和解档实现深拷贝

对于自定义对象,通常需要实现 NSCoding 协议,以支持归档和解档,从而实现深拷贝。

自定义对象实现深拷贝

@interface CustomObject : NSObject <NSCoding, NSCopying>
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSMutableArray *items;
@end

@implementation CustomObject

// NSCoding协议中的方法
- (void)encodeWithCoder:(NSCoder *)coder {
    [coder encodeObject:self.name forKey:@"name"];
    [coder encodeObject:self.items forKey:@"items"];
}

- (instancetype)initWithCoder:(NSCoder *)coder {
    self = [super init];
    if (self) {
        _name = [coder decodeObjectForKey:@"name"];
        _items = [coder decodeObjectForKey:@"items"];
    }
    return self;
}

// NSCopying协议中的方法
- (id)copyWithZone:(NSZone *)zone {
    CustomObject *copy = [[[self class] allocWithZone:zone] init];
    copy.name = [self.name copy];
    copy.items = [[NSMutableArray alloc] initWithArray:self.items copyItems:YES];
    return copy;
}
@end

使用归档和解档进行深拷贝

CustomObject *originalObject = [[CustomObject alloc] init];
originalObject.name = @"Original";
originalObject.items = [NSMutableArray arrayWithObjects:@"Item1", @"Item2", nil];

// 深拷贝
NSData *archivedData = [NSKeyedArchiver archivedDataWithRootObject:originalObject];
CustomObject *deepCopyObject = [NSKeyedUnarchiver unarchiveObjectWithData:archivedData];

// 修改原始对象
originalObject.name = @"Modified";
[originalObject.items addObject:@"Item3"];

// 输出结果
NSLog(@"Original Object: %@", originalObject.items); // 输出: Original Object: (Item1, Item2, Item3)
NSLog(@"Deep Copy Object: %@", deepCopyObject.items); // 输出: Deep Copy Object: (Item1, Item2)

小结

浅拷贝:

  • 浅拷贝只复制对象的引用,原对象和新对象共享相同的内存地址。
  • 实现方式:通常通过 copy 方法实现。
  • 不可变对象的浅拷贝会返回同一对象的引用。

深拷贝:

  • 深拷贝会复制对象及其包含的所有子对象,生成一个全新的对象。
  • 实现方式:通常通过归档(Archiving)和解档(Unarchiving)实现,或者通过自定义的拷贝方法。
  • 自定义对象需要实现 NSCodingNSCopying 协议以支持深拷贝。

通过深入理解浅拷贝和深拷贝的底层实现,可以更有效地管理内存和处理对象操作,避免因引用共享而导致的意外修改。