美文网首页
Copy和MutableCopy的区分

Copy和MutableCopy的区分

作者: 大白简先生 | 来源:发表于2016-12-19 17:02 被阅读0次

预备知识

内存的栈区:由编译器自动分配释放存放函数的参数值,局部变量的值等,其操作方式类似于数据结构中的栈
内存的堆区:一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收,主要以他与数据结构中的堆是两码事,分配方式倒是类似于链表

关于浅拷贝,简单来说,就像是人与人的影子一样,也可以说是指针拷贝,而深拷贝就像是我们人类,虽然都是被统一称为人,但是人与人之间是相互独立的,也可以说是内容拷贝,会产生一个新的对象,新的指针。
通常我们都有这样的误解,任务浅拷贝就是用copy,深拷贝就是用mutableCopy。这种理解是错误的,一定要更正过来。copy只是不可变拷贝,而mutableCopy是可变拷贝。比如,NSArray *arr = [modelsArray copy],那么arr是不可变的,而NSMutableArray *ma = [modelsArray mutableCopy],那么ma是可变的。

/*系统对象的copy与mutableCopy方法
     不管是集合类对象(数组,字典,set等)还是非集合类对象,接收到copy和mutableCopy消息时,都遵循一下准则
     1.copy返回的都是imutable(不可变)对象,所以如果对copy返回值使用mutable对象接口时就会crash
     2.mutablecopy返回mutable(可变)对象
     3.在非集合类对象中,对immutable对象进行copy操作,是指针复制,mutablecopy操作是内容复制,对mutable对象不管是进行copy还是mutablecopy操作是都是内容复制
     [immutableObject copy] 浅复制
     [immutableObject mutableCopy] 深复制
     [mutableObject copy] 深复制
     [mutableObject mutableCopy] 深复制
    4. 在集合类对象中,对immutable对象进行copy操作时,是指针复制,mutableCopy是内容复制,对mutable对象进行copy和mutableCopy都是内容复制。但是集合对象的内容复制仅限于对象本身,对象元素的指针仍然是指针复制,也就是说不管是源头对象还是复制对象,其中某一个对象元素的值发生改变,另外一个也会跟着改变
     [immutableObject copy] 浅复制
     [immutableObejct mutableCopy] 单层深复制
     [mutableObject copy] //单层深复制
     [mutableObejct mutableCopy]//单层深复制
     */
5.默认状况下,深拷贝指的都是不完全深拷贝,如果要实现完全深拷贝,则要重写copyWithZone:方法,自行实现完全深拷贝的需求,大体思路如下,再copyWithZone:里对象赋值不直接赋值而是通过copy方法即可实现。

我们来通过代码来验证一下
我们先来看看集合类对象

 //可变数组
    NSMutableArray *modelArr = [NSMutableArray arrayWithObjects:@"hello",@"world", nil];;
    NSMutableArray *copyArr = [modelArr copy];
    [copyArr addObject:@"!"];//运行到这里会闪退,应该返回的是不可变对象,而我们调用了可变对象的方法addObject
18C4ADB1-D557-416B-A4CD-B827E46B33C0.png
NSMutableArray *modelArr = [NSMutableArray arrayWithObjects:@"hello",@"world", nil];
    NSMutableArray *mutableArr = [modelArr mutableCopy];
    [mutableArr addObject:@"!"];
    NSLog(@"%@",mutableArr);
    NSLog(@"%@",modelArr);
BF691631-DD82-4835-952A-50406E9080A8.png
//不可变数组
    NSArray *arr = @[@"hello",@"world"];
    NSMutableArray *mutableArr = [arr mutableCopy];
    [mutableArr addObject:@"!"];
    NSLog(@"%@",mutableArr);
B20EF05C-7951-4F45-9B42-66EF2BCC8B41.png
非集合类对象
//可变字符串
    NSMutableString *mutableStr = [NSMutableString stringWithFormat:@"hello world"];
    NSMutableString *copyStr= [mutableStr copy];
    //[copyStr appendString:@"!"];//这里会闪退,因为是copy操作,返回的是不可变对象,即使他用的是NSMutableString类型做前缀
    NSMutableString *mutableCopyStr = [mutableStr mutableCopy];
     [mutableCopyStr appendString:@"!"];
//不可变字符串
    NSString *str = @"hello world";
    NSMutableString *copyStr= [str copy];
    [copyStr appendString:@"!"];//这里会闪退,因为是copy操作,返回的是不可变对象,即使他用的是NSMutableString类型做前缀
    NSMutableString *mutableCopyStr = [str mutableCopy];
    [mutableCopyStr appendString:@"!"];

再来看看在非集合类对象中,对immutable对象进行copy或者mutableCopy操作,是指针复制还是是内容复制,对mutable对象不管是进行copy还是mutablecopy操作是都是内容复制

在写代码的过程中出现了一个让我很疑惑的问题,看下面代码,既然说immutable对象进行copy是指针复制,但是我对copyStr重新赋值或者设置nil,那么原有的字符串是不是也应该改变,但是打印出来的结果完全不是我想的那样!通过下面的打印结果我们可以分析出,在对字符串做copy操作时,原变量的值所指向的内存地址和copy变量是一样的,但是他们两个变量自身的内存地址是不一样的,再对copy重新赋值之后,发现变量自身的内存地址没有变化,原变量的值所只想的内存地址没有变化,copy变量的值所指向的内存地址变化了,是不是很奇怪,这不是赋值吗,为什么内存地址会变化了,其实可以这样理解,在做赋值过程中,我们是把一个新的字符串对象的地址赋值给了copy变量,所以变量值的内存地址才会发生变化。

//不可变字符串
    NSString *str = @"hello world";
    NSString *copyStr= [str copy];
    NSMutableString *mutableCopyStr = [str mutableCopy];
   // [mutableCopyStr appendString:@"!"];
   //copyStr = @"hello china";
  //  NSLog(@"%@====%@",str,copyStr);
    NSLog(@"%p-------%p-------%p",str,copyStr,mutableCopyStr);//打印的地址是变量的  值 的内存地址,即对值的引用,内存地址指向@"hello world"这个对象
    NSLog(@"%p-------%p-------%p",&str,&copyStr,&mutableCopyStr);//打印的地址是对象本身的内存地址
    
    copyStr = @"hello China";//这个时候应该是变量本身的内存地址没有变化,而变量的值的内存地址应该由指向@“hello world”变成指向了@"hello China"
    NSLog(@"%p-------%p-------%p",str,copyStr,mutableCopyStr);//打印的地址是变量的  值 的内存地址,即对值的引用,内存地址指向@"hello world"这个对象
    NSLog(@"%p-------%p-------%p",&str,&copyStr,&mutableCopyStr);//打印的地址是对象本身的内存地址
NSMutableString *str = [NSMutableString stringWithFormat:@"hello world"];
    NSString *copyStr= [str copy];
    NSMutableString *mutableCopyStr = [str mutableCopy];
    copyStr = @"hello China";
    mutableCopyStr = @"hello Apple";
    NSLog(@"%@====%@====%@",str,copyStr,mutableCopyStr);
    //NSLog(@"%@====%@",str,copyStr);
    NSLog(@"%p-------%p-------%p",str,copyStr,mutableCopyStr);//打印的地址是变量的  值 的内存地址,即对值的引用,内存地址指向@"hello world"这个对象
    NSLog(@"%p-------%p-------%p",&str,&copyStr,&mutableCopyStr);//打印的地址是对象本身的内存地址
BAA3035C-07D4-43AA-8140-FF0204C02DA4.png

很容易看出mutable对象不管是copy还是mutableCopy操作,复制的对象的内存地址与原对象完全不一致,重新赋值也互相不影响,所以说是内容复制

我们再来看看在集合类对象中,对immutable对象进行copy或者mutableCopy操作时,是指针复制还是是内容复制

//不可变数组
    NSArray *arr = @[@"hello",@"world"];
    NSMutableArray *mutableArr = [arr mutableCopy];
    NSArray *copyArr = [arr copy];
    //打印对象内容的地址
    NSLog(@"%@=====%@",arr,copyArr);
    NSLog(@"%p",arr);
    NSLog(@"%p",mutableArr);
    NSLog(@"%p",copyArr);
    arr = nil;
    NSLog(@"%@=====%@",arr,copyArr);
    
    Person *p = [[Person alloc] init];
    Person *p1 = [[Person alloc] init];
    
    NSArray *pArr = @[p,p1];
    NSArray *pCopyArr = [pArr copy];
    NSLog(@"%@=====%@",pArr,pCopyArr);
    pArr = nil;
    NSLog(@"%@=====%@",pArr,pCopyArr);

很明显我们可以看到arr和copyArr内存地址相同,mutableArr是和arr相互独立的

1D960E97-6FB0-4D52-ABCD-525C4768CDD1.png

我们也可以看到我们把原数组设为nil,对copy数组没有影响,原因是集合类imutable对象进行copy操作是是指针复制,引用计数+1

//数组元素是字符串
    NSMutableArray *modelsArray = [NSMutableArray arrayWithArray:@[@"123",@"456"]];
    //arr是不可变的
   // NSArray *arr = [modelsArray copy];
    NSMutableArray *arr = [modelsArray mutableCopy];
    arr[0] = @"jse3";
    
    NSLog(@"modelsArr =%@   ma=%@  ",modelsArray[0],arr[0]);
    //ma是可变的
    NSMutableArray *ma = [modelsArray mutableCopy];
    NSMutableArray *otherMa = [modelsArray copy];//因为用的是copy,返回的都应该是不可变对象,所以other其实是NSArray类型,如果修改元素的值会crash
    ma[0] = @"789";
    modelsArray[1] = @"hhaha";
    NSLog(@"%@",[otherMa class]);
    NSLog(@"modelsArr =%@   ma=%@   otherma=%@",modelsArray[1],ma[1],otherMa[1]);//打印结果显示,不管是哪个数组元素值发生变化,其他的数组的元素值都没有变化
    
    //数组元素是对象
    NSMutableArray *personArr = [[NSMutableArray alloc] init];
    BRPerson *person1 = [[BRPerson alloc] init];
    person1.name = @"lili";
    [personArr addObject:person1];
    
    BRPerson *person2 = [[BRPerson alloc] init];
    person2.name = @"lisa";
    [personArr addObject:person2];
    
    //浅拷贝
    NSArray *newArr = [personArr copy];
    BRPerson *p = newArr[0];
    p.name = @"lili的名字被修改了";
    NSLog(@"%@",((BRPerson *)personArr[0]).name);//打印结果为lili的名字被修改了,表示原来的数组中的元素value也修改了
    
    NSMutableArray *arr2 = [personArr mutableCopy];
    [arr2 removeObjectAtIndex:0];
    BRPerson *p2 = arr2[0];
    p2.name = @"sdsdsasdd";
    NSLog(@"%@ ---------- %@",((BRPerson *)newArr[0]).name,((BRPerson *)personArr[0]).name);//打印结果为lili的名字被修改了,表示原来的数组中的元素value也修改了

通过上面的代码我们就知道了集合对象的内容复制仅限于对象本身,对象元素的指针仍然是指针复制,也就是说不管是源头对象还是复制对象,其中某一个对象元素的值发生改变,另外一个也会跟着改变,当然你也可以通过打印源头对象和复制对象的对象元素的地址,来看一下他们是否相同。

自定义类支持copy和mutableCopy

直接看代码

// .h
@interface BRTest : NSObject
@property (nonatomic,copy) NSString *name;
@end
// .m
@interface BRTest()<NSCopying,NSMutableCopying>

@end

@implementation BRTest

- (id)copyWithZone:(NSZone *)zone{
    //浅拷贝
    BRTest *test = self;
    test.name = [self.name copy];
    return test;
}

- (id)mutableCopyWithZone:(NSZone *)zone{
    //深拷贝
    BRTest *test = [[BRTest allocWithZone:zone] init];
    test.name = [self.name mutableCopy];
    return test;
}

@end
// viewController
BRTest *test = [[BRTest alloc] init];
    test.name = @"测试";
    BRTest *copyTest = [test copy];
    BRTest *mutableTest = [test mutableCopy];
NSLog(@"%@===%@===%@",test.name,copyTest.name,mutableTest.name);
    NSLog(@"%p====%p===%p",&test,&copyTest,&mutableTest);//变量本身的地址
    NSLog(@"%p====%p===%p",test,copyTest,mutableTest);//变量的值指向的地址
    NSLog(@"%p====%p===%p",test.name,copyTest.name,mutableTest.name);
    
    copyTest.name = @"haha";
    mutableTest.name = @"hello";
    NSLog(@"%@===%@===%@",test.name,copyTest.name,mutableTest.name);

我们来看打印结果

473092FB-A5A3-4CC9-82E9-91B0A88FEDB7.png

我们从打印结果可以分析出自定义类使用copy是浅拷贝,使用mutableCopy是深拷贝,上面有几点需要注意

  • 浅拷贝只是指针赋值,不开辟新的内存空间,所以我们在代码里 BRTest *test = self;是把当前类对象的指针赋值给test,类似于retain操作,引用计数+1
  • 深拷贝是内容复制,需要开辟新的内存空间,所以我们在代码里BRTest *test = [[BRTest allocWithZone:zone] init];,并且对他的属性进行mutableCopy操作来达到深复制的效果,当然这里也是完全深复制了
  • 不管是使用copyWithZone还是mutableCopyWithZone,都必须确定是否在类中实现浅复制或深复制,并为其编写文档,以告知类的其他使用者,因为在这两个方法中都能实现浅拷贝,单层深拷贝,完全深拷贝的效果。举个例子:
- (id)copyWithZone:(NSZone *)zone{
    //BRTest *test = self;
   BRTest *test  = [[self class] allocWithZone:zone];
    test.name = [self.name copy];
    return test;
}

这样就变成了单层深复制了,因为他们的name的属性的内存地址是一样的,但是类的内的地址则不一样

- (id)mutableCopyWithZone:(NSZone *)zone{
    //深拷贝
    BRTest *test = [[BRTest allocWithZone:zone] init];
   // test.name = [self.name mutableCopy];
    test.name = [self.name copy];
    return test;
}

这样写也变成了单层深拷贝了,所以说在copyWithZone还是mutableCopyWithZone代码里如何实现拷贝要看我们的具体需求

完全深复制的实现

 //新建一个测试字符串
    NSMutableString * str = [NSMutableString stringWithFormat:@"ludashi__"];
 
    //新建一个测试字典
    NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithCapacity:1];
    [dic setObject:str forKey:@"key1"];
 
    //把字典存入数组中
    NSMutableArray *oldArray = [NSMutableArray arrayWithObject:dic];
 
    //用旧得数组生成新的数组
    NSMutableArray *newArray = [NSMutableArray arrayWithArray:oldArray];
 
    //用copyItems拷贝数组中的元素
    NSMutableArray *copyItems = [[NSMutableArray alloc] initWithArray:oldArray copyItems:YES];
 
    //把数组归档成一个NSData,然后再实现完全拷贝
    NSData * data = 
   [NSKeyedArchiver archivedDataWithRootObject:oldArray];
    NSMutableArray *multable = [NSKeyedUnarchiver unarchiveObjectWithData:data];
 
    //往字典中加入新的值
    [dic setObject:@"new_value1" forKey:@"key2"];
    //改变str的值
    [str appendString:@"update"];
    NSLog(@"%@", oldArray);
    NSLog(@"%@", newArray);
    NSLog(@"%@", copyItems);
    NSLog(@"%@", multable);
 
    //每个数组的地址为:
    NSLog(@"%p", oldArray);
    NSLog(@"%p", newArray);
    NSLog(@"%p", copyItems);
    NSLog(@"%p", multable);

代码运行效果

2014-08-13 16:33:00.752 OC6-1[3942:303] (
        {
        key1 = "ludashi__update";
        key2 = "new_value1";
    }
)
2014-08-13 16:33:00.753 OC6-1[3942:303] (
        {
        key1 = "ludashi__update";
        key2 = "new_value1";
    }
)
2014-08-13 16:33:00.753 OC6-1[3942:303] (
        {
        key1 = "ludashi__update";
    }
)
2014-08-13 16:33:00.753 OC6-1[3942:303] (
        {
        key1 = "ludashi__";
    }
)
2014-08-13 16:33:00.754 OC6-1[3942:303] 0x100204560
2014-08-13 16:33:00.754 OC6-1[3942:303] 0x1002046d0
2014-08-13 16:33:00.754 OC6-1[3942:303] 0x1002047c0
2014-08-13 16:33:00.755 OC6-1[3942:303] 0x100406610

我们再来看看另外一个例子

NSMutableArray *array = [NSMutableArray array];
    for (int i=0; i<5; i++) {
        BRTest *test = [[BRTest alloc] init];
        test.name = [NSString stringWithFormat:@"test%d",(i+1)];
        [array addObject:test];
    }
    NSMutableArray *mutableArray = [array mutableCopy];
    NSMutableArray *copyItems = [[NSMutableArray alloc] initWithArray:array copyItems:YES];
    //数据归档再解档,实现完全拷贝
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array];
    NSMutableArray *archiveArr = [NSKeyedUnarchiver unarchiveObjectWithData:data];
    NSLog(@"%p",array);
    NSLog(@"%p",mutableArray);
    NSLog(@"%p",copyItems);
    NSLog(@"%p",archiveArr);
    NSLog(@"===========================================");
    NSLog(@"===========================================");
    NSLog(@"%p",array[0]);
    NSLog(@"%p",mutableArray[0]);
    NSLog(@"%p",copyItems[0]);
    NSLog(@"%p",archiveArr[0]);
    
    BRTest *test = array[0];
    test.name = @"haha";
    
    BRTest *test1 = copyItems[0];
    test1.name = @"hello";

    NSLog(@"===========================================");
    NSLog(@"===========================================");
    NSLog(@"%@",((BRTest *)array[0]).name);
    NSLog(@"%@",((BRTest *)mutableArray[0]).name);
    NSLog(@"%@",((BRTest *)copyItems[0]).name);
    NSLog(@"%@",((BRTest *)archiveArr[0]).name);

代码运行结果

**2016-12-21 16:29:42.541 propertyDemo[2631:1572498] 0x60000005ec90
**2016-12-21 16:29:42.541 propertyDemo[2631:1572498] 0x60000005f290
**2016-12-21 16:29:42.542 propertyDemo[2631:1572498] 0x60000005f890
**2016-12-21 16:29:42.542 propertyDemo[2631:1572498] 0x60000005fda0
**2016-12-21 16:29:42.542 propertyDemo[2631:1572498] ===========================================
**2016-12-21 16:29:42.542 propertyDemo[2631:1572498] ===========================================
**2016-12-21 16:29:43.325 propertyDemo[2631:1572498] 0x600000009700
**2016-12-21 16:29:45.810 propertyDemo[2631:1572498] 0x600000009700
**2016-12-21 16:29:46.440 propertyDemo[2631:1572498] 0x600000009700
**2016-12-21 16:29:48.679 propertyDemo[2631:1572498] 0x600000009020
**2016-12-21 16:30:06.845 propertyDemo[2631:1572498] ===========================================
**2016-12-21 16:30:06.845 propertyDemo[2631:1572498] ===========================================
**2016-12-21 16:30:06.845 propertyDemo[2631:1572498] hello
**2016-12-21 16:30:06.845 propertyDemo[2631:1572498] hello
**2016-12-21 16:30:06.846 propertyDemo[2631:1572498] hello
**2016-12-21 16:30:06.846 propertyDemo[2631:1572498] test1

分析一下打印结果,从结果我们可以看到四个数组的内存地址各不相同,互相独立,但是我们通过打印每个数组的第一个元素的地址,我们发现前三个的地址都是一样的,这说明 [array mutableCopy]和
[[NSMutableArray alloc] initWithArray:array copyItems:YES]都是单层深复制,最后一个与前三个不一样,说明了利用数据归档再解档的方式实现了完全深复制,从后面第一次更改源头数组的第一个元素的属性的值,接着更改copyItems的第一个元素的值,而打印结果是前三个打印的值是第二次更改的值,最后一个则没有任何影响,这也进一步的说明了前三个数组的单层深复制,最后一个才是完全深复制

相关文章

网友评论

      本文标题:Copy和MutableCopy的区分

      本文链接:https://www.haomeiwen.com/subject/afstvttx.html