美文网首页
Objective-C中copy与mutableCopy问题

Objective-C中copy与mutableCopy问题

作者: TonyGor | 来源:发表于2019-08-03 00:58 被阅读0次

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

    基本数据类型

    对于基本数据类型,如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
    

    结果分析:

    • 同样,观察每个指针自身的地址各不相同,说明也是生成了各个不同的指针,符合逻辑。观察使用copy消息的4个变量,发现无论是NSString还是NSMutableString,只要是copy消息返回的都是与原字符串A不同指向的地址。但是返回的这些指针,都是指向同一块新的内存地址(0xd4d03e2a99e5492a)。当字符串A是可变字符串,copy消息返回了新的对象,开辟了新的内存,这些对象都会指向新开辟的这一段内存。根据前面描述的,这种复制应该是深复制,这与上一个例子的非可变类型有所区别。
    • 对于mutableCopy,与copy的情况有类似的地方,都是返回的是与原字符串A不同指向的地址。不同的是,这些指针指向的却是不是同一块内存地址,无论我们定义的是NSString或者是NSMutableString,每个新的对象都是指向一块全新的地址。不过我们目前只考虑是否开辟了新的内存,所以这种情况也是认为是深复制。

    结论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
    

    结果分析:

    • 情况和非集合类型相似,对于被复制的对象是不可变集合类型,当向对象发送copy消息时,无论新定义的对象是可变还是不可变的,返回的总是和原对象所指向的地址一样的地址,同时,字典里无论是key还是value都是和原对象是一致的。这还是比较符合认知,既然所有的指针都指向同一个地址,证明这里copy消息只是做了一个浅复制,既然是浅复制,本质上并没有真正“复制”一个新的对象出来(没有开辟新的内存地址),而是只是简单地把新的对象指向了原来对象的地址,所以没有新的字典和内容生成,故所有地址都是一致的。
    • 当向对象发送mutableCopy消息时,无论新定义的对象是可变还是不可变的,返回的总是一段全新的内存,指向全新的地址,不过有趣的是,字典里的key和value也是像前面copy的情况一样,都是和原对象是一致。这就有点奇妙了,讨论到现在,对于复制我们讨论到的就两种复制,浅复制和深复制,区分它们的方法就是看是否有开辟新的内存,但现在的情况是,mutableCopy确实是返回了新开辟内存的新的字典对象,但字典里面的内容却和原对象的是指向同一块地址,也就是说如果通过某些手段改变了这些地址的值,所有这些新的字典对象的key和value也会随之改变。这还是跟我们理解中的复制有点区别。所以这里回应到一开始提到的深复制也有两种情况,一种是单单给对象开辟新的内存,另一种是 不仅给新的对象开辟内存,而且会对其里面包含的内容开辟新的地址,彻底复制一份全新的独立的拷贝 ,我们把后者这种深复制叫做 两层深复制 (two-layer-copy),同时为了区分,现在我们把第一种的深复制叫做 单层深复制

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

    • 实验代码
        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
    

    结果分析:

    • 情况有点出乎我预料,本来以为是跟非集合类型(NSString)类似,用copy消息返回的是一段新开辟的内存(深复制),所有4个指针都是指向那段新开的内存。但现实的情况确实每个对象各自指向了不同的新的内存,尽管都是深复制。而且这里提到的深复制,是我们上述的单层深复制,因为可以看出字典的Key和value都是跟原来的字典一样的。
    • 同时,mutableCopy就和之前的完全一致了,每个对象都各自开辟了新的互不相同的内存,然而字典内容也还是和原字典的一致,同样也是单程深复制。

    结论3:

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

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

    最终结论

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

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

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

    相关文章

      网友评论

          本文标题:Objective-C中copy与mutableCopy问题

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