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 "张三"]          ← 独立不可变副本

外部修改 → 内部不受影响

这就是 copy 和 strong 的本质区别: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" ←─── ↑ 共享

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


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

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

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 返回可变
浅拷贝 新容器 + 旧元素(指针共享)
深拷贝 新容器 + 对可变元素创建不可变副本
相关推荐
庚鑫12 小时前
iOS 27 兼容放大模式下,keyboardFrame 与 UIScreen.bounds 的坐标差异
ios·objective-c
sakiko_19 小时前
纯Appdelegate控制的老项目适配Xcode27(iOS27SDK)
macos·objective-c·cocoa
andyweike8 天前
iOS语法原理
macos·objective-c·cocoa
青柠之夏cc10 天前
Kotlin Multiplatform:一次编写,全平台运行,2026 年跨平台开发的 “最优解”?
macos·objective-c·cocoa
黑科技iOS上架11 天前
iOS framework/dylib动态库定向加固实战
macos·ios·objective-c·cocoa·审核·ios混淆·执行程序差异化
程序鉴定师14 天前
2026年深圳小程序/App开发公司技术选型指南:从架构设计到交付标准全维度评估
java·小程序·uni-app·php·objective-c
鹤卿12316 天前
「iOS」GCD
开发语言·ios·objective-c
用户21816970493017 天前
FFmpeg (二) shell echo 数组
objective-c
2501_9159090623 天前
移动端开发工具链怎么组合比较好,iOS、Android、跨端三套配置方案
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程
2501_9159184123 天前
iOS编程用什么软件好?主流工具Xcode、AppCode、CodeRunner与新兴快蝎对比
ide·vscode·ios·objective-c·个人开发·swift·敏捷流程