Objective-C 浅拷贝与深拷贝:从踩坑到彻底搞懂

引言

"为什么改了外面的字符串,我模型里的值也跟着变了?"

"不是说 copy 就是深拷贝吗,怎么元素地址还是一样的?"

如果你也被这些问题困扰过,恭喜你,来对地方了。今天我们从代码验证出发,一次性把 Objective-C 的浅拷贝和深拷贝彻底讲清楚。

一、strong 修饰 NSString 的经典坑

先看一个生产环境里最常见的 bug:

objc 复制代码
@interface Person : NSObject
@property (nonatomic, strong) NSString *name;  // ❌ 错误的写法
@end

// 使用场景
NSMutableString *mutableName = [NSMutableString stringWithString:@"张三"];
self.person.name = mutableName;

[mutableName appendString:@"(已修改)"];

NSLog(@"%@", self.person.name);  // "张三(已修改)" --- 内部值被污染了!

为什么?

css 复制代码
strong 修饰后的内存模型:

mutableName  ──→ [NSMutableString "张三"]  ←── person.name
               (同一个对象,同一块内存)

外部 appendString → 内部也跟着变了

解决方案

objc 复制代码
@property (nonatomic, copy) NSString *name;  // ✅ 标准写法
objectivec 复制代码
copy 修饰后的内存模型:

mutableName  ──→ [NSMutableString "张三"]
person.name  ──→ [NSString "张三"]          ← 独立不可变副本

外部修改 → 内部不受影响

这就是 copystrong 的本质区别:strong 共享对象,copy 创建独立副本。


二、copy vs mutableCopy:容器层面的行为

很多人以为这两个方法差不多,实际上它们的区别非常明确:

代码验证

objc 复制代码
// ========== 不可变源 ==========
NSArray *immutable = @[@"A", @"B", @"C"];

NSArray *copyResult = [immutable copy];           // 地址相同!Foundation 优化
NSMutableArray *mutableCopyResult = [immutable mutableCopy];  // 地址不同

// ========== 可变源 ==========
NSMutableArray *mutable = [NSMutableArray arrayWithObjects:@"A", @"B", nil];

NSArray *copyResult2 = [mutable copy];            // 地址不同!创建新不可变容器
NSMutableArray *mutableCopyResult2 = [mutable mutableCopy]; // 地址不同

��为矩阵

源对象 copy mutableCopy
不可变 (NSArray) 返回自身(retain),地址相同 NSMutableArray,地址不同
可变 (NSMutableArray) NSArray,地址不同 NSMutableArray,地址不同

核心结论:

  • copy 始终返回不可变对象
  • mutableCopy 始终返回可变对象
  • 对不可变源 copy 是优化过的,直接 retain,不创建新对象

三、浅拷贝 vs 深拷贝:元素层面才是关键

容器地址不同 ≠ 深拷贝。浅拷贝和深拷贝的核心区别在于元素是否被拷贝。

浅拷贝验证

objc 复制代码
NSMutableArray *original = [NSMutableArray arrayWithObjects:@"A", @"B", @"C", nil];
NSArray *shallowCopy = [original copy];

NSLog(@"容器: %p vs %p", original, shallowCopy);   // 不同 ✅
NSLog(@"元素: %p vs %p", original[0], shallowCopy[0]);  // 相同  ← 浅拷贝!

内存模型:

css 复制代码
original      ──→ [容器A] ──→ "A" ←── [容器B] ←── shallowCopy
                              → "B" ←─── ↑ 共享
                              → "C" ←─── ↑ 共享

结论:copymutableCopy 对容器元素都是浅拷贝,元素指针共享。


四、真正让元素地址不同的方法

三种常见的"深拷贝"写法:

objc 复制代码
NSMutableString *a = [NSMutableString stringWithString:@"A"];
NSMutableString *b = [NSMutableString stringWithString:@"B"];
NSMutableString *c = [NSMutableString stringWithString:@"C"];
NSArray *original = @[a, b, c];

// 方法1:归档解档
NSArray *deepCopy = [NSKeyedUnarchiver unarchiveObjectWithData:
                     [NSKeyedArchiver archivedDataWithRootObject:original]];

// 方法2:copyItems:YES
NSArray *deepCopy1 = [[NSArray alloc] initWithArray:original copyItems:YES];

// 方法3:手动递归
NSMutableArray *deepCopy2 = [NSMutableArray array];
for (id obj in original) {
    [deepCopy2 addObject:[obj copy]];
}

为什么之前验证时元素地址相同?

因为之前用的是 @"A"(不可变 NSString),它的 copy 就是返回自身:

objc 复制代码
NSString *str = @"A";
NSString *copied = [str copy];
NSLog(@"%p vs %p", str, copied);  // 相同!不可变对象 copy = retain

改成可变元素后,地址果然不同:

objc 复制代码
NSLog(@"元素: %p VS %p VS %p VS %p",
      original[0], deepCopy[0], deepCopy1[0], deepCopy2[0]);
// 元素: 0x600002a00010 VS 0x600002a00030 VS 0x600002a00050 VS 0x600002a00070
// 全部不同!✅

// 验证数据隔离
[a appendString:@"(被篡改)"];
NSLog(@"%@", original[0]);   // "A(被篡改)"  不受影响
NSLog(@"%@", deepCopy[0]);   // "A"             不受影响  ✅

五、正确的"深拷贝"心智模型

讨论深拷贝时,需要准确定义"深度":

层次 含义 示例
一层深拷贝 对第一层可变元素调用 copy,创建不可变副本 copyItems:YES
完全深拷贝 递归处理所有嵌套层级 归档解档

核心认知:

"深"不是要求所有元素都必须新建,而是对可变元素 创建独立副本,防止外部修改影响内部。不可变元素本身不会被意外修改,copy 返回自身既安全又高效。


六、三种深拷贝方式的对比

方式 深度 要求 性能 适用场景
copyItems:YES 一层 元素实现 NSCopying 较快 一层嵌套足够
手动递归 可控 自己控制深度 灵活 需要自定义逻辑
归档解档 完全 所有元素实现 NSCoding 最慢 需要完全独立副本

七、总结

概念 一句话记忆
strong vs copy 需要保护内部数据的属性用 copy(NSString/NSArray等)
copy vs mutableCopy copy 返回不可变,mutableCopy 返回可变
浅拷贝 新容器 + 旧元素(指针共享)
深拷贝 新容器 + 对可变元素创建不可变副本
相关推荐
2501_915921431 天前
从零开始学 Swift iOS 开发 iOS应用入门
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
白玉cfc3 天前
熟悉Objective-C
开发语言·ios·objective-c
鹤卿1234 天前
谓词与正则表达式(OC)
ios·正则表达式·objective-c·xcode
秋雨梧桐叶落莳4 天前
iOS——3GShare项目总结
学习·macos·ios·objective-c·cocoa
pop_xiaoli7 天前
【iOS】YYModel源码学习
学习·ios·objective-c·cocoa
末代iOS程序员华仔7 天前
iOS 开发到上架 App Store 全流程详解
ios·objective-c·swift
pop_xiaoli8 天前
【iOS】离屏渲染
macos·ios·objective-c·cocoa
唐诺15 天前
Objective-C 之 KVC 详解
ios·objective-c·kvc
00后程序员张15 天前
iOS 打包方式汇总 从 Xcode Archive 到轻量级工具链的 IPA 构建
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程