深拷贝和浅拷贝都是针对复合类型(对象,结构体指针)
一、浅拷贝和深拷贝
浅拷贝
新对象是拷贝了原对象的地址(引用),指向的还是原来的对象,修改其中一个另一个也会跟着改变深拷贝
新对象是开辟了了一块新的地址,然后把原对象的值赋值给新对象,修改任意一个不会影响另一个对象(这也是区分浅拷贝和深拷贝地方)
苹果官方文档中的图解

二、copy和mutableCopy
copy和mutableCopy是foundation框架下的两种拷贝方式;
copy:返回一个不可变对象:
mutableCopy:返回一个可变对象
分别对应如下两个方法
需要分别遵守两个协议NSCopying,NSMutableCopying然后实现:
- (nonnull id)copyWithZone:(nullable NSZone *)zone {
//...
}
- (nonnull id)mutableCopyWithZone:(nullable NSZone *)zone {
//...
}
不然直接调用会crash,好在常用的字符串,数组等 苹果帮我门实现了这两个协议.
copy和mutableCopy分为可变和不可变,容器和非容器的情况
三、容器类,非容器类
容器是指实例为其他类的对象的集合的类,否则就是非容器类
iOS中的容器类包含:
NSArray,NSDictionary,NSSet
以及他们的可变版本
非容器类除了以上几种之外的都是非容器类
NSString
四、可变和不可变
大多数可可对象都是可变的,这意味着您可以更改其封装的值,但是有些是不可变的,并且在创建它们之后就无法更改其封装的值。不可变对象的主要好处是可以确保在使用它们时它们的值不会改变。一旦创建了一个不可变的对象,该对象表示的值在其整个运行期间将保持不变。但是您可以随时更改可变对象的封装值。您可以通过替换值的方法(“ setter”访问器方法)或以增量方式修改它们的方法来执行此操作

在iOS中主要可变和不可变是针对容器类,和字符串,data而言的:
可变类如下:
NSMutableArray,NSMutableDictionary,NSMutableSet,NSMutableString...
可变:可变类都可以进行对应的集合操作(增,删,改,);
例如:数组
- (void)addObject:(ObjectType)anObject;//追加
- (void)insertObject:(ObjectType)anObject atIndex:(NSUInteger)index;//插入
- (void)removeLastObject;//删除最后一个
- (void)removeObjectAtIndex:(NSUInteger)index;//删除对应下标元素
- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(ObjectType)anObject;//修改
......
不可变类不能进行以上对应的集合操作,执行相关操作会crash
不可变数组NSArray,有追加操作,但是追加之后产生一个新数组原来的数组还是没变
- (NSArray<ObjectType> *)arrayByAddingObject:(ObjectType)anObject;
五、copy和mutableCopy对应的几种情况:
1.1 不可变 - 非容器类
对可变字符串str1,分别copy, mutableCopy
NSString *str1 = @"非容器类不可变";//常量字符串
NSString *str2 = str1.copy;//浅拷贝-常量字符串
NSString *str3 = str1.mutableCopy;//深拷贝-可变字符串
输出:
str1:0x100002040,class:__NSCFConstantString//常量不可变字符串
str2:0x100002040,class:__NSCFConstantString//常量不可变字符串
str3:0x102300100,class:__NSCFString//可变字符串
结论:
copy本来就是产生一个不可变对象, str1被 copy之后产生的对象str2指向str1(浅拷贝),因为str1和str2都不能修改
mutableCopy要产生一个可变对象,但是原来的对象str1不可变,所以只能产生一个新的可变对象str3(深拷贝)
非容器-不可变对象 用copy的结果:发生浅拷贝,拷贝的对象不可变
非容器-不可变对象 用mutableCopy的结果:发生深拷贝,深拷贝的对象可变
1.2 可变-非容器类
对可变字符串mutStr1,分别copy, mutableCopy
NSMutableString *mutStr1 = [NSMutableString stringWithString:@"非容器类可变"];//可变字符
NSMutableString *mutStr2 = [mutStr1 copy];//深拷贝-不可变字符串
NSMutableString *mutStr3 = [mutStr1 mutableCopy];//深拷贝-可变字符串
输出:
mutStr1:0x100602040,class:__NSCFString //深拷贝-可变
mutStr2:0x100601180,class:__NSCFString //深拷贝-不可变
mutStr3:0x100600630,class:__NSCFString //深拷贝-可变
copy 产生一个不可变对象,mutStr1是一个可变字符串copy之后的mutStr2要不可变只能产生一个新的对象让其不可变(深拷贝)
对mutStr2执行可变操作会crash
'Attempt to mutate immutable object with appendString:'
mutableCopy 产生一个可变对象,mutStr1是一个可变字符串mutableCopy之后的mutStr3可变, 生成一个新的可变对象
结论:
非容器-可变对象 调用copy 后发生深拷贝,拷贝的对象不可变
非容器-可变对象 调用mutableCopy 后发生深拷贝,拷贝的对象可变
2.1容器类-不可变
对不可变数组array1,分别copy, mutableCopy
NSArray *array1 = @[@"1",@"2",@"3"];//不可变数组
NSArray *array2 = array1.copy;//浅拷贝,不可变数组
NSArray *array3 = array1.mutableCopy;//深拷贝,可变数组
输出:
array1:0x100748b00,class:__NSArrayI //浅拷贝-不可变
array2:0x100748b00,class:__NSArrayI //浅拷贝-不可变
array3:0x100748bd0,class:__NSArrayM //深拷贝-可变
分析过程同上:
容器类中保存的也是对象,这些对象也会跟着变为对应的浅拷贝和深拷贝吗?
for (id item in array1) {
NSLog(@"array1-%p",item);
}
for (id item in array2) {
NSLog(@"array2-%p",item);
}
for (id item in array3) {
NSLog(@"array3-%p",item);
}
输出:
原对象
array1-0x100002048 //1
array1-0x100002068 //2
array1-0x100002088 //3
copy的对象
array2-0x100002048 //copy-1
array2-0x100002068 //copy-2
array2-0x100002088 //copy-3
mutableCopy的对象
array3-0x100002048 //mutableCopy-1
array3-0x100002068 //mutableCopy-2
array3-0x100002088 //mutableCopy-3
分析:原对象array1,copy对象array2,mutableCopy对象array3 数组保存的对象地址都是一样的,所以不可变容器的二层拷贝都是浅拷贝的结果
结论:
容器类-不可变对象:调用copy 发生浅拷贝,拷贝的对象不可变
容器类-不可变对象:调用mutableCopy 发生深拷贝,拷贝的对象可变
容器里的对象都是浅拷贝的
2.2容器类-可变
对可变数组array1,分别copy, mutableCopy
NSMutableArray *mutArray1 = [NSMutableArray arrayWithArray:@[@"1",@"2",@"3"]];//可变
NSMutableArray *mutArray2 = mutArray1.copy;//深拷贝-不可变
NSMutableArray *mutArray3 = mutArray1.mutableCopy;//深拷贝-可变
输出:
mutArray1:0x102d00200,class:__NSArrayM //深拷贝-可变
mutArray2:0x102d00310,class:__NSArrayI //深拷贝-不可变
mutArray3:0x102d00350,class:__NSArrayM //深拷贝-可变
分析同上:
通过打印各个对象的地址,数组内的对象都是浅拷贝(代码同上),看起来apple没有考虑容器的二层拷贝情况
结论:
容器类-可变:调用copy深拷贝 拷贝后产生的对象不可变
容器类-可变:调用mutableCopy深拷贝 拷贝后产生的对象可变
容器里的元素当然也是浅拷贝的,验证方法和2.1.1是一样的
可变和不可变对象之间发生拷贝是会创建新对象,所以会深拷贝
把以上分析结果整理成表格:
| - | 非容器-不可变对象 | 非容器-可变对象 | 容器-不可变对象 | 容器-可变对象 |
|---|---|---|---|---|
| copy | 浅拷贝-不可变 | 深拷贝-不可变 | 浅拷贝-不可变 | 深拷贝-不可变 |
| mutableCopy | 深拷贝-可变 | 深拷贝-可变 | 深拷贝-可变 | 深拷贝-可变 |
3.1. 自定义对象支持copy, mutableCopy
initWithArray: copyItems:会使NSArray中元素均执行copy方法,如果是未实现NSCopying定义的方法,就会发生Crash
///比如NSArray, flag参数为YES将执行下层深拷贝
- (instancetype)initWithArray:(NSArray<ObjectType> *)array copyItems:(BOOL)flag;
NSCopying和NSMutableCopying都只有一个方法,实现如下:
- (nonnull id)copyWithZone:(nullable NSZone *)zone {
id copyObject = [[self class] allocWithZone:zone];
//如果有对应的属性,还需要对属性赋值
//...
return copyObject;
}
比较省事的写法:
- (nonnull id)copyWithZone:(nullable NSZone *)zone {
id copyInstance = [[self class] allocWithZone:zone];
//runtime 需要导入runtime库
size_t instanceSize = class_getInstanceSize([self class]);
memcpy((__bridge void *)(copyInstance), (__bridge const void *)(self), instanceSize);
return copyInstance;
}
在Objective-C中,copy和mutableCopy方法由所有对象继承,用于执行复制;后者用于创建原始对象的可变类型。这些方法依次调用copyWithZone和mutableCopyWithZone方法来执行复制。对象必须实现相应的copyWithZone方法才能进行复制。
关于容器的浅拷贝和深拷贝苹果给的一张图解:

六、copy修饰符
6.1 用copy修饰NSMutableArray的情况
@property(nonatomic,copy)NSMutableArray *array;
默认起况下通过set方法赋值的时候,系统会在底层执行copy的行为,copy会产生一个不可变的深拷贝对象,所以我们在对数组array进行删除,修改,插入等操作使就会异常;
copy修饰的属性,赋值使系统会执行copy行为,参考底层源码:
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
if (offset == 0) {
object_setClass(self, newValue);
return;
}
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:nil];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:nil];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spinlock_t& slotlock = PropertyLocks[slot];
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
objc_release(oldValue);
}
解决办法有:
- 重写set方法,
- 懒加载初始化不要调用copy
- 用strong替换copy修饰符