在日常开发中,会经常用到copy,Objective-C中很多对象都支持copy操作,如NSString,NSArray,NSDictionary,开发者自定义的对象也是可以支持copy操作的。和copy放在一起比较的还有mutableCopy、strong,那么三者之间到底有什么区别呢?什么情况下用copy,什么情况下用strong呢?下面围绕这几个问题来研究一下。
copy 和 mutableCopy
深拷贝 浅拷贝
在区分copy 和 mutableCopy的不同之前,首先了解一下深拷贝和浅拷贝。
在学习和工作中,可能会涉及到多门语言,每门语言中可能都有深拷贝和浅拷贝。简单来说,浅拷贝就是增加了一个指针,该指针指向的内存地址就是赋值对象的内存地址;深拷贝不仅仅是增加了指针,同时也分配了一块内存,该内存中的值和赋值对象所在内存中的值是一样的,深拷贝所增加的指针,指向的也是新分配的内存。
区分一个操作到底是深拷贝还是浅拷贝,可以打印内存地址。如果两个内存地址一致,则是浅拷贝,否则是深拷贝。
非集合对象的copy 和 mutableCopy操作
Objecitve-C中对象分为集合对象和非集合对象。
集合对象:NSArray、NSDictionary等。集合对象又分为可变对象和不可变对象。不可变集合对象:NSArray、NSDictionary;可变集合对象:NSMutableArray、NSMutableDictionary。
非集合对象:NSString等。非集合对象可分为可变对象和非可变对象。不可变对象:NSString;可变对象:NSMutableString。
非集合对象的copy、mutableCopy分别对应什么操作呢?
先说结论:非集合对象,可变对象,copy 和 mutableCopy都是深复制;不可变对象,copy是浅复制,mutableCopy是深复制。
写代码来验证上面的结论。
- (void)notSetTestCopy
{
// 非集合对象,如NSString
// 非集合对象又可分为可变对象和不可变对象,NSString为可变对象,NSMutableString为不可变对象
NSString *str1 = @"123";
NSString *str2 = [str1 copy];
NSLog(@"str1 = %p str2 = %p",str1,str2); //两者的地址相同,对于非集合对象且不可变对象,copy为浅复制
NSString *str3 = [str1 mutableCopy];
NSLog(@"str1 = %p str3 = %p",str1,str3); //两者的地址不相同,对于非集合对象且不可变对象,mutableCopy为深复制
NSMutableString *str4 = [NSMutableString stringWithString:@"123"];
NSString *str5 = [str4 copy];
NSLog(@"str4 = %p str5 = %p",str4,str5); // 两者的地址不相同,非集合对象且可变对象,copy为深复制
NSMutableString *str6 = [str4 mutableCopy];
NSLog(@"str4 = %p str6 = %p",str4,str6); // 两者的地址不相同,非集合对象且可变对象,mutableCopy为深复制
[str4 insertString:@"111" atIndex:0];
NSLog(@"str4 = %@,str5 = %@,str6 = %@",str4,str5,str6); // 111123 123 123
}
集合对象的copy 和 mutableCopy 操作
集合对象的copy、mutableCopy 对应的是什么操作呢?
同样先说结论:集合对象,可变对象,copy 和 mutableCopy 都是深复制;不可变对象,copy是浅复制,mutableCopy 是深复制。
写代码来验证。
- (void)setTestCopy
{
// 集合对象,NSArray、NSDictionary等
// 可变集合对象,NSMutableArray;不可变集合对象,NSArray
NSArray *array1 = @[@"1",@"2",@"3"];
NSArray *array2 = [array1 copy];
NSLog(@"array1 = %p array2 = %p",array1,array2); // 两者的地址相同,不可变集合对象copy为浅复制
NSArray *array3 = [array1 mutableCopy];
NSLog(@"array1 = %p array3 = %p",array1,array3); // 两者的地址不相同,不可变集合对象mutableCopy为深复制
NSMutableArray *array4 = [NSMutableArray arrayWithArray:array1];
NSArray *array5 = [array4 copy];
NSLog(@"array4 = %p array5 = %p",array4,array5); //两者的地址不相同,可变集合对象copy为深复制
NSArray *array6 = [array4 mutableCopy];
NSLog(@"array4 = %p array6 = %p",array4,array6); // 两者的地址不相同,可变集合对象mutableCopy为深复制
}
copy 和 strong
为什么NSString类型的对象通常使用 copy 修饰符?
在回答上面的问题之前,先看NSString类型的对象两种不同的写法:
@property (nonatomic, copy) NSString *str1;
@property (nonatomic, strong) NSString *str2;
NSString 类型的对象 str1、str2分别用了copy 和 strong修饰符,那么两者有什么不同呢?
NSString 有一个子类是 NSMutableString,两者的区别是,NSString 类型的对象是不可变的,而 NSMutableString 类型的对象是可变的。如果一个变量是NSString 类型的,那么通常是不希望变量的值被改变的,否则直接使用 NSMutableString 就可以了。NSString 类型的对象,可以使用NSMutableString 类型的对象来赋值,而copy修饰符,能够保证NSString 类型的对象不被修改。如果用strong 修饰NSString 对象,NSString 对象的值有可能会被篡改。
写代码验证一下使用 copy 和 strong有什么不同。
@property (nonatomic, copy) NSString *customStr;
- (void)testCopy
{
NSString *tempStr = @"123";
NSMutableString *mutString = [NSMutableString stringWithString:tempStr];
self.customStr = mutString; // 其实就相当于 self.customStr = [mutString copy];
NSLog(@"self str = %p mutStr = %p",self.customStr,mutString); // 两者指向的地址是不一样的
[mutString insertString:@"456" atIndex:0];
NSLog(@"self str = %@ mutStr = %@",self.customStr,mutString); // self.customStr不会随着mutString的值而改变
}
使用copy,即使mutString的值改变了,self.customStr的值不会随之改变。
再来看一下如果使用 strong 的效果:
@property (nonatomic, strong) NSString *strongStr;
- (void)testStrong
{
NSString *tempStr = @"123";
NSMutableString *mutString = [NSMutableString stringWithString:tempStr];
self.strongStr = mutString;
NSLog(@"self str = %p mutStr = %p",self.strongStr,mutString); // 两者指向的地址是一样的
[mutString insertString:@"456" atIndex:0];
NSLog(@"self str = %@ mutStr = %@",self.strongStr,mutString); // 两者的值都会改变
}
可以看到,self.strongStr 的值会随着 mutString值的改变而改变。
为了防止NSString 类型的对象意外被改变,开发中NSString 类型的对象通常使用copy修饰符。同理,NSArray、NSDictionary 类型的对象通常也使用copy 修饰符。
为什么NSMutableString类型的对象通常使用 strong 修饰符?
其实通过上面的介绍,对于NSMutableString 类型的对象为什么使用 strong 修饰符,已经有一定了解了。通常来说,使用NSMutableString 对象,则希望该对象能够被改变。如果用copy来修饰,那么该对象是不能被修改的,也就不具有了 NSMutableString 的特性。
下面我们看一个可变对象使用copy 修饰符来修饰,会带来什么问题。
@property (nonatomic, copy) NSMutableArray *mutArray; 会有什么问题?
写代码测试一下:
@property (nonatomic, copy) NSMutableArray *mutArray;
/**
可变对象的copy操作,虽然是深复制,但是得到的是一个不可变对象
*/
- (void)testArrayCopy
{
NSArray *array = [NSArray arrayWithObjects:@"1",@"2", nil];
self.mutArray = [NSMutableArray arrayWithArray:array]; // 相当于: self.mutArray = [array copy];虽然是深复制,但是得到一个不可变对象
NSLog(@"array = %p self.mutArray = %p",array,self.mutArray); // 两者的地址不一样
NSLog(@"array class = %@ self.mutArray class = %@",NSStringFromClass([array class]),NSStringFromClass([self.mutArray class])); // 两者类一致,都是NSArray 对象
[self.mutArray removeObjectAtIndex:0]; // 会崩溃,因为此时 self.mutArray是NSArray
}
最终mutArray 会由于 copy 操作,得到一个不可变对象,无法进行增加、删除的操作。
自定义对象的copy操作
在开发中,会经常有自定义对象的需求。那么自定义的对象如何支持copy操作呢?自定义对象支持copy操作,需要两个步骤:
1. 实现 NSCopying 协议
2. 重写 CopyWithZone 方法
假设有Person 类,代码如下:
Person.h
#import <Foundation/Foundation.h>
@interface Person : NSObject <NSCopying>
/**
昵称
*/
@property (nonatomic, copy) NSString *name;
/**
年龄
*/
@property (nonatomic, assign) NSInteger age;
/**
使用昵称、年龄初始化
@param name 昵称
@param age 年龄
@return 对象
*/
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age;
+ (instancetype)personWithName:(NSString *)name age:(NSInteger)age;
@end
Person.m 中重写 copyWithZone方法。代码如下:
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age
{
if(self = [super init]){
self.name = name;
self.age = age;
}
return self;
}
+ (instancetype)personWithName:(NSString *)name age:(NSInteger)age
{
return [[self alloc] initWithName:name age:age];
}
- (id)copyWithZone:(NSZone *)zone
{
Person *copyPerson = [[Person allocWithZone:zone] initWithName:self.name age:self.age];
return copyPerson;
}
测试Person 对象是否支持copy 操作:
- (void)testPersonCopy
{
Person *p1 = [[Person alloc] initWithName:@"Wang" age:18];
Person *p2 = p1;
NSLog(@"p1 = %p p2 = %p",p1,p2); // p1、 p2指向的是同一个地址
Person *p3 = [p1 copy];
NSLog(@"p1 = %p p3 = %p",p1,p3); // p1、p3 指向的是不同地址
p1.name = @"Tom";
NSLog(@"p1.name = %@ p2.name = %@ p3.name = %@",p1.name,p2.name,p3.name); // p1、p2 name相同,p3 name不同
}
说明Person 支持copy操作,且是深拷贝。
总结
copy 虽然是一个简单的操作,但是其相关的知识是非常多的。在使用中,一定要记清楚什么情况下使用copy,什么情况下使用strong,以及mutableCopy,并且明白其原理,避免项目出现一些奇怪的bug。