对象拷贝有两种方式:浅复制和深复制。顾名思义,浅复制,并不拷贝对象本身,仅仅是拷贝指向对象的指针;深复制是直接拷贝整个对象内存到另一块内存中
浅复制(shallow copy):在浅复制操作时,对于被复制对象的每一层都是指针复制。
深复制(one-level-deep copy):在深复制操作时,对于被复制对象,至少有一层是深复制。
完全复制(real-deep copy):在完全复制操作时,对于被复制对象的每一层都是对象复制。
在非集合类对象中:对immutable对象进行copy操作,是指针复制,mutableCopy操作时内容复制;对mutable对象进行copy和mutableCopy都是内容复制。用代码简单表示如下:
[immutableObject copy] // 浅复制
[immutableObject mutableCopy] //深复制
[mutableObject copy] //深复制
[mutableObject mutableCopy] //深复制
在集合类对象中,对immutable对象进行copy,是指针复制,mutableCopy是内容复制;对mutable对象进行copy和mutableCopy都是内容复制。但是:集合对象的内容复制仅限于对象本身,对象元素仍然是指针复制。用代码简单表示如下:
[immutableObject copy] // 浅复制
[immutableObject mutableCopy] //单层深复制
[mutableObject copy] //单层深复制
[mutableObject mutableCopy] //单层深复制
copy:创建的是不可变副本(NSString,NSArray,NSDictionary)
mutableCopy:创建的是可变副本(NSMutableString,NSMutableArray,NSMutableDictionary)
容器类的深拷贝实现
- 可以通过归解档生成两份完全独立的对象,但是前提是对象必须支持 NSCoding
为什么字符串要用copy
上代码解释,先用strong
试试
@interface ViewController ()
@property(nonatomic,strong)NSString*whyStringUserCopyStr;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self whyStringUserCopy];
}
-(void)whyStringUserCopy{
NSMutableString *string = [NSMutableString string];
[string appendString:@"hello"];
self.whyStringUserCopyStr = string;
NSLog(@"%@",self.whyStringUserCopyStr);
NSLog(@"string =%p %p whyStringUserCopyStr=%p %p",string,&string,_whyStringUserCopyStr,&_whyStringUserCopyStr);
[string appendString:@"World"];
NSLog(@"%@",self.whyStringUserCopyStr);
}
打印出的信息
hello
string =0x600000245b50 0x7ffee35f92e8 whyStringUserCopyStr=0x600000245b50 0x7f9db3607800
helloWorld
你会发现self.whyStringUserCopyStr
只被赋值了一次,却改变了两次
在看中间打印出string
的本身的地址和string
指向的地址 以及 whyStringUserCopyStr
的本身的地址和whyStringUserCopyStr
指向的地址
用strong
相当于whyStringUserCopyStr
指向了可变字符串的地址,所以可变字符串改变了那么whyStringUserCopyStr
也会跟着改变,如果用copy
将可变字符串重新拷贝一份,重新开辟内存空间,修改可变字符串的值,不会对whyStringUserCopyStr造成影响
因为可变对象的copy是深拷贝
刚刚的演示,我使用了NSMutableString(可变字符串),对mutableString执行copy操作,属于深拷贝,所以开辟的新内存空间,如果使用的是NSString(不可变内存),对NSString进行copy属于浅拷贝,不会开辟新的内存空间,是不是就不会出现这个问题了呢?
接下来使用NSString来进行演示,将属性修饰符再次改回strong
#import "ViewController.h"
@interface ViewController ()
/* 为什么字符串使用copy修饰?
这里先不使用copy修饰,NSString是OC对象,并且是ARC模式,所以先使用strong来修饰演示
*/
@property (nonatomic,strong) NSString *name;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSString *str = [NSString string];
str = @"xiaoming";
self.name = str;
NSLog(@"str:%p,name:%p",str,_name);
str = @"xiaogang";
NSLog(@"str:%p,name:%p",str,_name);
NSLog(@"%@",_name);
}
@end
打印
str:0x10c937060,name:0x10c937060
str:0x10c9370a0,name:0x10c937060
xiaoming
从结果上看,重新设置str的值后,self.name确实没有受到影响
原因:
将之前的可变字符串变为不可变字符串,因为NSString不支持append添加操作,我这里的两次str赋值操作,其实是让str重新指向了一片内存空间,并不是修改了str原本内存中的值
OC中对象即指针,实际上存储的是内存地址,self.name = str;
实际是将str存储的@"xiaoming"
这块地址给了self.name
self.name还指向着@"xiaoming"
所以改变str的指向后,self.name的指向并没有改变,输出没有受到影响