“为学日益,为道日损,损之又损,以至于无为,无为而无不为”。用自己的思考去理解问题,从问题的源头探索,才有可能寻找到最接近设计者思想的答案。网上有非常多的博客,博客内容良莠不齐,且绝大多数是为被大众证实,仅仅是一面之词。日常阅读,日常解惑,有这两篇文档可以阅读,分别是更新在MRR、ARC时代的两篇文章。
Updated: 2009-10-21
Memory Management Programming Guide for Core Foundation
Updated: 2012-07-17
Advanced Memory Management Programming Guide
下面只是笔者对上面两篇文章一点读后感。
引用拷贝(Reference Copy)
引用拷贝
这个名词在文章中并没有直接指出,但有相关的概念提示,例如截图第二段中,第三行:copies the reference to the object
,第五行中提到:the reference is duplicated
,拷贝的结果是一个新的指针,故亦称之为指针拷贝
。
基本数据类型
下面以整数的拷贝为例:
myInt2 = myInt1
-
myInt1
和myInt2
分别是两个独立的内存空间; -
myInt1
的值拷贝进myInt2
Core Foundation类型
myCFString2 = myCFString1
-
myCFString2
和myCFString1
都是CFStringRef类型,仅仅只是拷贝对象的引用,并不能拷贝对象的内容; - 拷贝一个对象的类型(引用)是非常快的,因为没有拷贝对象的内容;
- 拷贝一个可变对象的的引用是非常危险的操作。因为在某个地方修改了这个对象的数据,其他地方的引用是无法知道数据已经被修改了;(数据的修改可以是对象本身被修改,也可以是对象的某个属性被修改)
-
CFStringCreateCopy
拷贝一个全新的对象,对象内容与原始数据一模一样,当我们在修改这个新对象时,是不会对旧对象造成任何影响;
浅拷贝(Shallow Copy)
上面提到了 CFStringCreateCopy
,这个方法仅仅是适用于字符串的copy,而对于array,同样有CFArrayCreateCopy
方法。CFxxtypexxCreateCopy
系列的方法都会拷贝一个新的对象。
单一对象
例如CFStringRef、CFDataRef,在使用CFxxtypexxCreateCopy
时,都会拷贝一个全新的对象。
容器对象
- 对容器对象本身,拷贝一个全新的容器对象;
- 新容器中的元素,只是拷贝的元素的引用;
- 文章给出的解释是,你只希望有一个不可变数组去记录这些元素,也就是你本身就不想去改变他们,那就没有必要分配额外的空间。
深拷贝(Deep Copy)
- 深拷贝可以拷贝出一个全新的容器对象,对象本身以及容器对象里面的元素都会被拷贝;
-
CFPropertyListCreateDeepCopy
,对于给定的list,递归调用CFxxtypexxCreateCopy
方法,拷贝容器中的元素; - 如果需要深拷贝其他数据结构,需要自己手动递归拷贝容器对象里面的所有元素;
- 文章也指出,在递归的时候需要避免
递归循环
;
从上面的一些特点不难分析出,深拷贝是一种概念
,而不是某个具体的操作。不管是集合对象,还是自定义的实例对象,除了对象本身,他们还依赖一些其他对象,我们可以通过引用的方式来表示其他对象。当对象本身被拷贝时,仅仅只会开辟这个对象需要的空间,然后将原对象中的数据一比一复制一份存到这块内存,到这里就是浅拷贝的过程。如果是深拷贝,还需要把所有引用指向的对象再拷贝一份,让引用也指向新的对象,这样才能将这个对象以及这个对象所依赖的其他对象完全与原来的那份隔离开。
满足以下两个过程的拷贝可以称作深拷贝:
- 拷贝对象本身;
- 递归拷贝对象所引用的对象;
copy 和 mutableCopy
上面仅仅是对平时一直遇到,但没有理解透彻的概念做了一个整理,加深对这些概念的理解。上面的三个拷贝都是在MRR模式下,Core Foundation
框架中的概念,而且有API支持。在ARC模式下,我们遇到最多的其实是strong/copy/mutableCopy
这些关键词。
-
retain/strong,引用拷贝
,对象的引用计数+1。图中classB对对象retain,引用计数变为2; -
copy,对象拷贝
,会拷贝一个新的对象。图中classC对原对象copy,拷贝一个新的对象,新对象引用计数为1,图中虚线流程;
这张图可以佐证上面的一些概念。Foundation
框架中的copy
只表示一件事——拷贝对象
,所以copy
与浅拷贝、深拷贝
的概念无直接联系。在ARC中的copy
可以理解为MRR中的CreateCopy
——The Create Rule
mutableCopy
/* from CFBag.h */
CF_EXPORT CFBagRef CFBagCreate(CFAllocatorRef allocator, const void **values, CFIndex numValues, const CFBagCallBacks *callBacks);
CF_EXPORT CFMutableBagRef CFBagCreateMutableCopy(CFAllocatorRef allocator, CFIndex capacity, CFBagRef bag);
The CFBag function
CFBagCreateMutableCopy
has both “Create” and “Copy” in its name. It is a creation function because the function name contains the word “Create”. Note also that the first argument is of typeCFAllocatorRef
—this serves as a further hint. The “Copy” in this function is a hint that the function takes aCFBagRef
argument and produces a duplicate of the object. It also refers to what happens to the element objects of the source collection: they are copied to the newly created bag. The secondary “Copy” and “NoCopy” substrings of function names indicate how objects owned by some source objects are treated—that is, whether they are copied or not.
上述案例介绍了create
和copy
的规则,与上面分析的一致,而且完全没有提到任何关于mutable
相关的解释。下面再看一个API的定义
theArray
The array to copy. The pointer values from the array are copied into the new array. However, the values are also retained by the new array.
- CFArrayCreateMutableCopy 方法的第三个参数,用来拷贝的原数组;
- 原数组的指针被拷贝到了新的数组;
- 指针所指向的对象被新的数组持有;
根据上面对深拷贝概念
的对比,发现mutableCopy
并没有完成深拷贝的过程,仅仅只是一个浅拷贝的过程。与copy
不同的是,返回的容器对象是可变的,两者差异仅此而已。对容器对象,有以下特点:
-
copy
和mutableCopy
都是浅拷贝,只拷贝容器对象本身,不拷贝元素对象,只拷贝元素引用; -
copy
返回的容器是不可变的,mutableCopy
返回的对象是可变的;
那么在Foundation
框架中要完成深拷贝,可以使用框架提供的API:
总结
明确每一种概念所发生的变化,相关的问题都将迎刃而解。
- 指针拷贝
- 引用拷贝
- 对象拷贝
- 浅拷贝
- 深拷贝
Memory Management Programming Guide for Core Foundation
Advanced Memory Management Programming Guide
The Create Rule
CFArrayCreateMutableCopy
initWithArray:copyItems: