2. 怎么用 copy 关键字?

1 阅读2分钟

借助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 环境下,使用 strongcopy 修饰 Block 其实效果是一样的(编译器会自动进行 copy 操作),但为了遵循传统习惯和显式表达意图,我们依然习惯用 copy

2. 深拷贝 vs 浅拷贝(Deep Copy vs Shallow Copy)

那 “copymutableCopy 到底做了什么?”

源对象类型调用方法结果对象类型拷贝类型备注
不可变对象
(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,里面的元素会被拷贝吗?”

回答:

  • 默认的 copymutableCopy 对集合类(Array/Dictionary)都是单层深拷贝(One-level deep copy)。
  • 意思是:数组这个容器本身被复制了(新的地址),但是数组里面的元素依然是指针拷贝(指向原来的对象)。
  • 如何实现完全深拷贝?
    1. 使用 initWithArray:copyItems:YES(前提是元素实现了 NSCopying 协议)。
    2. 归档解档(Archive/Unarchive):将对象序列化成二进制再读回来,实现完全彻底的深拷贝。

4. 自定义对象如何支持 copy?

如果你有一个自定义的 Person 类,想让它支持 copy,需要做两件事:

  1. 遵守 <NSCopying> 协议。
  2. 实现 copyWithZone: 方法。
- (id)copyWithZone:(NSZone *)zone {
    Person *p = [[Person allocWithZone:zone] init];
    p.name = self.name;
    p.age = self.age;
    return p;
}

参考文章

  1. 怎么用 copy 关键字?