11-主题|内存管理@iOS-深浅拷贝与内存

8 阅读6分钟

本文介绍 浅拷贝(Shallow Copy)深拷贝(Deep Copy) 的含义、在 Objective-C / Foundation 中的表现、与内存的关系(引用计数、新对象分配、共享与独立),以及 NSCopying、属性 copy、Swift 值类型与写时拷贝。前置知识见 03-引用计数与MRC详解04-ARC详解


一、浅拷贝与深拷贝的定义

1.1 概念

类型含义内存上的表现
浅拷贝只复制「当前这一层」:得到一个新对象(新指针),但对象内部的元素/子对象仍指向原有的实例。新对象占用新内存(新引用计数);内部元素复制,多出一份对原元素的引用(引用计数 +1)。
深拷贝递归复制整棵对象树:当前对象及其内部所有引用到的对象都重新创建一份。整棵对象树都占用新内存,拷贝前后完全独立,无共享引用。
  • 单层对象(如 NSString、NSData):浅拷贝与深拷贝在「是否共享内容」上的差异,取决于类型是否可变、实现是否共享底层存储(如 copy 后可能共享 buffer,仅引用计数 +1)。
  • 集合类(NSArray、NSDictionary 等):浅拷贝 = 新容器 + 元素仍指向原元素;深拷贝 = 新容器 + 对每个元素再递归 copy,需自行实现或使用 initWithArray:copyItems:YES 等 API。

1.2 与内存、引用计数的关系

  • 浅拷贝:生成一个新对象(容器或包装类),该对象对内部子对象的引用会使这些子对象的引用计数 +1;子对象本身不复制,内存上共享子对象。
  • 深拷贝:生成全新的对象图,每个被拷贝的对象都有新内存、新引用计数;原对象与拷贝无共享,释放一方不影响另一方。

二、Foundation 中的 copy 与 mutableCopy

2.1 常见类型的拷贝语义(概览)

类型copymutableCopy说明
NSString不可变副本(可能共享存储,引用计数 +1)NSMutableString不可变 → 不可变 多为浅拷贝;不可变 → 可变 会分配新缓冲
NSMutableString不可变 NSString(新内存)NSMutableString(浅拷贝)copy 得到不可变,防止外部修改
NSArray浅拷贝,新数组、元素仍指向原元素NSMutableArray(浅拷贝)元素引用计数 +1,元素本身不复制
NSDictionary浅拷贝NSMutableDictionary(浅拷贝)同上
NSData浅拷贝(可能共享字节)NSMutableData实现可能共享底层 buffer
自定义类copyWithZone: 实现决定mutableCopyWithZone: 决定可做浅拷贝或深拷贝
  • 上述「浅拷贝」指:容器是新对象,元素仍是原对象引用;对容器增删不影响对方,对元素内容的修改可能影响对方(若元素可变)。

2.2 集合的「单层深拷贝」

  • [[NSArray alloc] initWithArray:array copyItems:YES]:会向每个元素发送 copy,得到新数组 + 一层新元素;若元素本身是集合,其内部不会递归 copy,因此是单层深拷贝,不是递归深拷贝。
  • 真正递归深拷贝需自己实现或使用序列化(如 NSKeyedArchiver)再反序列化,注意性能与内存。

三、NSCopying 与 NSMutableCopying

3.1 协议

  • NSCopying:实现 - (id)copyWithZone:(NSZone *)zone;调用 [obj copy] 时最终走 copyWithZone:
  • NSMutableCopying:实现 - (id)mutableCopyWithZone:(NSZone *)zone;调用 [obj mutableCopy] 时走 mutableCopyWithZone:

3.2 拷贝与内存管理

  • ARC:copy/mutableCopy 返回的对象由调用方持有(引用计数 +1),遵循 ARC 规则。
  • MRC:返回的对象为调用方拥有,需在适当时机 release 或 autorelease;在 copyWithZone: 里返回的对象应为 +1 所有权(alloc 或 copy 出来的)。

3.3 自定义类的浅拷贝与深拷贝示例(概念)

// 浅拷贝:新对象,但 property 仍指向原对象(retain/copy 使引用计数 +1)
- (id)copyWithZone:(NSZone *)zone {
    MyClass *copy = [[MyClass allocWithZone:zone] init];
    copy.name = self.name;           // 若 name 是 copy 属性,会 [self.name copy]
    copy.child = self.child;         // 若 child 是 strong,仅 retain,共享同一 child
    return copy;
}

// 深拷贝:递归复制子对象
- (id)copyWithZone:(NSZone *)zone {
    MyClass *copy = [[MyClass allocWithZone:zone] init];
    copy.name = [self.name copy];
    copy.child = [self.child copy];  // 子对象也 copy,完全独立
    return copy;
}
  • 选择浅拷贝还是深拷贝取决于业务:共享子对象可省内存但需注意多线程/可变性;完全独立则省心但内存与耗时更大。

四、属性的 copy 与内存

4.1 copy 属性

  • 声明为 @property (copy) NSString *name 时,setter 会对传入值调用 copy,即持有的是「传入对象的拷贝」的所有权;若传入的是 NSMutableString,拷贝后得到不可变 NSString,避免外部在别处修改导致当前实例被意外改动。
  • Block 使用 copy 属性:Block 的 copy 会把栈 Block 拷贝到堆(见 10-Block内存管理),并持有该堆 Block;与「深浅拷贝」中的「拷贝」语义不同,但都涉及「新对象 + 引用计数」。

4.2 深浅拷贝与属性

  • 若属性是 集合(如 NSArray),用 copy 只是对集合本身做浅拷贝(新容器、元素仍共享);若希望「外部传入的数组」与内部完全隔离,要么接受浅拷贝(元素共享),要么在 setter 里做一层 initWithArray:copyItems:YES 或自定义深拷贝,并注意内存与性能。

五、内存注意与选型

5.1 浅拷贝

优点缺点
省内存、速度快与原对象共享子对象;若子对象可变,一边修改会影响另一边;多线程需额外同步

5.2 深拷贝

优点缺点
完全独立,无共享,线程安全更易控制内存与 CPU 开销大,递归深拷贝需防循环引用与栈溢出

5.3 何时用哪种

  • 浅拷贝:只关心「多一份容器引用」、元素共享可接受(或元素不可变)时;Foundation 的 copy/mutableCopy 默认多为浅拷贝(容器层)。
  • 深拷贝:需要「完全独立副本」、避免外部修改或跨线程共享可变状态时;可单层深拷贝(copyItems:YES)或自定义递归深拷贝。

六、流程图:浅拷贝与深拷贝的内存关系(概念)

flowchart TB
    subgraph 浅拷贝
        A1[原容器] --> A2[新容器]
        A1 --> A3[元素a]
        A2 --> A3
    end
    subgraph 深拷贝
        B1[原容器] --> B2[新容器]
        B1 --> B3[元素a]
        B2 --> B4[元素a 的副本]
    end

七、Swift 中的「拷贝」与内存

7.1 值类型与引用类型

  • 值类型(struct、enum、基础类型):赋值与传参是拷贝语义(复制一份值);从「不共享同一块堆对象」的角度看,更像「深拷贝」。
  • 引用类型(class):赋值与传参是引用,不产生新对象,仅多一个指针;若要独立副本需显式实现拷贝(如实现 NSCopying 或自定义 copy() 方法)。

7.2 写时拷贝(Copy-on-Write)

  • Array、Dictionary、Set 等是值类型,但底层存储可能共享 buffer;修改时才复制一份(Copy-on-Write),既保证值语义又减少不必要的内存与拷贝开销。
  • 与 OC 的「浅拷贝」不同:Swift 集合的「拷贝」在未修改前可能共享存储,修改时再分配新内存,由标准库保证语义正确。COW 原理、Swift 实现要点(如 isKnownUniquelyReferenced)及与内存的关系见 12-Option与内存优化技术 中的「Copy-on-Write」一节。

八、思维导图:深浅拷贝与内存

mindmap
  root((深浅拷贝与内存))
    概念
      浅拷贝 新对象 共享元素
      深拷贝 新对象 递归复制
    引用计数
      浅拷贝 元素 rc+1
      深拷贝 全新对象图
    Foundation
      copy mutableCopy
      集合 copyItems
    NSCopying
      copyWithZone
      自定义浅/深拷贝
    属性 copy
      setter 调 copy
      Block NSString
    Swift
      值类型 拷贝语义
      CopyOnWrite

九、参考文献