面试题引发的思考:
Q: 谈一谈对copy
、mutableCopy
、浅拷贝、深拷贝的理解?
copy
:不可变拷贝,产生不可变副本;mutableCopy
:可变拷贝,产生可变副本;- 浅拷贝:指针拷贝,没有产生新的对象;
- 深拷贝:内容拷贝,产生新的对象。
Q: iOS项目中copy
修饰词的使用?
copy
修饰词 只能声明 不可变类型。- 字符串一般用
copy
修饰,用于UI控件显示的时候不会有问题。 - 属性不存在
mutableCopy
修饰词,只有部分Foundation
框架自带的一些类可以用mutableCopy
修饰。
Q: 自定义的类实现 copy
功能?
- 遵守
NSCopying
协议; - 实现
copyWithZone:
方法。
// TODO: ----------------- Person类 -----------------
// 自定义的类实现 copy 功能需要:1. 遵守 NSCopying 协议
@interface Person : NSObject
@property (nonatomic, assign) int age;
@property (nonatomic, copy) NSString *name;
@end
@implementation Person
// 自定义的类实现 copy 功能需要:2. 实现 copyWithZone: 方法
- (nonnull id)copyWithZone:(nullable NSZone *)zone {
Person *person = [[Person allocWithZone:zone] init];
person.age = self.age;
person.name = self.name;
return person;
}
- (NSString *)description {
return [NSString stringWithFormat:@"age = %d, weight = %@", self.age, self.name];
}
@end
1. 原理介绍
拷贝的目的:产生一个副本对象,跟源对象互不影响
- 修改了源对象,不会影响副本对象;
- 修改了副本对象,不会影响源对象。
iOS提供了2个拷贝方法:
copy
:不可变拷贝,产生不可变副本;mutableCopy
:可变拷贝,产生可变副本。
浅拷贝和深拷贝:
- 浅拷贝:指针拷贝,没有产生新的对象;
不可变对象NSString
、NSArray
、NSDictionary
调用copy
;- 深拷贝:内容拷贝,产生新的对象;
深拷贝:其他情况。
2. 案例分析
(1) copy
和mutableCopy
分析
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSString *str1 = [NSString stringWithFormat:@"test"];
NSString *str2 = [str1 copy]; // 返回的是NSString
NSMutableString *str3 = [str1 mutableCopy]; // 返回的是NSMutableString
[str3 appendString:@"ceshi"];
NSLog(@"%@ %@ %@", str1, str2, str3);
}
return 0;
}
// 打印结果
Demo[1234:567890] test test testceshi
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSMutableString *str1 = [NSMutableString stringWithFormat:@"test"];
NSString *str2 = [str1 copy]; // 返回的是NSString
NSMutableString *str3 = [str1 mutableCopy]; // 返回的是NSMutableString
[str3 appendString:@"ceshi"];
NSLog(@"%@ %@ %@", str1, str2, str3);
}
return 0;
}
// 打印结果
Demo[1234:567890] test test testceshi
由以上代码可知:
copy
:不可变拷贝,产生不可变副本;mutableCopy
:可变拷贝,产生可变副本。
(2) 浅拷贝和深拷贝分析
将环境改为手动内存管理,我们知道:
当调用
alloc
、new
、copy
、mutableCopy
方法返回了一个对象,在不需要这个对象时,要调用release
或者autorelease
来释放它。
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
// 避免使用Tagged Pointer 技术存储数据,所以赋值长一些
NSString *str1 = [[NSString alloc] initWithFormat:@"test123456789"];
NSString *str2 = [str1 copy]; // 浅拷贝:指针拷贝,没有产生新的对象
NSMutableString *str3 = [str1 mutableCopy]; // 深拷贝:内容拷贝,产生新的对象
NSLog(@"%@ %@ %@", str1, str2, str3);
NSLog(@"%p %p %p", str1, str2, str3);
[str3 release];
[str2 release];
[str1 release];
}
return 0;
}
// 打印结果
Demo[1234:567890] test123456789 test123456789 test123456789
Demo[1234:567890] 0x1005b9b60 0x1005b9b60 0x100604a40
由打印结果可知:
str1
、str2
的地址是一样的,说明str1
、str2
是指向的是同一个对象。其指针分析如下:
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSMutableString *str1 = [[NSMutableString alloc] initWithFormat:@"test123456789"];
NSString *str2 = [str1 copy]; // 深拷贝:内容拷贝,产生新的对象
NSMutableString *str3 = [str1 mutableCopy]; // 深拷贝:内容拷贝,产生新的对象
[str1 appendFormat:@"111"];
[str3 appendFormat:@"333"];
NSLog(@"%@ %@ %@", str1, str2, str3);
NSLog(@"%p %p %p", str1, str2, str3);
[str3 release];
[str2 release];
[str1 release];
}
return 0;
}
// 打印结果
Demo[1234:567890] test123456789111 test123456789 test123456789333
Demo[1234:567890] 0x10049d9b0 0x10049ded0 0x10049dfd0
由打印结果可知:
str1
、str2
、str3
的地址都是不一样的,说明str1
、str2
、str3
是分别指向的是不同的对象。其指针分析如下:
(3) 扩展分析
1> 对于数组
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSArray *array1 = [[NSArray alloc] initWithObjects:@"1", @"2", nil];
NSArray *array2 = [array1 copy]; // 浅拷贝:指针拷贝,没有产生新的对象
NSMutableArray *array3 = [array1 mutableCopy]; // 深拷贝:内容拷贝,产生新的对象
NSLog(@"%p %p %p", array1, array2, array3);
[array1 release];
[array2 release];
[array3 release];
}
return 0;
}
// 打印结果
Demo[1234:567890] 0x10049eca0 0x10049eca0 0x10049ef50
由打印结果可知:
array1
、array2
的地址是一样的,说明array1
、array2
是指向的是同一个对象。
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSMutableArray *array1 = [[NSMutableArray alloc] initWithObjects:@"1", @"2", nil];
NSArray *array2 = [array1 copy]; // 深拷贝:内容拷贝,产生新的对象
NSMutableArray *array3 = [array1 mutableCopy]; // 深拷贝:内容拷贝,产生新的对象
NSLog(@"%p %p %p", array1, array2, array3);
[array1 release];
[array2 release];
[array3 release];
}
return 0;
}
// 打印结果
Demo[1234:567890] 0x10313c020 0x10313c700 0x10313c720
由打印结果可知:
array1
、array2
、array3
的地址都是不一样的,说明array1
、array2
、array3
是分别指向的是不同的对象。
2> 对于字典
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSDictionary *dict1 = [[NSDictionary alloc] initWithObjectsAndKeys:@"jack", @"name", nil];
NSDictionary *dict2 = [dict1 copy]; // 浅拷贝:指针拷贝,没有产生新的对象
NSMutableDictionary *dict3 = [dict1 mutableCopy]; // 深拷贝:内容拷贝,产生新的对象
NSLog(@"%p %p %p", dict1, dict2, dict3);
[dict1 release];
[dict2 release];
[dict3 release];
}
return 0;
}
// 打印结果
Demo[1234:567890] 0x100511810 0x100511810 0x1005118f0
由打印结果可知:
dict1
、dict2
的地址是一样的,说明dict1
、dict2
是指向的是同一个对象。
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSMutableDictionary *dict1 = [[NSMutableDictionary alloc] initWithObjectsAndKeys:@"jack", @"name", nil];
NSDictionary *dict2 = [dict1 copy]; // 深拷贝:内容拷贝,产生新的对象
NSMutableDictionary *dict3 = [dict1 mutableCopy]; // 深拷贝:内容拷贝,产生新的对象
NSLog(@"%p %p %p", dict1, dict2, dict3);
[dict1 release];
[dict2 release];
[dict3 release];
}
return 0;
}
// 打印结果
Demo[1234:567890] 0x103132250 0x103132850 0x103132870
由打印结果可知:
dict1
、dict2
、dict3
的地址都是不一样的,说明dict1
、dict2
、dict3
是分别指向的是不同的对象。
(4) 总结
根据以上代码及分析可总结如下:
copy
:不可变拷贝,产生不可变副本;mutableCopy
:可变拷贝,产生可变副本;- 浅拷贝:指针拷贝,没有产生新的对象;
- 深拷贝:内容拷贝,产生新的对象;
- 不可变对象
NSString
、NSArray
、NSDictionary
调用copy
时是浅拷贝;- 其他情况是深拷贝。
总结图例如下:
3. 修饰词copy
的使用分析
(1) 总结修饰词copy
和retain
的区别
对于retain
修饰词修饰:
// TODO: ----------------- Person类 -----------------
@interface Person : NSObject
@property(nonatomic, retain) NSArray *dataArray;
@end
@implementation Person
// retain 相当于一下代码
- (void)setDataArray:(NSArray *)dataArray {
if (_dataArray != dataArray) {
[_dataArray release];
_dataArray = [dataArray retain]; // 仅此处有差别
}
}
- (void)dealloc {
self.dataArray = nil;
[super dealloc];
}
@end
对于copy
修饰词修饰:
// TODO: ----------------- Person类 -----------------
@interface Person : NSObject
@property(nonatomic, copy) NSArray *dataArray;
@end
@implementation Person
// copy 相当于一下代码
- (void)setDataArray:(NSArray *)dataArray {
if (_dataArray != dataArray) {
[_dataArray release];
_dataArray = [dataArray copy]; // 仅此处有差别
}
}
- (void)dealloc {
self.dataArray = nil;
[super dealloc];
}
@end
因为用copy
修饰词修饰的dataArray
,会先把旧的_dataArray
先release
,然后调用copy
赋值给_dataArray
,那么_dataArray
就是不可变类型。
得出结论:
copy
修饰词只能声明不可变类型。
(2) 案例分析
1> 对于copy
修饰词修饰不可变数组NSArray
:
// TODO: ----------------- Person类 -----------------
@interface Person : NSObject
@property(nonatomic, copy) NSArray *dataArray;
@end
@implementation Person
@end
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
Person *person = [[Person alloc] init];
person.dataArray = @[@"jack", @"rose"];
// 相当于 不可变的数组 调用 copy 返回一个 不可变的数组 给 person.dataArray
// person.dataArray = [@[@"jack", @"rose"] copy];
NSLog(@"%@", person.dataArray);
[person release];
}
return 0;
}
// 打印结果
Demo[1234:567890] (
jack,
rose
)
运行正常,没有问题。
2> 对于copy
修饰词修饰可变数组NSMutableArray
:
// TODO: ----------------- Person类 -----------------
@interface Person : NSObject
@property(nonatomic, copy) NSMutableArray *dataArray;
@end
@implementation Person
@end
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
Person *person = [[Person alloc] init];
person.dataArray = [NSMutableArray array];
// 相当于 可变的数组 赋值给 person.dataArray,然后调用 copy 返回一个 不可变数组
[person.dataArray addObject:@"jack"];
[person.dataArray addObject:@"rose"];
NSLog(@"%@", person.dataArray);
[person release];
}
return 0;
}
// 打印结果
Demo[1234:567890] '-[__NSArray0 addObject:]: unrecognized selector sent to instance 0x7fff898d1060'
运行崩溃,提示“方法找不到错误”。
因为可变数组
dataArray
用copy
修饰词修饰,语句person.dataArray = [NSMutableArray array];
相当于可变的数组
赋值给person.dataArray
,然后调用copy
返回一个不可变数组
,所以不可变数组
person.dataArray
没有addObject:
方法,运行崩溃。
(3) iOS项目中copy
修饰词的使用
1> UI控件显示使用copy
修饰
对于UITextField
的text
属性内部代码如下:
我们发现iOS底层代码对于字符串相关的基本上都是使用copy
修饰。
具体事例分析如下:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSMutableString *text = [NSMutableString stringWithFormat:@"123"];
UITextField *textField;
// copy 保证 textField.text 赋值的是 text 的副本,不会因为 text 的改变而改变
textField.text = text;
// 此处修改 text 不会影响 textField.text 的值
[text appendString:@"abc"];
}
由以上分析可知:
字符串一般用
copy
修饰,用于UI控件显示的时候不会有问题。
属性不存在mutableCopy
修饰词,因为只有部分Foundation
框架自带的一些类可以用mutableCopy
修饰,比如以下类:
NSString, NSMutableString;
NSArray, NSMutableArray;
NSDictionary, NSMutableDictionary;
NSData, NSMutableData;
NSSet, NSMutableSet;
等等...
2> 自定义的类实现copy
功能
// TODO: ----------------- Person类 -----------------
// 自定义的类实现 copy 功能需要:1. 遵守 NSCopying 协议
@interface Person : NSObject
@property (nonatomic, assign) int age;
@property (nonatomic, copy) NSString *name;
@end
@implementation Person
// 自定义的类实现 copy 功能需要:2. 实现 copyWithZone: 方法
- (nonnull id)copyWithZone:(nullable NSZone *)zone {
Person *person = [[Person allocWithZone:zone] init];
person.age = self.age;
person.name = self.name;
return person;
}
- (NSString *)description {
return [NSString stringWithFormat:@"age = %d, weight = %@", self.age, self.name];
}
@end
// TODO: ----------------- main -----------------
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p1 = [[Person alloc] init];
p1.age = 20;
p1.name = @"Jack";
// p2是一个全新的副本对象,与p1互不干涉
Person *p2 = [p1 copy];
p2.age = 30;
NSLog(@"%@", p1);
NSLog(@"%@", p2);
[p2 release];
[p1 release];
}
return 0;
}
// 打印结果
Demo[1234:567890] age = 20, weight = Jack
Demo[1234:567890] age = 30, weight = Jack
自定义的类实现 copy
功能需要:
- 遵守
NSCopying
协议;- 实现
copyWithZone:
方法。