借助AI辅助。
1. 核心应用场景
我们主要在以下两种情况使用 copy 关键字:
A. 修饰具有“可变子类”的不可变对象
最典型的是 NSString, NSArray, NSDictionary。
- 为什么? 为了 “防御性编程”。确保属性的封装性,防止外部修改影响内部逻辑。
- 例子:
如果你用@property (nonatomic, copy) NSString *name;strong修饰name,当外部传进来一个NSMutableString时,由于多态性是允许的。但如果外部修改了这个NSMutableString,你对象内部的name也会莫名其妙地变了,这违反了封装原则,极易导致 Bug。使用copy可以确保我们持有一个不可变的副本。
B. Block 属性
- 为什么? 在 MRC 时代,Block 默认是在栈上的,出了作用域就会销毁。使用
copy会将 Block 从栈拷贝到堆上,保证其生命周期。 - 现状:在 ARC 环境下,使用
strong或copy修饰 Block 其实效果是一样的(编译器会自动进行 copy 操作),但为了遵循传统习惯和显式表达意图,我们依然习惯用copy。
2. 深拷贝 vs 浅拷贝(Deep Copy vs Shallow Copy)
那 “copy 和 mutableCopy 到底做了什么?”
| 源对象类型 | 调用方法 | 结果对象类型 | 拷贝类型 | 备注 |
|---|---|---|---|---|
| 不可变对象 (NSString) | copy | 不可变 | 浅拷贝 (Pointer Copy) | 指针指向原地址,引用计数+1。因为原对象本就不可变,无需浪费内存复制。 |
| 不可变对象 (NSString) | mutableCopy | 可变 (NSMutableString) | 深拷贝 (Content Copy) | 复制了内容到新内存地址。 |
| 可变对象 (NSMutableString) | copy | 不可变 (NSString) | 深拷贝 (Content Copy) | 必须复制,否则原对象变了新对象也会变。 |
| 可变对象 (NSMutableString) | mutableCopy | 可变 (NSMutableString) | 深拷贝 (Content Copy) | 复制了内容到新内存地址。 |
总结口诀:
只有 “不可变对象 copy” 是浅拷贝(指针拷贝),其他情况全都是深拷贝(内容拷贝)。
copy返回的永远是不可变对象,mutableCopy返回的永远是可变对象。
3. 集合类的特殊情况(单层深拷贝)
那 “如果我对一个 NSArray 进行了 copy,里面的元素会被拷贝吗?”
回答:
- 默认的
copy或mutableCopy对集合类(Array/Dictionary)都是单层深拷贝(One-level deep copy)。 - 意思是:数组这个容器本身被复制了(新的地址),但是数组里面的元素依然是指针拷贝(指向原来的对象)。
- 如何实现完全深拷贝?
- 使用
initWithArray:copyItems:YES(前提是元素实现了NSCopying协议)。 - 归档解档(Archive/Unarchive):将对象序列化成二进制再读回来,实现完全彻底的深拷贝。
- 使用
4. 自定义对象如何支持 copy?
如果你有一个自定义的 Person 类,想让它支持 copy,需要做两件事:
- 遵守
<NSCopying>协议。 - 实现
copyWithZone:方法。
- (id)copyWithZone:(NSZone *)zone {
Person *p = [[Person allocWithZone:zone] init];
p.name = self.name;
p.age = self.age;
return p;
}