转自http://blog.csdn.net/jiangwei0910410003/article/details/41926531
在前一篇文章中我们说到了如何解决对象的循环引用问题:http://blog.csdn.net/jiangwei0910410003/article/details/41926369,这一篇文章我们就来介绍一下OC中的对象拷贝概念,这个对于面向对象语言中都会有这种的问题,只是不同的语言有不同的解决方式:C++中有拷贝构造函数,Java中需要实现Cloneable接口,在clone方法中进行操作。但是不过OC更偏向于Java这种方式,OC中如果一个对象需要被拷贝,他需要实现协议:
<NSCopying>、<NSMutableCopying>
从名字上我们可以看到,一个协议是用于不可变对象的,一个协议适用于可变对象的
首先来介绍一下对象的拷贝的概念吧:
为什么要由对象的拷贝这么一个概念呢?看一个场景:假如现在一个对象A中有一个数组对象,现在我们生成一个对象A,同时将这个对象A赋值给另外一个对象B,那么现在问题是这两个对象中的数组对象是同一个(这两个对象的内容相同,只是指针不同,但是这两个对象的指针都是指向同一个数组的),那么如果一个对象A中去修改这个数值中的内容,另外一个对象B中的数组内容也会被修改,相当于这个数组对象是共享的,当然我们有时候是不希望这种形式的出现的,这时候我们就出现了对象的拷贝。
具体来看一个例子吧
一、系统类对象的拷贝
main.m
- #import <Foundation/Foundation.h>
- /**
- */
- int main(int argc, const charchar * argv[]) {
- @autoreleasepool {
- //对象具备拷贝功能,必须实现如下协议
- //<NSCopying>、<NSMutableCopying>
- //copy方法返回的是一个不可变对象,mutableCopy方法返回的是一个可变对象
- /*
- NSMutableArray *array1 = [NSMutableArray arrayWithObjects:@"one",@"two",nil];
- NSMutableArray *array2 = [array1 retain];
- //retain只是引用计数+1,没有创建新的对象
- //array1与array2指针相同,指向同一个对象
- if(array1 == array2){
- NSLog(@"array1 == array2");
- NSLog(@"array1的引用计数:%ld",array1.retainCount);
- }
- */
- NSMutableArray *array1 = [NSMutableArray arrayWithObjects:@"one",@"two",nil];
- //复制对象,创建一个新的副本对象
- //这里使用copy方法复制,返回的是一个不可变数组,但是用一个可变数组来声明,但是我们关心的是指针的的内容,而不是类型
- //所以array2的真实类型还是不可变类型的
- NSMutableArray *array2 = [array1 copy];//array2计数为:1,因为是新创建出来的对象
- //使用mutableCopy方法,返回的就是可变数组
- //当然这种方法只针对于那些有可变对象之分有用,对于其他的对象这个方法和copy方法的效果是一样的
- NSMutableArray *array3 = [array1 mutableCopy];
- if(array1 != array2){
- NSLog(@"array1 != array2");
- NSLog(@"array1的引用计数:%ld",array1.retainCount);
- NSLog(@"array2的引用计数:%ld",array2.retainCount);
- }
- [array2 release];
- [array1 release];
- }
- return 0;
- }
但是这里需要注意的是:
copy方法和mutableCopy方法的区别
这两个方法的区别只在于那些有可变对象和不可变对象之分的对象上,对于没有这种区分的对象来说,这两个方法的效果是一样的。
[不可变对象 copy]是假拷贝,等价于[不可变对象 retain]
[不可变对象 mutableCopy是真拷贝
二、深拷贝和浅拷贝
在拷贝对象中也是有深拷贝和浅拷贝之分的
浅拷贝:只拷贝所有属性对象的指针
深拷贝:拷贝属性对象的内容
看个例子:
Person.h
- //
- // Person.h
- // 31_DeepCopy
- //
- // Created by jiangwei on 14-10-13.
- // Copyright (c) 2014年 jiangwei. All rights reserved.
- //
- #import <Foundation/Foundation.h>
- @interface Person : NSObject <NSCopying>
- @property(nonatomic,retain)NSMutableArray *apples;
- @property(nonatomic)int age;
- @end
- //
- // Person.m
- // 31_DeepCopy
- //
- // Created by jiangwei on 14-10-13.
- // Copyright (c) 2014年 jiangwei. All rights reserved.
- //
- #import "Person.h"
- @implementation Person
- - (id)copyWithZone:(NSZone *)zone{
- //创建一个新的副本对象
- //这个方法是会被继承的,所以这里还是不用
- //[Person allocWithZone:<#(struct _NSZone *)#>];
- Person * p = [[self class] allocWithZone:zone];
- //p.apples = _apples;//是指针赋值,所以还是浅拷贝
- //深拷贝
- //拷贝之后引用计数会+1,需要release以下
- p.apples = [_apples mutableCopy];
- p.age = _age;
- [p.apples release];
- //但是如果我们使用->语法就不需要了,因为我们没有使用set方法,引用计数没有操作
- //但是这种方式我们不采用
- //p->_apples = [_apples mutableCopy];
- return p;
- }
- @end
我们看到,Person实现了NSCopying协议,然后需要实现一个方法:copyWithZone
在这个方法中我们开始进行拷贝操作:
Person类中有一个属性类型是数组
这里我们需要生成一个Person对象,然后进行属性的拷贝,最后在返回这个对象
浅拷贝:直接复制数组指针
深拷贝:直接复制数组的内容,这里可以直接使用mutableCopy方法进行实现
测试类
main.m
- //
- // main.m
- // 31_DeepCopy
- //
- // Created by jiangwei on 14-10-13.
- // Copyright (c) 2014年 jiangwei. All rights reserved.
- //
- #import <Foundation/Foundation.h>
- #import "Person.h"
- //深拷贝和浅拷贝
- //默认是浅拷贝
- int main(int argc, const charchar * argv[]) {
- @autoreleasepool {
- NSMutableArray *array1 = [NSMutableArray arrayWithCapacity:2];
- for(int i=0;i<2;i++){
- Person *p = [[Person alloc] init];
- [array1 addObject:p];
- [p release];
- }
- //引用计数都是1
- for(Person *p in array1){
- NSLog(@"复制之前的引用计数:%ld",p.retainCount);
- NSLog(@"复制之前的指针:%p",p);
- }
- //引用计数都是2,因为是浅拷贝,又有指针指向对象了,array2也是使用了person
- //浅拷贝:只拷贝对象指针
- //深拷贝:复制属性
- NSArray *array2 = [array1 copy];
- for(Person *p in array2){
- NSLog(@"复制之前的引用计数:%ld",p.retainCount);
- NSLog(@"复制之前的指针:%p",p);
- }
- //这里Person中有一个属性是NSMutableArray,但是我们只是赋值,并不是拷贝
- //所以这里还不算是深拷贝
- Person *p = [[Person alloc] init];
- p.apples = [NSMutableArray arrayWithObjects:@"iphone",@"ipad", nil nil];
- p.age = 20;
- Person *p1 = [p copy];
- if(p != p1){
- NSLog(@"p1.age=%d",p1.age);
- NSLog(@"p1.apples=%@",p1.apples);
- }
- }
- return 0;
- }
三、字符串的拷贝
- //
- // main.m
- // 32_NSStringCopy
- //
- // Created by jiangwei on 14-10-13.
- // Copyright (c) 2014年 jiangwei. All rights reserved.
- //
- #import <Foundation/Foundation.h>
- #import "Person.h"
- //字符串为什么使用copy
- int main(int argc, const charchar * argv[]) {
- @autoreleasepool {
- Person *p = [[Person alloc] init];
- NSMutableString *name = [NSMutableString stringWithString:@"jack"];
- p.name = name;
- //人的名字被修改了
- //如果Person的name是retain,则此处的name和person对象的name执行的是同一个字符串对象
- //此处的name修改之后,会导致person的name也被修改,破坏了person对象的封装性
- //正常情况下,我们会使用set方法设置名字
- //所以如果使用的是copy的话,就不会修改名字了
- [name appendString:@"-tom"];
- //Foundation框架中可复制的对象,当我们拷贝的是一个不可变对象时候
- //他的作用相当于retain(系统做的内存优化)
- //所以这里的如果换成NSString类型的时候,其实没有拷贝的动作的,因为NSString是不可变的
- //但是使用mutableCopy就可以做到拷贝了,mutableCopy是真正意义上的拷贝
- //mutableCopy拷贝方法,不管什么对象都是真实拷贝
- //[不可变对象 copy]是假拷贝,等价于[不可变对象 retain]
- //[不可变对象 mutableCopy是真拷贝
- }
- return 0;
- }
因为字符串是一个特殊的对象,我们应该调用他的copy方法。因为我们对于字符串其实我们是期望他只有一分值得,就看上面的例子:
我们用NSMutableString产生一个name,然后将其赋值给person对象,当我们在外面修改name的内容的时候,其实person的name属性的值也应该修改。所以我们一般在拷贝字符串对象的时候,都会调用他的copy方法
总结
浅 复 制:在复制操作时,对于被复制的对象的每一层复制都是指针复制。
深 复 制:在复制操作时,对于被复制的对象至少有一层复制是对象复制。
完全复制:在复制操作时,对于被复制的对象的每一层复制都是对象复制。
注:1、在复制操作时,对于对象有n层是对象复制,我们可称作n级深复制,此处n应大于等于1。
2、对于完全复制如何实现(目前通用的办法是:迭代法和归档),这里后续是否添加视情况而定,
暂时不做讲解。
3、指针复制俗称指针拷贝,对象复制也俗称内容拷贝。
4、一般来讲,
浅层复制:复制引用对象的指针。
深层复制:复制引用对象内容。
retain:始终是浅复制。引用计数每次加一。返回对象是否可变与被复制的对象保持一致。
copy:对于可变对象为深复制,引用计数不改变;对于不可变对象是浅复制,
引用计数每次加一。始终返回一个不可变对象。
mutableCopy:始终是深复制,引用计数不改变。始终返回一个可变对象。
不可变对象:值发生改变,其内存首地址随之改变。
可变对象:无论值是否改变,其内存首地址都不随之改变。
引用计数:为了让使用者清楚的知道,该对象有多少个拥有者(即有多少个指针指向同一内存地址)。
最近有一个好朋友问我,什么时候用到深浅复制呢?那么我就把我所总结的一些分享给大家,希望能帮助你们更好的理解深浅复制!
那么先让我们来看一看下边数组类型的转换
1、不可变对象→可变对象的转换:
NSArray *array1=[NSArrayarrayWithObjects:@"a",@"b",@"c",@"d",nil];
NSMutableArray *str2=[array1 mutableCopy];
2、可变对象→不可变对象的转换:
NSMutableArray *array2 = [NSMutableArray arrayWithObjects:@"aa",@"bb",@"cc",@"dd",nil];
NSArray *array1=[ array2 Copy];
3、可变对象→可变对象的转换(不同指针变量指向不同的内存地址):
NSMutableArray *array1=[NSMutableArray arrayWithObjects:@"a",@"b",@"c",@"d",nil];
NSMutableArray *str2=[array1 mutableCopy];
通过上边的两个例子,我们可轻松的将一个对象在可变和不可变之间转换,并且这里不用考虑内存使用原则(即引用计数的问题)。没错,这就是深拷贝的魅力了。
4、同类型对象之间的指针复制(不同指针变量指向同一块内存地址):
a、
NSMutableString *str1=[NSMutableString stringWithString:@"twoday"];
NSMutableString *str2=[str1 retain];
[str1 release];
b、
NSArray *array1=[NSArrayarrayWithObjects:@"a",@"b",@"c",@"d",nil];
NSArray *str2=[array1 Copy];
[array1release];
通俗的讲,多个指针同时指向同一块内存区域,那么这些个指针同时拥有对该内存区的所有权。所有权的瓜分过程,这时候就要用到浅拷贝了。
则简化为:
问:什么时候用到深浅拷贝?
答:深拷贝是在要将一个对象从可变(不可变)转为不可变(可变)或者将一个对象内容克隆一份时用到;
浅拷贝是在要复制一个对象的指针时用到。