在前一篇文章中我们说到了如何解决对象的循环引用问题,这一篇文章我们就来介绍一下OC中的对象拷贝概念,这个对于面向对象语言中都会有这种的问题,只是不同的语言有不同的解决方式:C++中有拷贝构造函数,Java中需要实现Cloneable接口,在clone方法中进行操作。但是不过OC更偏向于Java这种方式,OC中如果一个对象需要被拷贝,他需要实现协议:
<NSCopying>、<NSMutableCopying>
从名字上我们可以看到,一个协议是用于不可变对象的,一个协议适用于可变对象的
首先来介绍一下对象的拷贝的概念吧:
为什么要有对象的拷贝这么一个概念呢?看一个场景:假如现在一个对象中有一个数组对象,现在我们生成一个对象,同时将这个对象赋值给另外一个对象,那么现在问题是这两个对象中的数组对象是同一个,那么如果一个对象中去修改这个数值中的内容,另外一个对象中的数组内容也会被修改,相当于这个数组对象是共享的,当然我们有时候是不希望这种形式的出现的,这时候我们就出现了对象的拷贝。
具体来看一个例子吧
一、系统类对象的拷贝
1 // 2 // main.m 3 // 30_CopyObject 4 // 5 // Created by jiangwei on 14-10-13. 6 // Copyright (c) 2014年 jiangwei. All rights reserved. 7 // 8 9 #import <Foundation/Foundation.h> 10 11 /** 12 13 */ 14 int main(int argc, const charchar * argv[]) { 15 @autoreleasepool { 16 17 //对象具备拷贝功能,必须实现如下协议 18 //<NSCopying>、<NSMutableCopying> 19 20 //copy方法返回的是一个不可变对象,mutableCopy方法返回的是一个可变对象 21 22 /* 23 NSMutableArray *array1 = [NSMutableArray arrayWithObjects:@"one",@"two",nil]; 24 NSMutableArray *array2 = [array1 retain]; 25 //retain只是引用计数+1,没有创建新的对象 26 //array1与array2指针相同,指向同一个对象 27 if(array1 == array2){ 28 NSLog(@"array1 == array2"); 29 NSLog(@"array1的引用计数:%ld",array1.retainCount); 30 } 31 */ 32 33 NSMutableArray *array1 = [NSMutableArray arrayWithObjects:@"one",@"two",nil]; 34 //复制对象,创建一个新的副本对象 35 //这里使用copy方法复制,返回的是一个不可变数组,但是用一个可变数组来声明,但是我们关心的是指针的的内容,而不是类型 36 //所以array2的真实类型还是不可变类型的 37 NSMutableArray *array2 = [array1 copy];//array2计数为:1,因为是新创建出来的对象 38 39 //使用mutableCopy方法,返回的就是可变数组 40 //当然这种方法只针对于那些有可变对象之分有用,对于其他的对象这个方法和copy方法的效果是一样的 41 NSMutableArray *array3 = [array1 mutableCopy]; 42 43 if(array1 != array2){ 44 NSLog(@"array1 != array2"); 45 NSLog(@"array1的引用计数:%ld",array1.retainCount); 46 NSLog(@"array2的引用计数:%ld",array2.retainCount); 47 } 48 [array2 release]; 49 [array1 release]; 50 51 52 } 53 return 0; 54 }
我们看到,NSMutableArray有一个mutableCopy方法,这样返回的一个数组对象就是一个拷贝对象了。
但是这里需要注意的是:
copy方法和mutableCopy方法的区别
这两个方法的区别只在于那些有可变对象和不可变对象之分的对象上,对于没有这种区分的对象来说,这两个方法的效果是一样的。
[不可变对象 copy]是假拷贝,等价于[不可变对象 retain]
[不可变对象 mutableCopy]是真拷贝
二、深拷贝和浅拷贝
在拷贝对象中也是有深拷贝和浅拷贝之分的
浅拷贝:只拷贝所有属性对象的指针
深拷贝:拷贝属性对象的内容
看个例子:
Person.h
1 // 2 // Person.h 3 // 31_DeepCopy 4 // 5 // Created by jiangwei on 14-10-13. 6 // Copyright (c) 2014年 jiangwei. All rights reserved. 7 // 8 9 #import <Foundation/Foundation.h> 10 11 @interface Person : NSObject <NSCopying> 12 13 @property(nonatomic,retain)NSMutableArray *apples; 14 @property(nonatomic)int age; 15 16 @end
Person.m
1 // 2 // Person.m 3 // 31_DeepCopy 4 // 5 // Created by jiangwei on 14-10-13. 6 // Copyright (c) 2014年 jiangwei. All rights reserved. 7 // 8 9 #import "Person.h" 10 11 @implementation Person 12 13 - (id)copyWithZone:(NSZone *)zone{ 14 //创建一个新的副本对象 15 //这个方法是会被继承的,所以这里还是不用 16 //[Person allocWithZone:<#(struct _NSZone *)#>]; 17 Person * p = [[self class] allocWithZone:zone]; 18 //p.apples = _apples;//是指针赋值,所以还是浅拷贝 19 //深拷贝 20 //拷贝之后引用计数会+1,需要release以下 21 p.apples = [_apples mutableCopy]; 22 p.age = _age; 23 24 [p.apples release]; 25 26 //但是如果我们使用->语法就不需要了,因为我们没有使用set方法,引用计数没有操作 27 //但是这种方式我们不采用 28 //p->_apples = [_apples mutableCopy]; 29 30 return p; 31 } 32 33 @end
我们看到,Person实现了NSCopying协议,然后需要实现一个方法:copyWithZone
在这个方法中我们开始进行拷贝操作:
Person类中有一个属性类型是数组
这里我们需要生成一个Person对象,然后进行属性的拷贝,最后在返回这个对象
浅拷贝:直接复制数组指针
深拷贝:直接复制数组的内容,这里可以直接使用mutableCopy方法进行实现
测试类
main.m
1 // 2 // main.m 3 // 31_DeepCopy 4 // 5 // Created by jiangwei on 14-10-13. 6 // Copyright (c) 2014年 jiangwei. All rights reserved. 7 // 8 9 #import <Foundation/Foundation.h> 10 #import "Person.h" 11 12 //深拷贝和浅拷贝 13 //默认是浅拷贝 14 int main(int argc, const charchar * argv[]) { 15 @autoreleasepool { 16 17 NSMutableArray *array1 = [NSMutableArray arrayWithCapacity:2]; 18 19 for(int i=0;i<2;i++){ 20 Person *p = [[Person alloc] init]; 21 [array1 addObject:p]; 22 [p release]; 23 } 24 25 //引用计数都是1 26 for(Person *p in array1){ 27 NSLog(@"复制之前的引用计数:%ld",p.retainCount); 28 NSLog(@"复制之前的指针:%p",p); 29 } 30 31 //引用计数都是2,因为是浅拷贝,又有指针指向对象了,array2也是使用了person 32 //浅拷贝:只拷贝对象指针 33 //深拷贝:复制属性 34 NSArray *array2 = [array1 copy]; 35 for(Person *p in array2){ 36 NSLog(@"复制之前的引用计数:%ld",p.retainCount); 37 NSLog(@"复制之前的指针:%p",p); 38 } 39 40 //这里Person中有一个属性是NSMutableArray,但是我们只是赋值,并不是拷贝 41 //所以这里还不算是深拷贝 42 Person *p = [[Person alloc] init]; 43 p.apples = [NSMutableArray arrayWithObjects:@"iphone",@"ipad", nil nil]; 44 p.age = 20; 45 46 Person *p1 = [p copy]; 47 48 if(p != p1){ 49 NSLog(@"p1.age=%d",p1.age); 50 NSLog(@"p1.apples=%@",p1.apples); 51 } 52 53 } 54 return 0; 55 }
三、字符串的拷贝
1 // 2 // main.m 3 // 32_NSStringCopy 4 // 5 // Created by jiangwei on 14-10-13. 6 // Copyright (c) 2014年 jiangwei. All rights reserved. 7 // 8 9 #import <Foundation/Foundation.h> 10 11 #import "Person.h" 12 13 //字符串为什么使用copy 14 int main(int argc, const charchar * argv[]) { 15 @autoreleasepool { 16 Person *p = [[Person alloc] init]; 17 NSMutableString *name = [NSMutableString stringWithString:@"jack"]; 18 p.name = name; 19 20 //人的名字被修改了 21 //如果Person的name是retain,则此处的name和person对象的name执行的是同一个字符串对象 22 //此处的name修改之后,会导致person的name也被修改,破坏了person对象的封装性 23 //正常情况下,我们会使用set方法设置名字 24 //所以如果使用的是copy的话,就不会修改名字了 25 [name appendString:@"-tom"]; 26 27 //Foundation框架中可复制的对象,当我们拷贝的是一个不可变对象时候 28 //他的作用相当于retain(系统做的内存优化) 29 30 //所以这里的如果换成NSString类型的时候,其实没有拷贝的动作的,因为NSString是不可变的 31 //但是使用mutableCopy就可以做到拷贝了,mutableCopy是真正意义上的拷贝 32 //mutableCopy拷贝方法,不管什么对象都是真实拷贝 33 34 //[不可变对象 copy]是假拷贝,等价于[不可变对象 retain] 35 //[不可变对象 mutableCopy是真拷贝 36 } 37 return 0; 38 }
这里为什么要单独说一下字符串的拷贝呢?
因为字符串是一个特殊的对象,我们应该调用他的copy方法。因为我们对于字符串其实我们是期望他只有一分值得,就看上面的例子:
我们用NSMutableString产生一个name,然后将其赋值给person对象,当我们在外面修改name的内容的时候,其实person的name属性的值也应该修改。所以我们一般在拷贝字符串对象的时候,都会调用他的copy方法
总结
这一篇文章主要介绍了OC中对象拷贝的相关概念和知识点。我们在操作对象的时候,有时候进行拷贝,还要仔细考虑一下是深拷贝还是浅拷贝。