copy的目的
顾名思义拷贝就是要产生一个副本对象,和我们平时使用的Ctr+C
、Ctr+V
是一样的,目的是保证副本对象与源对象互不影响。
互不影响可以从两个方面去理解:
1、修改了源对象,不会影响副本对象。
2、修改了副本对象,不会影响源对象。
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSMutableString *str1 = [[NSMutableString alloc] initWithFormat:@"季末"];
NSString *str2 = [str1 copy];
NSLog(@"before: str1 - %@ str2 - %@", str1, str2);
[str1 appendString:@"1"];
NSLog(@"after: str1 - %@ str2 - %@", str1, str2);
}
return 0;
}
以下运行结果显示拷贝结束后修改str1
的内容没有影响str2
。
2019-06-10 20:55:03.776131+0800 Interview04-copy[42200:688528] before: str1 - 季末 str2 - 季末
2019-06-10 20:55:03.776296+0800 Interview04-copy[42200:688528] after: str1 - 季末1 str2 - 季末
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSString *str1 = [NSString stringWithFormat:@"季末"];
NSMutableString *str2 = [str1 mutableCopy];
NSLog(@"before: str1 - %@ str2 - %@", str1, str2);
[str2 appendString:@"1"];
NSLog(@"after: str1 - %@ str2 - %@", str1, str2);
}
return 0;
}
以下运行结果显示拷贝结束后修改str2
的内容没有影响str1
。
2019-06-10 20:36:19.432627+0800 Interview04-copy[42047:672403] befor: str1 - 季末 str2 - 季末
2019-06-10 20:36:19.432782+0800 Interview04-copy[42047:672403] after: str1 - 季末 str2 - 季末1
所以copy
的目的你是不是现在更清楚了呢?
copy 和 mutableCopy
copy
:不可变拷贝,产生不可变副本。
mutableCopy
:可变拷贝,产生可变副本。
首先我们验证一下copy
和mutableCopy
生成对象的类型。
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSString *str1 = [NSString stringWithFormat:@"季末"];
NSMutableString *str2 = [str1 mutableCopy];
NSMutableString *str3 = [str1 copy];
[str2 appendString:@"2"];
NSLog(@"str2 - %@", str2);
[str3 appendString:@"3"];
NSLog(@"str3 - %@", str3);
}
return 0;
}
2019-06-07 15:10:44.638794+0800 Interview04-copy[2826:278171] str2 - 季末2
2019-06-07 15:10:44.639270+0800 Interview04-copy[2826:278171] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to mutate immutable object with appendString:'
从打印结果可以看出str2
是可变字符串,str3
为不可变字符串。
接下来我们观察下str1
、str2
、str3
的内存地址:
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSString *str1 = [NSString stringWithFormat:@"季末"];
NSString *str2 = [str1 copy];
NSMutableString *str3 = [str1 mutableCopy];
NSLog(@"%@ %p", str1, str1);
NSLog(@"%@ %p", str2, str2);
NSLog(@"%@ %p", str3, str3);
}
return 0;
}
2019-06-07 14:56:39.835480+0800 Interview04-copy[2751:264704] 季末 0x10386b500
2019-06-07 14:56:39.835642+0800 Interview04-copy[2751:264704] 季末 0x10386b500
2019-06-07 14:56:39.835651+0800 Interview04-copy[2751:264704] 季末 0x1038694e0
我们发现 str1
、str2
、str3
的内容都是“季末”
,str1
和str2
指向同一块内存,str3
是新开辟的一块内存,为何str2
、str3
两者的情况不一致呢?
这里便引入了深拷贝
和浅拷贝
的概念, 所谓深拷贝
就是内容拷贝
,新开辟一块内存,将内容拷贝到该内存,由于str3
为可变字符串,当其变化时候不能影响str1
,所以需要新开辟内存。
而浅拷贝
也就是指针拷贝
,不会新开辟内存,仅仅是将指针指向该内存,作用同retain
,因为源对象str1
和副本对象str2
都是不可变字符串,从节约内存的角度来讲不需要存在两份内容,所以两者指向了同一块内存。
那么如果原始数据为可变字符串的话情况又是什么样的呢?
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSMutableString *str1 = [NSMutableString stringWithFormat:@"季末"];
NSMutableString *str2 = [str1 mutableCopy];
NSString *str3 = [str1 copy];
NSLog(@"%@ %p", str1, str1);
NSLog(@"%@ %p", str2, str2);
NSLog(@"%@ %p", str3, str3);
}
return 0;
}
2019-06-07 15:22:50.911181+0800 Interview04-copy[2889:287084] 季末 0x103805e10
2019-06-07 15:22:50.911343+0800 Interview04-copy[2889:287084] 季末 0x103805f10
2019-06-07 15:22:50.911352+0800 Interview04-copy[2889:287084] 季末 0x1038057a0
此时,你会发现str1
、str2
、str3
的内存各不相同,我们来分析下为什么?
由于str1
是可变字符串,当其变化的时候想要不影响str2
和str3
,str2
和str3
必须是新开辟的内存。
深拷贝
还是浅拷贝
与源对象和拷贝方法有关 ,只有源对象和副本对象都不可变时是浅拷贝
,其余场景都是深拷贝
。现总结如下:
内存管理copy
@property (nonatomic, copy) NSString *name;
那么我们平时在属性中使用copy
的作用又是什么呢?
@property (nonatomic, copy) NSMutableString *name;
上面这种写法又有什么错误呢?
#import
NS_ASSUME_NONNULL_BEGIN
@interface LJPerson : NSObject
@property (nonatomic, copy) NSString *nameCopy;
@property (nonatomic, strong) NSString *nameStrong;
@end
NS_ASSUME_NONNULL_END
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSMutableString *str1 = [NSMutableString stringWithFormat:@"季末"];
LJPerson *p = [[LJPerson alloc] init];
p.nameCopy = str1;
p.nameStrong = str1;
[str1 appendString:@"灬离殇"];
NSLog(@"str1 %p-%@", str1, str1);
NSLog(@"nameCopy %p-%@ nameStrong %p-%@", p.nameCopy, p.nameCopy, p.nameStrong, p.nameStrong);
}
return 0;
}
2019-06-07 16:06:02.737937+0800 Interview04-copy[3020:321754] str1 0x103a05ca0-季末灬离殇
2019-06-07 16:06:02.738104+0800 Interview04-copy[3020:321754] nameCopy 0x103a05ec0-季末 nameStrong 0x103a05ca0-季末灬离殇
运行发现nameCopy
的内存地址和内容同str1
都不同,而nameStrong
内存地址和内容同str1
都一样。当外部修改str1
时候会对nameStrong
造成影响,破坏了其封装性,所以在当你不希望受到外部内容变化影响的时候需要使用copy
修改Core Foundation
中存在可变类型的不可变对象。而copy
无论源对象可变与否,产生的副本对象均为不可变对象,此时声明类型为可变非常危险,如果在运行时调用可变对象的方法会造成崩溃,因为对象的实际类型为不可变。
nameCopy
在MRC
下的setter
方法如下:
@property (nonatomic, copy) NSString *nameCopy;
- (void)setNameCopy:(NSString *)nameCopy {
if (_nameCopy != nameCopy) {
[_nameCopy release];
_nameCopy = [nameCopy copy];
}
}
nameStrong
在MRC
下的setter
方法如下:
@property (nonatomic, strong) NSString *nameStrong;
- (void)setNameStrong:(NSString *)nameStrong {
if (_nameStrong != nameStrong) {
[_nameStrong release];
_nameStrong = [nameStrong retain];
}
}
自定义类copy
如果我们希望自己定义的类,也能使用copy
方法,该如何操作呢?
假设我们直接使用copy
,看看会出现什么情况。
LJPerson *p = [[LJPerson alloc] init];
p.name = @"Jack";
LJPerson *p1 = [p copy];
运行结果
-[LJPerson copyWithZone:]: unrecognized selector sent to instance 0x104100310
我们会发现,这时候程序会报错,说我们没有实现copyWithZone:
方法。这说明,我们是可以为自己的类定义copy
方法的,只是要进行一些规范性的操作。
自定义类实现copy
的步骤:
(1)遵守NSCopying
协议
(2)实现copyWithZone:
方法
#import
NS_ASSUME_NONNULL_BEGIN
// 遵守NSCopying协议
@interface LJPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end
NS_ASSUME_NONNULL_END
#import "LJPerson.h"
// 实现copyWithZone方法
@implementation LJPerson
- (id)copyWithZone:(NSZone *)zone {
LJPerson *p = [[self.class allocWithZone:zone] init];
p.name = self.name;
return p;
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
LJPerson *p = [[LJPerson alloc] init];
p.name = @"Jack";
LJPerson *p1 = [p copy];
NSLog(@"%p %@", p, p.name);
NSLog(@"%p %@", p1, p1.name);
}
return 0;
}
运行结果:
2019-06-07 12:28:58.074770+0800 Interview04-copy[2632:234842] 0x104006cd0 Jack
2019-06-07 12:28:58.074962+0800 Interview04-copy[2632:234842] 0x104006ae0 Jack
关于copy
今天就讲到这里,如果有哪里讲的不对或者有疑问的地方,欢迎私信我。