copy 和 mutableCopy
2018-06-30 14:16:37 # Objective-C

今天来谈下深浅拷贝【copy 和 mutableCopy】

想写这篇文章,是因为看到了一个有意思的文章,对于其中的一些观点不太认同,所以想简单分析一下深浅拷贝

先看一段代码,稍后会对代码进行分析

1
2
3
4
5
6
7
8
9
-(void)testOne
{
NSArray *test = @[@1,@2,@3];
NSLog(@"test->%p\n",test);
NSArray *testCopy = [test copy];
NSLog(@"testCopy->%p\n",testCopy);
NSArray *testMutableCopy = [test mutableCopy];
NSLog(@"testMutableCopy->%p\n",testMutableCopy);
}

分析:

很简单的创建一个数组,对其进行copy和mutableCopy操作,打印其内存地址。结果看图会知道:
test和testCopy 内存地址是一样的,而testMutableCopy的内存地址是和test不同的。

小总结:

  1. copy 只是拷贝指向对象的指针,并没有出现新的内存地址,我们称之为浅拷贝
  2. mutableCopy 拷贝整个对象内存到另一块内存中,是产生了新的内存地址。

copy和mutableCopy操作1


再看下面的一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
-(void)testTwo
{
NSLog(@"\n");
NSMutableArray *test2 = [NSMutableArray arrayWithObjects:@"1",@"2",@"3", nil];
NSLog(@"test2->%p\n",test2);
NSMutableArray *testCopy2 = [test2 copy];
NSLog(@"testCopy2->%p\n",testCopy2);

NSMutableArray *testMutableCopy2 = [test2 mutableCopy];
NSLog(@"testMutableCopy2->%p\n",testMutableCopy2);

[test2 addObject:@"A"];

//这里不能对数组做操作, 可变数组的copy属于深拷贝,和mutableCopy一样都是将数组拷贝一份存入新的内存中,但通过mutableCopy得到的可变数组可以再操作,而copy出来的是不可变数组。
// [testCopy2 removeObject:@"1"];
// [testCopy2 addObject:@"B"];

[testMutableCopy2 addObject:@"C"];
[testMutableCopy2 removeObject:@"3"];
NSLog(@"test2 = %@, testCopy2 =%@,testMutableCopy2 = %@",test2,testCopy2,testMutableCopy2);

//打印结果: test2 = @[1,2,3,A] testCopy2 = @[1,2,3] testMutableCopy2 = @[1,2,3,C]

}

分析:
上面这段代码会和之前的不一样,我们这里创建的是一个可变数组,然后对可变数组进行copymutableCopy操作。发现打印其内存地址都不一样,
这说明对可变数组进行copymutableCopy都是深拷贝。
但你会发现copy得到的数组不能添加删除操作,这是因为copy得到的是不可变数组。

小总结:

可变数组的copymutableCopy都是深拷贝

copy和mutableCopy操作2

最后一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-(void)testThree
{
NSMutableArray *test4 = [NSMutableArray arrayWithObjects:@"1",@"2",@"3", nil];
NSMutableArray *newTest = [NSMutableArray arrayWithObject:test4];
NSMutableArray *testMutableCopy4 = [newTest mutableCopy];//拷贝出来的是NSMutableArray类型数组
NSLog(@"newTest->%p,testMutableCopy4->%p\n",newTest,testMutableCopy4);

//copy的先不说, 我们看下mutableCopy得到的数组,你会发现通过mutableCopy得到的数组中的元素地址是不变的
NSLog(@"newTest.first->%p,testMutableCopy4first->%p\n",newTest.firstObject,testMutableCopy4.firstObject);

//test
[testMutableCopy4.firstObject addObject:@6];
[newTest.firstObject addObject:@"12"];

//打印出来这里两个数组,会发现结果是一样的,原因就是mutableCopy的深拷贝是容器和其中的元素,但元素的地址并没有改变,修改其中的元素中的内容,结果必然都改变

NSLog(@"newTest = %@,testMutableCopy2 = %@",newTest,testMutableCopy4);
}

分析:

通过mutableCopy得到的数组,你会发现通过mutableCopy得到的数组中的元素地址是不变的。如果你往数组的元素中添加或删除元素,会发现newTest和testMutableCopy4中的元素都发生了改变。

小总结:

集合对象的内容复制(mutableCopy)仅限于对象本身,对象中的元素仍然是指针复制。

copy和mutableCopy操作4

关于这点其实苹果官网文档中CopyFunctions给了解释,看下以下介绍:

关于复制的介绍:

Copy Functions
In general, a standard copy operation, which might also be called simple assignment, occurs when you use the = operator to assign the value of one variable to another. The expression myInt2 = myInt1, for example, causes the integer contents of myInt1 to be copied from the memory used by myInt1 into the memory used by myInt2. Following the copy operation, two separate areas of memory contain the same value. However, if you attempt to copy a Core Foundation object in this way, be aware that you will not duplicate the object itself, only the reference to the object.

For example, someone new to Core Foundation might think that to make a copy of a CFString object she would use the expression myCFString2 = myCFString1. Again, this expression does not actually copy the string data. Because both myCFString1 and myCFString2 must have the CFStringRef type, this expression only copies the reference to the object. Following the copy operation, you have two copies of the reference to the CFString. This type of copy is very fast because only the reference is duplicated, but it is important to remember that copying a mutable object in this way is dangerous. As with programs that use global variables, if one part of your application changes an object using a copy of the reference, there is no way for other parts of the program which have copies of that reference to know that the data has changed.

If you want to duplicate an object, you must use one of the functions provided by Core Foundation specifically for this purpose. Continuing with the CFString example, you would use CFStringCreateCopy to create an entirely new CFString object containing the same data as the original. Core Foundation types which have “CreateCopy” functions also provide the variant “CreateMutableCopy” which returns a copy of an object that can be modified.

浅拷贝:

Shallow Copy
Copying compound objects, objects such as collection objects that can contain other objects, must also be done with care. As you would expect, using the = operator to perform a copy on these objects results in a duplication of the object reference. In contrast to simple objects like CFString and CFData, the “CreateCopy” functions provided for compound objects such as CFArray and CFSet actually perform a shallow copy. In the case of these objects, a shallow copy means that a new collection object is created, but the contents of the original collection are not duplicated—only the object references are copied to the new container. This type of copy is useful if, for example, you have an array that’s immutable and you want to reorder it. In this case, you don’t want to duplicate all of the contained objects because there’s no need to change them—and why use up that extra memory? You just want the set of included objects to be changed. The same risks apply here as with copying object references with simple types.

深拷贝:

Deep Copy
When you want to create an entirely new compound object, you must perform a deep copy. A deep copy duplicates the compound object as well as the contents of all of its contained objects. The current release of Core Foundation includes a function that performs deep copying of a property list (see CFPropertyListCreateDeepCopy). If you want to create deep copies of other structures, you could perform the deep copy yourself by recursively descending into the compound object and copying all of its contents one by one. Take care in implementing this functionality as compound objects can be recursive—they may directly or indirectly contain a reference to themselves—which can cause a recursive loop.

引用自:苹果官网文档-CopyFunctions

小总结:

我们通过对集合类对象进行mutableCopy得到的新对象,从某种意义上来说,并不是真正的深层次的复制,严格意义上说:它只是一个单层次的深复制。原因上面已说明:集合对象的内容复制(mutableCopy)仅限于对象本身,对象中的元素仍然是指针复制。

那如果需要深层次的复制,就需要以递归的形式找到集合中的元素对象,再对元素对象进行mutableCopy,以此来实现深层次的复制。但Apple并不提倡这种方式,因为这可能会导致递归循环。

最后总结:

  1. 不管是集合类对象还是非集合类对象,copy和mutableCopy时,都遵循以下准则:
    1. copy得到的都是不可变对象(imutable),所以对其copy返回的对象做可变对象的操作,都会崩溃。
    2. mutableCopy返回的是可变对象(mutable)
  2. 在非集合类对象中:
    1. 对不可变对象进行copy操作,是指针复制,其内存地址不变。
    2. 对不可变对象进行mutableCopy操作,是内容复制,其内存地址改变。
    3. 对可变对象进行copy和mutableCopy操作,都是内容复制,其内存地址改变。
  3. 在集合类对象中:
    1. 对不可变对象进行copy操作,是指针复制,其内存地址不变。
    2. 对不可变对象进行mutableCopy操作,是内容复制,其内存地址改变。
    3. 对可变对象进行copy和mutableCopy操作,都是内容复制,其内存地址改变。但集合对象的内容复制仅限于对象本身,对象中的元素仍然是指针复制。

最后可以看下这个Apple的官方文档《苹果官网文档-Copying