本文源自本人的学习记录整理与理解,其中参考阅读了部分优秀的博客和书籍,尽量以通俗简单的语句转述。引用到的地方如有遗漏或未能一一列举原文出处还望见谅与指出,另文章内容如有不妥之处还望指教,万分感谢 !
拷贝语法在开发中经常用到,拷贝的目的也很简单:产生一个新的副本对象,跟原对象互不影响;
- 互不影响: 修改原对象不会影响副本对象,修改副本对象也不会影响原对象
在iOS中提供了两个拷贝方法:
copy
:不可变拷贝,产生不可变副本
不可变副本:不论拷贝对象是可变还是不可变,最终生成的都是不可变对象
;比如:NSString
、NSMutableString
拷贝生成的都是NSString
mutableCopy
: 可变拷贝,产生可变副本
可变副本: 不论拷贝对象是可变还是不可变,最终生成的都是可变对象
;
比如:NSString
、NSMutableString
拷贝生成的都是NSMutableString
深拷贝、浅拷贝
深拷贝:
内容拷贝
,不可变产生可变、可变产生不可变、可变产生可变;会生成新对象
- 调用copy时类型不同的才是深拷贝,调用mutableCopy时类型相不相同都是深拷贝
- 产生的副本对象存储在堆空间,因其为可变对象所占空间大小不固定
浅拷贝:
指针拷贝
,不可变产生不可变 ,两个指针指向同一个内存地址;不会生成新对象
- 产生的副本对象可能存储在栈空间、也可能存储在堆空间,因所占内存大小而定
- 指针拷贝:不可变对象本身就不可被修改,于是为了节省资源占用,所以就让其指向同一个对象,只拷贝其指针即可;
在OC对象中只有以下是浅拷贝:
[NSString copy]
[NSArray copy]
[NSDictionary copy]
总结:可变和不可变有四种组合,只有不可变产生不可变是浅拷贝,其它都是深拷贝;所以深拷贝和浅拷贝的明显区别就是:是否产生新对象
注意不要混淆 copy、mutableCopy
、 浅拷贝、深拷贝
copy和mutableCopy是两种不同的拷贝方式
浅拷贝和深拷贝是对这两种方式产生的结果划分
划分依据:是否产生新对象
图解
用生活中的场景来生动的描述深拷贝和浅拷贝
浅拷贝
小明和小鱼俩是邻居,一天小明发现小鱼有一个可以看时间的电子表很羡慕,回到家嚷嚷着要让家人也给弄一个电子表看时间,小明爸爸就去挑了一个和小鱼一样的给小明看时间,小明很开心!
深拷贝
小明和小鱼俩是邻居,一天小明发现小鱼有一个可以看时间的电子表很羡慕,可恶的是小鱼竟然说他的手表时最好的;委屈回到家嚷嚷着要让家人也给弄一个比小鱼的电子表更好的电子表,小明爸爸马上到城里找到那家店把除了小鱼那款的其他三款更好的都打包带回家给小明玩 ,小明很开心!
示例代码
NSString *str1 = [[NSString alloc] initWithFormat:@"test"];
NSString *str2 = [str1 copy]; // 浅拷贝,指针拷贝,没有产生新对象
NSMutableString *str3 = [str1 mutableCopy]; // 深拷贝,内容拷贝,有产生新对象
NSLog(@"%p %p %p", str1, str2, str3);
log输出
//str1: 0x8b12089136018f1b
//str2: 0x8b12089136018f1b
//str3: 0x1005992d0
从log输出可以看出
str1和str2地址相同,说明指向同一个对象,没有产生新对象;
str3的地址变了,说明和str1指向的不是同一个对象,有产生新对象;
疑问:不是说修改副本对象不会影响原对象吗 ?str1和str2地址相同,都没有产生新对象呀 ?
- str1和str2地址虽然相同,但依然产生了副本对象,只不过是副本对象和原对象相同而已
- 原因:
这是编译器优化的结果。编译器在编译时发现str1和str2的内容是相同常量字符串,会把它们都指向一个相同的区域,而不是再开辟出一块额外的空间。因此它们是相同的地址值。
这样的拷贝被称为浅拷贝,浅拷贝的特点就是不会生成新对象,是指针拷贝
如果让 str2 指向另一个字符串呢 ?会影响str1吗
str2指向另一个字符串,相当于改变了指针指向,对str1的值没有影响;
如下代码
NSString *str1 = [[NSString alloc] initWithFormat:@"test"];
NSString *str2 = [str1 copy]; // 浅拷贝,指针拷贝,没有产生新对象
NSMutableString *str3 = [str1 mutableCopy]; // 深拷贝,内容拷贝,有产生新对象
NSLog(@"%p %p %p", str1, str2, str3);
//str1: 0x8b12089136018f1b
//str2: 0x8b12089136018f1b
//str3: 0x1005992d0
str2 = [[NSString alloc] initWithFormat:@"逍遥侯"];
NSLog(@"%p %p", str1, str2);
//str1: 0x8b12089136018f1b
//str2: 0x10061f0a0
拷贝后产生的对象是不可变对象,这一定是浅拷贝吗 ?
- 不一定,产生不可变对象的情况并不唯一;原对象类型是可变和不可变都有可能
- 对一个可变对象拷贝和对一个不可变对象拷贝,产生的副本对象都是不可变对象;
- 对一个可变对象拷贝产生不可变对象,这叫深拷贝;因为副本对象和原对象类型不同
- 对一个不可变对象拷贝产生不可变对象,这叫浅拷贝;因为副本对象和原对象类型相同
拷贝后产生的对象是可变对象,这一定是深拷贝吗 ?
是的,因为只有调用mutableCopy才会产生可变副本对象,而mutableCopy返回的对象都是深拷贝
说的都是OC对象拷贝,那block能使用copy吗 ?如果可以是深拷贝还是浅拷贝 ?为什么
block可以被拷贝,block是指向结构体的指针,本质上是一个OC 对象,它的内部有个isa指针,和OC对象的底层结构相同;所以可以被拷贝
是深拷贝,一个被声明的block首先会被存放在栈上,对其拷贝后编译器会自动将栈上的block拷贝到堆上;这过程中生成新的对象,是属于内容拷贝,所以是深拷贝
拷贝语法使用有哪些使用注意点 ?
-
@property
声明属性时:
NSString 用copy修饰
NSArray 用copy修饰
NSDictionary 用copy修饰
NSMutableString 用strong修饰
NSMutableArray 用strong修饰
NSMutableDictionary 用strong修饰
字符串用copy修饰的原因:
可以保证不管外面传进来的是可变还是不可变,到我这里通通是不可变;这就确保了自身数据的独立,字符串通常是用来显示在屏幕上的,屏幕上显示的文字肯定不能相同,若想显示不同文字,就要保证相互不受影响;若外面想要改变显示出来的文字,需要再次赋值;不允许你改我就改,所以用copy最合适可变数组和可变字典不用copy修饰的原因:
在数据传递场景下NSMutableString
、NSMutableArray
和NSMutableDictionary
用 copy 后数据类型都发生变化了,这会造成很多潜在的问题,比如抛出异常方法找不到
的错误一个类如果想使用copy,需要遵循
NSCopying
协议,并实现copyWithZone:方法
- (id)copyWithZone:(nullable NSZone *)zone;
- 一个类如果想使用mutableCopy,需要遵循
NSMutableCopying
协议,并实现mutableCopyWithZone:方法
- (id)mutableCopyWithZone:(nullable NSZone *)zone;
为什么不能用
mutableCopy
修饰 ?
- 在属性声明时不存在mutableCopy关键字的,官方并未提供;
- 因为:属性声明的对象类型有很多种,mutableCopy是Foundation框架中只对某些特殊的类才可以使用,这些类都是遵循了拷贝协议
(NSCopying、NSMutableCopying)
的;所以不能
用mutableCopy修饰