OC基础16:复制对象

1、浅复制即是指针赋值,复制对象的修改会影响被复制对象;深复制即是真正的对象复制,会复制出来一个副本,这个副本和原对象是完全分开的。

   Foundation类实现了两个复制的方法:copy和mutableCopy。copy复制出来的是一个不可变的副本,mutableCopy复制出来的是一个可变的副本。

   同时注意,产生一个对象的可变副本并不要求被复制的对象本身是可变的,同样的,产生一个不可变副本也不要求被复制对象本身是不可变的。

 

2、在iOS中,并不是所有的对象都支持copy和mutableCopy,只有遵守NSCopying协议的类可以发送copy消息,遵守NSMutableCopying协议的类才可以发送mutableCopy消息。但是默认的自定义类并没有遵守这两个协议。所以如果想自定义使用copy,那么就必须遵守NSCopying协议,并且实现copyWithZone:方法,如果想自定义使用mutableCopy,那么就必须遵守NSMutableCopying协议,并且实现mutableCopyWithZone:方法。

 

3、深复制和浅复制的操作:

iOS提供了copy和mutablecopy两个方法,顾名思义,copy方法复制出来的是一个不可变对象,而mutablecopy就是复制出来的就是一个mutable的对象。以下将分两种情况来说明:

(1)、系统的非容器类对象(即是指NSString、NSNumber等这一类的对象)情况1:

           ...

       NSString *s = @"abcd";

       NSString *sCopy = [s copy];

       NSMutableString *mutaSMutaCopy = [s mutableCopy];

       [mutaSMutaCopy appendString:@"ef"];

           ... 

       在这种情况下,s和sCopy指向的是同一块内存区域,此时sCopy的引用计数和s一样都为2。而sMCopy则是我们所认为的真正意义上的复制,系统为其分配了新内存,但指针所指向的字符串还是和s所指的一样。

(2)、系统的非容器类对象情况2:

         ...

         NSMutableString *s = [NSMutableString stringWithString: @"abcd"];

         NSString *sCopy = [s copy];

         NSMutableString *mutaSCopy = [s copy];  //Mutable S Copy,它其实是不可变的

         NSMutableString *sMutaCopy = [s mutableCopy];

         [mutaSCopy appendString:@"ef"];  //这句会产生错误,因为mSCopy其实不可变的

         [s appendString:@"ef"];

         [sMutaCopy appendString:@"gh"]; 

         ...

以上四个NSString对象所分配的内存都是不一样的。由于mutaSCopy其实是个不可变对象,所以会有上述的报错。所以对于系统的非容器类对象,我们可以认为:

对不可变对象做复制,copy是指针复制(浅复制),而mutableCopy就是对象复制(深复制);如果是对可变对象进行复制,就全部都是深复制,这种情况需要注意:使用copy复制返回的对象无论如何都会是不可变的。

(3)、系统的容器类对象 (即是指NSArray、NSDictionary类等)情况1:

对于容器类,上面的结论也是适用的,需要探讨的是复制后容器内对象的变化,看以下代码:

        ...

        NSArray *a = [NSArray arrayWithObjects:@"a",@"b",@"c",nil];

        NSArray *aCopy = [a copy];

        ...

        aCopy和a是同一个NSArray对象(指向相同的对象),包括array里面的元素也是指向相同的指针,所以只是指针复制。再看下一个语句:

        ...

        NSMutableArray *aMCopy = [a mutableCopy];

        ...

        aMCopy是a的可变副本,指向的对象就和a不同了,但是其中的元素和a中的元素指向的仍然是同一个对象。aMCopy还可以修改自己的对象:

        ...

        [aMCopy addObject:@"de"];

        [aMCopy removeObjectAtIndex: 0];

        ...

从上面的例子可以看出:aCopy是指针复制,而aMCopy是对象复制,aMCopy还可以删除或添加其元素。但是需要注意的是,容器内的元素的内容都是指针复制

(4)、系统的容器类对象情况2:

下面再看另一种情况:

        ...

        NSArray *a = [NSArray arrayWithObjects: [NSMutableString stringWithString: @"a"], @"b", @"c", nil];

        NSArray *aCopy = [a copy];

        NSMutableArray *aMCopy = [a mutableCopy];

        ...

        aCopy,aMCopy和a指向的都是不一样的对象,但是其中的元素都是一样的,因为元素内容存储的是指针,还是指向同一个位置。看以下代码:

        ...

        NSMutableString *testString = [a objectAtIndex:0];

        [testString appendString:@" tail"];

        ...

然后可以发现三个数组的首元素都被改变。注意,在这里进行赋值的时候,使用testString = @"tail"这样赋值是不行的,因为这样其实是改变了testString的指针指向,将@“tail”临时对象赋给了testString。

(5)、由此可见,对于容器而言,其元素对象始终是指针复制。如果需要元素对象也是对象复制,就需要实现深拷贝:

        NSArray *array = [NSArray arrayWithObjects: [NSMutableString stringWithString: @"first"] ,[NSString stringWithString:@"b"], @"c", nil];

        NSArray *deepCopyArray=[[NSArray alloc] initWithArray: array copyItems: YES];

        NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:

        [NSKeyedArchiver archivedDataWithRootObject: array]];

trueDeepCopyArray是完全意义上的深拷贝,而deepCopyArray则还不是,对于deepCopyArray内的不可变元素,deepCopyArray使用的还是指针复制。

因为如果容器的某一元素是不可变的,那你复制完后该对象仍旧是不能改变的,因此只需要指针复制即可。这种情况下,哪怕你对容器内的元素重新赋值,也不会影响到其他的容器。举个例子,使用[array objectAtIndex: 0] = @”sd”之后,其他的容器内对象并不会受影响。

[array objectAtIndex: 1]和[deepCopyArray objectAtIndex: 0]尽管是指向同一块内存,但是我们没有办法对其进行修改——因为它是不可改变的。所以指针复制已经足够。所以虽然这并不是完全意义上的深拷贝,但是apple的官方文档将其列为deep copy了。

 

4、使用Foundation类的copy或mutableCopy方法是,深复制并不是默认执行的。从3中也可以看出,对不可变对象使用copy的效果也仅仅是指针复制。

 

5、关于实现自定义类的copy方法:

   (1)、首先自定义类必须是Foundation类的子类;

   (2)、要为自定义类实现copy方法,必须先实现<NSCopying>协议,再重载copyWithZone方法;要为自定义类实现mutableCopy方法,必须先实现<NSMutableCopying>协议,然后重载mutableCopyWithZone方法;因为自定义类的copy和mutableCopy是通过调用copyWithZone和mutableCopyWithZone来实现的;

   (3)、以自定义分数类Fraction类为例,它含有两个实例变量:分母d和分子n,要实现这个类的copy方法,首先应该在@interface部分引入<NSCopying>协议:

       @interface Fraction: NSObject <NSCopying>

       然后在@implementation部分添加以下内容来重载copyWithZone方法:

       - (id) copyWithZone: (NSZone *) zone {

         id newFraction = [[[self class] allocWithZone] init]; 

         //这么写是考虑到可能有子类继承会这个方法

         [newFraction setTo: n over: d];   //使用原有的设值方法

         return newFraction;

       }

   (4)、如果父类本身已实现了copy或者mutableCopy方法,那么在重载的过程中应先调用父类的copy方法,再添加附加的内容;

  

6、在合成设置属性的时候,如果使用以下语句合成属性:

   @property (nonatomic, copy) NSString *name;

   ...

   @synthesize name;

   ...

   那么编译器自动生成的setter方法是:

   -(void) setName: (NSString *) theName {

     if(theName != name)

     name = [theName copy];

   }

   其实也就是使用了copy方法了。

 

7、属性没有mutableCopy特性。即使是可变的实例变量也是使用copy方法去复制,所以都会生成一个对象的不可变副本。

 

8、在copy方法中,如果正在复制的实例变量包含不可变的对象(比如不可变的字符串对象),那么系统其实并没有生成这个对象内容的副本。仅仅生成一个对于这个对象的新引用,就足够了,因为反正它也是不可变的,不可能会有改动副本影响到原对象这种情况发生。

 

你可能感兴趣的:(OC基础16:复制对象)