-
在OC语法中,提供了Copy语法(Copy + MutableCopy)用于对象的拷贝。其中很容易混淆的是浅拷贝和深拷贝。
所谓浅拷贝,即是地址拷贝,并不产生新的对象,而是对原对象的引用计数值加1即调用所谓的retain。而深拷贝,即是对象拷贝,产生新的对象副本,计数器为1,调用copy或mutableCopy。
何时用copy, 何时用 mutableCopy?
1、不可变对象→可变对象的转换:
2、可变对象→不可变对象的转换:NSArray *array1 = [NSArray arrayWithObjects:@"a",@"b",@"c",nil]; NSMutableArray *str2 = [array1 mutableCopy];
3、可变对象→可变对象的转换(不同指针变量指向不同的内存地址):NSMutableArray *array2 = [NSMutableArray arrayWithObjects:@"aa",@"bb",@"cc",@"dd",nil]; NSArray *array1=[array2 copy];
NSMutableArray *array1= [NSMutableArray arrayWithObjects:@"a",@"b", nil]; NSMutableArray *str2=[array1 mutableCopy];
通过上边的两个例子,我们可以很方便的将一个对象在可变和不可变之间转换,并且这里不用考虑内存使用原则(即引用计数的问题)。这就是深拷贝的魅力所在。
4、同类型对象之间的导向保持(不同指针变量指向同一块内存地址):
a.
b.NSMutableString *str1=[NSMutableString stringWithString:@"hello world"]; NSMutableString *str2=[str1 retain]; [str1 release];
NSArray *array1= [NSArray arrayWithObjects:@"a",@"b",@"c",nil]; NSArray *str2=[array1 Copy]; [array1 release];
通俗的讲,就是甲在执行交通导航任务,但接到上级新的命令要执行新的任务,那么在甲执行新任务之前,需要有人替代甲继续执行交通导航任务。这时候就要用到浅拷贝了。
则简化为:
问:什么时候用到深浅拷贝?
答:深拷贝是在要将一个对象从可变(不可变)转为不可变(可变)或者将一个对象内容克隆一份时用到;浅拷贝是在要复制一个对象的指针时用到。
下面通过一个例子来分析一下这个比较容易乱的Copy:
1. 对于NSString/NSMutableString; NSArray/NSMutableArray...这些OC提供的类对象:
以NSString/NSMutableString为例:
对于copy,返回的一定是不可变类型;而mutableCopy,返回的一定是可变类型。
①对于 mutableCopy,一定是深拷贝。
-
//对于 mutableCopy,都是深拷贝:对象的拷贝,产生新的对象 void strMutableCopy(){ NSString *str=[[NSString alloc]initWithFormat:@"abcd"]; //产生了一个新的对象 计数器为1 原对象的计数器不变 NSMutableString *str2=[str mutableCopy]; //输出二者的地址,二者的地址是不同的 NSLog(@"str --> %p",str); NSLog(@"str2 --> %p",str2); }
②对于 copy:
如果是 NSString ---> NSString;则是浅拷贝;如果是 NSMutableString --->NSString;则是深拷贝。
如果是 NSString 、NSMutableString ---> NSMutableString;则是深拷贝。
Note :只有一种情况下是发生浅拷贝:不可变对象复制到不可变对象。
-
除了以上这种情形外,其他都是深拷贝。//浅拷贝:指针拷贝 不会产生新的对象,原对象计数器加1 void strCopy(){ NSString *str=[[NSString alloc]initWithFormat:@"abcd"]; //因为NSString对象本身就不可变,所以并没产生新的对象,而是返回对象本身,会做一次retain操作,所以原对象也会retain NSString *str2=[str copy]; //输出二者的地址,二者的地址是相同的 NSLog(@"str --> %p",str); NSLog(@"str2 --> %p",str2); }
-
2. 对于自定义对象的Copy:该类必须实现NSCopying协议,重写 copyWithZone方法。
同理,对于自定义对象的mutableCopy:必须实现 NSMutableCopying协议,重写 mutableCopyWithZone方法。
在NSCopying协议中,其实只有一个协议方法, 如下:
在NSMutableCopying协议:@protocol NSCopying - (id)copyWithZone:(NSZone *)zone; @end
下面给出一个例子:@protocol NSMutableCopying - (id)mutableCopyWithZone:(NSZone *)zone; @end
-
// // Person.h // test_1 // // Created by lin on 14-8-26. // Copyright (c) 2014年 linshaolie. All rights reserved. // #import <Foundation/Foundation.h> @interface Person : NSObject<NSCopying> @property(nonatomic,copy) NSString *name; @property(nonatomic,assign) int age; -(id)initWithName:(NSString*)name andAge:(int)age; @end
testing...// // Person.m // test_1 // // Created by lin on 14-8-26. // Copyright (c) 2014年 linshaolie. All rights reserved. // #import "Person.h" @implementation Person -(instancetype)initWithName:(NSString*)name andAge:(int)age { if ( self = [super init] ) { self.age = age; self.name = name; } return self; } -(id)copyWithZone:(NSZone *)zone { Person* person = [[[self class] allocWithZone:zone] initwithName:self.name andAge:self.age]; return person; } @end
// // main.m // test_1 // // Created by lin on 14-8-26. // Copyright (c) 2014年 linshaolie. All rights reserved. // #import <Foundation/Foundation.h> #import "Person.h" #import "Student.h" int main(int argc, const char * argv[]) { @autoreleasepool { Person *per1 = [[Person alloc] initWithName:@"张三" andAge:12]; Person *per2 = [p1 copy]; //输出两个 Person 对象的地址,二者是不同的 NSLog(@"per1 --> %p",per1); NSLog(@"per2 --> %p",per2); } return 0; }
问:如果加入对于某些自定义对象是不可变的,怎么办呢?
答:只需要直接返回self就行了,如下:
-
这样,输出的两个对象的地址就是相同的了。-(id)copyWithZone:(NSZone *)zone { return self; }
-
下面了解一下关于如果某一个自定义类继承了 这个Person类的情况。
如果某一个子类继承了实现了NSCopying协议的基类,那么该子类也是会自动继承这个协议的方法。但是需要自己重新实现。
例如:有一个Student子类继承了这个Person类:
// // Student.h // test_1 // // Created by lin on 14-8-26. // Copyright (c) 2014年 linshaolie. All rights reserved. // #import <Foundation/Foundation.h> #import "Person.h" @interface Student : Person @property(nonatomic,copy)NSString *school; -(id)initWithName:(NSString *)name andAge:(int)age andSchool:(NSString*)school; @end
testing...// // Student.m // test_1 // // Created by lin on 14-8-26. // Copyright (c) 2014年 linshaolie. All rights reserved. // #import "Student.h" #import "Person.h" @implementation Student -(id)initWithName:(NSString *)name andAge:age andSchool:(NSString*)school { self = [super initWithName:name andAge:name]; if ( self != nil ) { self.school = school; } return self; } -(id)copyWithZone:(NSZone *)zone { Student *student = [super copyWithZone:zone]; student.school = self.school; return student; } @end
-
// // main.m // test_1 // // Created by lin on 14-8-26. // Copyright (c) 2014年 linshaolie. All rights reserved. // #import <Foundation/Foundation.h> #import "Person.h" #import "Student.h" int main(int argc, const char * argv[]) { @autoreleasepool { Student *student1 = [[Student alloc] initWithName:@"小明" andAge:20]; Student *student2 = [student1 copy]; //打印不同地址 NSLog(@"student1 --> %p", student1); NSLog(@"student2 --> %p", student2); } return 0; }
- 注意其中copyWithZone方法的实现。
- 最后,记录下另外一种实现深复制的方法:使用Foundation的归档技术。下面是代码:
-
NSArray *dataArray1 = @[@"aaa", @"bbb", @"ccc"]; NSData *data = [NSKeyedArchiver archivedDataWithRootObject:dataArray1]; NSMutableArray *dataArray2 = [NSKeyedUnarchiver unarchiveObjectWithData:data]; // 上述即实现了将dataArray1深复制给dataArray2,另外一种简便写法是: NSLog(@"dataArray1 --> %p ", dataArray1); NSLog(@"dataArray2 --> %p ", dataArray2);