Objective-C中copy与mutableCopy问题

2019-08-03  本文已影响0人  TonyGor

先说下概念,我们对变量的复制,其实就是在写代码的过程中,再定义多几个不同名字的变量,让他们都“等于”某一个变量,这个过程我认为就是我们平常说的“复制”。

基本数据类型

对于基本数据类型,如int,double,BOOL这些,在赋值的过程中就是真正意义上的复制了,赋值时不仅把值传递到新的变量中,而且新的变量也 重新开辟了内存 ,使得原来的变量和后来的变量所指的不是同一块内存,就如同现实中真的复制(克隆)了一个新的一模一样个体一样。于是,我们把这种有 新开辟内存 的复制,暂且先叫做 深复制(深复制也有分两种,后面会说到)。

    int a = 0;
    int b = a;
    JMPLog(&a);
    JMPLog(&b);
0x7ffee662c1dc
0x7ffee662c1d8

结论1:所有基本数据类型的复制,都是深复制

非集合类型对象

所谓非集合类型对象,比较常用的就是NSString,下面就以NSString作为例子,说明copy和mutableCopy之间的区别,并与深复制浅复制进行联系。
首先展示一个错误的示范,网上有很多关于copy和mutableCopy的文章,里面举了这么一个例子:

    NSString *str1 = @"str1";
    NSString *str2 = [str1 copy];
    
    str1 = @"asdf";

    NSLog(@"\nstr1 = %@ str1P = %p \n str2 = %@ str2P = %p", str1, str1, str2, str2);

    /*输出结果,修改str2 同理
       str1 = asdf str1P = 0x10776b1a0
       str2 = str1 str2P = 0x10776b180
     */

然后就说,因为str2 = str1的时候,两个字符串都是不可变的,指向的同一块内存空间中的 @"str1",是不可能变成@"abcd"的。所以这个时候,为了优化性能,系统没必要另外提供内存,只生成另外一个指针,指向同一块内存空间就行。
但是当你从新给 str1 或者str2赋值的时候,因为之前的内容不可变,还有互不影响的原则下,这个时候,系统会从新开辟一块内存空间。

上面的解释和代码有个很严重的问题,当执行str1 = @"asdf";这行代码的时候其实str1的指针已经指向了新的字符串@“asdf”身上了,所以这并不能很好的说明深浅复制的问题。

言归正传,我们先讨论对于不可变的非集合类型对象(这里用NSString作为例子),当发送copy和mutableCopy消息后,新的对象的内存情况。

    NSString *strA = @"strA";
    NSString *strB = [strA copy];
    NSString *strC = [strA mutableCopy];
    NSLog(@"Value -- strA: %@, strB: %@, strC: %@", strA, strB, strC);
    NSLog(@"Pointer -- strA: %p, strB: %p, strC: %p", &strA, &strB, &strC);
    NSLog(@"Pointer of value -- strA: %p, strB: %p, strC: %p", strA, strB, strC);
Value -- strA: strA, strB: strA, strC: strA
Pointer -- strA: 0x7ffee3adf1d8, strB: 0x7ffee3adf1d0, strC: 0x7ffee3adf1c8
Pointer of value -- strA: 0x10c124800, strB: 0x10c124800, strC: 0x6000009e0a80

结果分析:

  1. 可以看出,三个字符串的内容都是一样的,达到了我们对“复制”这个概念的目的。但是,字符串B和C是不是做到真正意义上的复制呢?我们要看B,C变量所指向的内存地址(Pointer of value),结果显示B的地址与A的地址一致(0x1039f0800),而C的地址则与A的不同(A: 0x1039f0800, C: 0x6000021f4450),所以只有C才是真正意义上的复制,也就是我们上面提到的 深复制 ,而像字符串B这种 只是把指针指向同一块内存地址,而实现对应内容“复制” 的做法,我们称之为 浅复制
  2. 观察三个字符串变量本身的地址(注意不是字符串所指向的地址),发现三个是不同的,也就是说上述行为是新建了三个指针(指针本身也占有内存),然后A,B指向的是同一块地址,C则指向另一块新的地址,这些内存地址的内容都是@“strA”。
    就目前来看,我们暂时可以得出的结论是,copy的作用仅仅是把指针指向同一块内存地址,是浅复制,那么如果有其他手段能够改变该段内存的内容,那么用copy消息返回的对象的值(所指内存的内容)也会跟着原本被“复制”的对象而改变。而mutableCopy的作用则会新开辟一段内存,让对象指向该段内存,从而实现复制,如果被复制的对象内容改变,新对象的内容并不会跟着改变(由于指向的不是同一段内存)。

接下来再讨论可变非集合类型的对象(这里用NSMutableString作为例子),先上代码

    NSMutableString *strA = [NSMutableString stringWithFormat:@"strA"];
    NSString *copyStr = [strA copy];
    NSString *copyStr2 = [strA copy];
    NSMutableString *copyMStr = [strA copy];
    NSMutableString *copyMStr2 = [strA copy];
    NSString *mutableCopyStr = [strA mutableCopy];
    NSString *mutableCopyStr2 = [strA mutableCopy];
    NSMutableString *mutableCopyMStr = [strA mutableCopy];
    NSMutableString *mutableCopyMStr2 = [strA mutableCopy];
    
    NSLog(@"strA -- %p &strA -- %p", strA, &strA);
    NSLog(@"copyStr -- %p &copyStr -- %p", copyStr, &copyStr);
    NSLog(@"copyStr2 -- %p &copyStr2 -- %p", copyStr2, &copyStr2);
    NSLog(@"copyMStr -- %p &copyMStr -- %p", copyMStr, &copyMStr);
    NSLog(@"copyMStr2 -- %p &copyMStr2 -- %p", copyMStr2, &copyMStr2);
    NSLog(@"mutableCopyStr -- %p &mutableCopyStr -- %p", mutableCopyStr, &mutableCopyStr);
    NSLog(@"mutableCopyStr2 -- %p &mutableCopyStr2 -- %p", mutableCopyStr2, &mutableCopyStr2);
    NSLog(@"mutableCopyMStr -- %p &mutableCopyMStr -- %p", mutableCopyMStr, &mutableCopyMStr);
    NSLog(@"mutableCopyMStr2 -- %p &mutableCopyMStr2 -- %p", mutableCopyMStr2, &mutableCopyMStr2);
strA -- 0x600001c093e0 &strA -- 0x7ffeebd531d8
copyStr -- 0xd4d03e2a99e5492a &copyStr -- 0x7ffeebd531d0
copyStr2 -- 0xd4d03e2a99e5492a &copyStr2 -- 0x7ffeebd531c8
copyMStr -- 0xd4d03e2a99e5492a &copyMStr -- 0x7ffeebd531c0
copyMStr2 -- 0xd4d03e2a99e5492a &copyMStr2 -- 0x7ffeebd531b8
mutableCopyStr -- 0x600001c08930 &mutableCopyStr -- 0x7ffeebd531b0
mutableCopyStr2 -- 0x600001c08a50 &mutableCopyStr2 -- 0x7ffeebd531a8
mutableCopyMStr -- 0x600001c08f30 &mutableCopyMStr -- 0x7ffeebd531a0
mutableCopyMStr2 -- 0x600001c09290 &mutableCopyMStr2 -- 0x7ffeebd53198

结果分析:

结论2:

- 非可变非集合类型 可变非集合类型
copy 浅复制 深复制
mutableCopy 深复制 深复制

集合类型对象

集合类型对象应该是我们开发过程中最常用到的结构之一,比如NSArray,NSDictionary等。那么对于集合类型的对象,我们向他们发送copy和mutableCopy消息时,又会产生何种效果?
首先还是讨论不可变的情况,这里以NSDictionary作为例子(选择字典作为例子是想更全面的研究字典中的Key和Value出现的情况是否相同,数组则体现不出这个效果)。

    NSDictionary *aDic = @{@"aaa": @"111"};
    
    NSDictionary *copyDic = [aDic copy];
    NSDictionary *copyDic2 = [aDic copy];
    NSMutableDictionary *copyMDic = [aDic copy];
    NSMutableDictionary *copyMDic2 = [aDic copy];
    
    NSDictionary *mutableCopyDic = [aDic mutableCopy];
    NSDictionary *mutableCopyDic2 = [aDic mutableCopy];
    NSMutableDictionary *mutableCopyMDic = [aDic mutableCopy];
    NSMutableDictionary *mutableCopyMDic2 = [aDic mutableCopy];

    NSLog(@"aDic -- %p aDic[@\"aaa\"] -- %p key aaa -- %p", aDic, aDic[@"aaa"], [[aDic allKeys] firstObject]);
    
    NSLog(@"copyDic -- %p copyDic[@\"aaa\"] -- %p key aaa -- %p", copyDic, copyDic[@"aaa"], [[copyDic allKeys] firstObject]);
    NSLog(@"copyDic2 -- %p copyDic2[@\"aaa\"] -- %p key aaa -- %p", copyDic2, copyDic2[@"aaa"], [[copyDic2 allKeys] firstObject]);
    NSLog(@"copyMDic -- %p copyMDic[@\"aaa\"] -- %p key aaa -- %p", copyMDic, copyMDic[@"aaa"], [[copyMDic allKeys] firstObject]);
    NSLog(@"copyMDic2 -- %p copyMDic2[@\"aaa\"] -- %p key aaa -- %p", copyMDic2, copyMDic2[@"aaa"], [[copyMDic2 allKeys] firstObject]);
    
    NSLog(@"mutableCopyDic -- %p mutableCopyDic[@\"aaa\"] -- %p key aaa -- %p", mutableCopyDic, mutableCopyDic[@"aaa"], [[mutableCopyDic allKeys] firstObject]);
    NSLog(@"mutableCopyDic2 -- %p mutableCopyDic2[@\"aaa\"] -- %p key aaa -- %p", mutableCopyDic2, mutableCopyDic2[@"aaa"], [[mutableCopyDic2 allKeys] firstObject]);
    NSLog(@"mutableCopyMDic -- %p mutableCopyMDic[@\"aaa\"] -- %p key aaa -- %p", mutableCopyMDic, mutableCopyMDic[@"aaa"], [[mutableCopyMDic allKeys] firstObject]);
    NSLog(@"mutableCopyMDic2 -- %p mutableCopyMDic2[@\"aaa\"] -- %p key aaa -- %p", mutableCopyMDic2, mutableCopyMDic2[@"aaa"], [[mutableCopyMDic2 allKeys] firstObject]);
aDic -- 0x6000027a1e00 aDic[@"aaa"] -- 0x1042da820 key aaa -- 0x1042da800
copyDic -- 0x6000027a1e00 copyDic[@"aaa"] -- 0x1042da820 key aaa -- 0x1042da800
copyDic2 -- 0x6000027a1e00 copyDic2[@"aaa"] -- 0x1042da820 key aaa -- 0x1042da800
copyMDic -- 0x6000027a1e00 copyMDic[@"aaa"] -- 0x1042da820 key aaa -- 0x1042da800
copyMDic2 -- 0x6000027a1e00 copyMDic2[@"aaa"] -- 0x1042da820 key aaa -- 0x1042da800
mutableCopyDic -- 0x6000027a16e0 mutableCopyDic[@"aaa"] -- 0x1042da820 key aaa -- 0x1042da800
mutableCopyDic2 -- 0x6000027a1d40 mutableCopyDic2[@"aaa"] -- 0x1042da820 key aaa -- 0x1042da800
mutableCopyMDic -- 0x6000027a1920 mutableCopyMDic[@"aaa"] -- 0x1042da820 key aaa -- 0x1042da800
mutableCopyMDic2 -- 0x6000027a0ee0 mutableCopyMDic2[@"aaa"] -- 0x1042da820 key aaa -- 0x1042da800

结果分析:

接下来把可变集合类型也测试一下。

    NSMutableDictionary *aDic = [NSMutableDictionary dictionaryWithCapacity:10];
    [aDic setObject:@"111" forKey:@"aaa"];
    
    NSDictionary *copyDic = [aDic copy];
    NSDictionary *copyDic2 = [aDic copy];
    NSMutableDictionary *copyMDic = [aDic copy];
    NSMutableDictionary *copyMDic2 = [aDic copy];
    
    NSDictionary *mutableCopyDic = [aDic mutableCopy];
    NSDictionary *mutableCopyDic2 = [aDic mutableCopy];
    NSMutableDictionary *mutableCopyMDic = [aDic mutableCopy];
    NSMutableDictionary *mutableCopyMDic2 = [aDic mutableCopy];
    
    NSLog(@"aDic -- %p aDic[@\"aaa\"] -- %p key aaa -- %p", aDic, aDic[@"aaa"], [[aDic allKeys] firstObject]);
    
    NSLog(@"copyDic -- %p copyDic[@\"aaa\"] -- %p key aaa -- %p", copyDic, copyDic[@"aaa"], [[copyDic allKeys] firstObject]);
    NSLog(@"copyDic2 -- %p copyDic2[@\"aaa\"] -- %p key aaa -- %p", copyDic2, copyDic2[@"aaa"], [[copyDic2 allKeys] firstObject]);
    NSLog(@"copyMDic -- %p copyMDic[@\"aaa\"] -- %p key aaa -- %p", copyMDic, copyMDic[@"aaa"], [[copyMDic allKeys] firstObject]);
    NSLog(@"copyMDic2 -- %p copyMDic2[@\"aaa\"] -- %p key aaa -- %p", copyMDic2, copyMDic2[@"aaa"], [[copyMDic2 allKeys] firstObject]);
    
    NSLog(@"mutableCopyDic -- %p mutableCopyDic[@\"aaa\"] -- %p key aaa -- %p", mutableCopyDic, mutableCopyDic[@"aaa"], [[mutableCopyDic allKeys] firstObject]);
    NSLog(@"mutableCopyDic2 -- %p mutableCopyDic2[@\"aaa\"] -- %p key aaa -- %p", mutableCopyDic2, mutableCopyDic2[@"aaa"], [[mutableCopyDic2 allKeys] firstObject]);
    NSLog(@"mutableCopyMDic -- %p mutableCopyMDic[@\"aaa\"] -- %p key aaa -- %p", mutableCopyMDic, mutableCopyMDic[@"aaa"], [[mutableCopyMDic allKeys] firstObject]);
    NSLog(@"mutableCopyMDic2 -- %p mutableCopyMDic2[@\"aaa\"] -- %p key aaa -- %p", mutableCopyMDic2, mutableCopyMDic2[@"aaa"], [[mutableCopyMDic2 allKeys] firstObject]);
aDic -- 0x600002f53240 aDic[@"aaa"] -- 0x10709f800 key aaa -- 0x10709f820
copyDic -- 0x600002f53220 copyDic[@"aaa"] -- 0x10709f800 key aaa -- 0x10709f820
copyDic2 -- 0x600002f531a0 copyDic2[@"aaa"] -- 0x10709f800 key aaa -- 0x10709f820
copyMDic -- 0x600002f52d20 copyMDic[@"aaa"] -- 0x10709f800 key aaa -- 0x10709f820
copyMDic2 -- 0x600002f52f00 copyMDic2[@"aaa"] -- 0x10709f800 key aaa -- 0x10709f820
mutableCopyDic -- 0x600002f52de0 mutableCopyDic[@"aaa"] -- 0x10709f800 key aaa -- 0x10709f820
mutableCopyDic2 -- 0x600002f52d60 mutableCopyDic2[@"aaa"] -- 0x10709f800 key aaa -- 0x10709f820
mutableCopyMDic -- 0x600002f52e00 mutableCopyMDic[@"aaa"] -- 0x10709f800 key aaa -- 0x10709f820
mutableCopyMDic2 -- 0x600002f52e60 mutableCopyMDic2[@"aaa"] -- 0x10709f800 key aaa -- 0x10709f820

结果分析:

结论3:

- 不可变集合类型 可变集合类型
copy 浅复制 单层深复制
mutableCopy 单层深复制 单层深复制

彩蛋:在一开始的时候,我是把key和value都设置成@“aaa”,运行后发现key和value的地址都是一样的,这不就是浅复制吗?猜测系统这样的做法是为了节省内存吧?

最终结论

所以,我们可以得出:
对于不可变的非集合类对象进行 copy 操作,其内存地址并没有发生变化,属于浅复制;进行 mutableCopy 操作,内存地址发生了变化,深复制。

对于不可变的集合类对象进行 copy 操作,其内存地址并没有发生变化,属于浅复制;进行 mutableCopy 操作,内存地址发生了变化,但是其中的内容的内存地址并没有发生变化,属于单层深复制。

对于可变集合类对象,不管是进行 copy 操作还是 mutableCopy 操作,其内存地址都发生了变化,但是其中的元素内存地址都没有发生变化,属于单层深复制。

上一篇 下一篇

猜你喜欢

热点阅读