偶然间看到这篇文档,所以记录下来。文档出处:Ownership Policy
当我们在APP中使用Core Foundation API时,为了避免内存泄漏,在获取对象或者创建对象时应该遵守Core Foundation的相关规定。
基础
在使用Core Foundation时,Ownership Policy
不仅仅帮助我们理解内存管理,同时还帮助我们理解对象的所属关系。对象可能会有不止一个拥有者,它依靠引用计数(retain count)来记录拥有者的数量,一旦引用计数为0(没有拥有者),这个对象就会被释放。Core Foundation规定了下面三条规则来进行对象的创建和处理。
- 如果你创建了这个对象(直接创建或者间接拷贝已经存在的对象),那么就拥有这个对象。
- 如果你从别处获取了一个对象,你并没有拥有这个对象,所以为了避免对象被释放,可以使用
CFRetain
函数来是引用计数+1,成为这个对象的所有者。 - 如果你是这个对象的拥有者,那么在使用完这个对象之后必须使用
CFRelease
函数解除所有权,否则会造成内存泄漏。
通常命名方式
当你使用Core Foundation时,有很多方式引用一个对象。按照Core Foundation的Ownership Policy
,你需要知道调用函数返回的对象是否被你拥有,这样才能进行相应的内存管理操作。简单来说,如果一个函数的函数名包含Create
或者Copy
,那么你拥有这个函数返回的对象。如果函数的函数名包含Get
,那么你并没有拥有这个返回的对象。创建规则(The Create Rule)
和获取规则(The Get Rule)
非常细节地解释了这种命名方式。
重点:Cocoa为内存管理提供了一个非常相似的命名方式,Core Foundation的函数命名方式,
尤其是函数名中带有`Create`的,仅用于C函数返回Core Foundation对象。
Objective-C的方法命名方式由Cocoa管理,无论方法返回Cocoa对象还是Core Foundation对象。
创建规则
Core Foundation函数的函数名中包含如下名字指明了你拥有函数返回的对象:
- 函数名中包含
Create
代表这是一个创建对象的函数; - 函数名中包含
Copy
代表这是一个拷贝已经存在的对象的函数。
如果你拥有这个对象,那么当你使用完这个对象后有责任解除和这个对象的所有权(使用CFRelease
函数)。
思考下面的例子。第一个例子展示了两个和CFTimeZone
有关的创建函数以及一个和CFBundle
有关的创建函数。
CFTimeZoneRef CFTimeZoneCreateWithTimeIntervalFromGMT (CFAllocatorRef allocator, CFTimeInterval ti);
CFDictionaryRef CFTimeZoneCopyAbbreviationDictionary (void);
CFBundleRef CFBundleCreate (CFAllocatorRef allocator, CFURLRef bundleURL);
第一个函数的函数名包含了Create
,它返回了一个新创建的CFTimeZone对象。你拥有这个对象,当你使用完这个对象有责任取消所有权。第二个函数的函数名包含Copy
,创建了一个CFTimeZone对象的属性的拷贝(注意一下这个函数和获取CFTimeZone对象的属性的函数不一样)。当然,你也拥有这个对象,。当你使用完这个对象有责任取消所有权第三个函数也包含了Create
,尽管可能返回一个已经存在的CFBundle对象。当返回已经存在的CFBundle对象,这个对象的Retain Count
+1,你还是拥有这个对象,所以当你使用完这个对象有责任取消所有权。
下面的例子可能会有点复杂,但还是遵守简单的规则。
/* 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);
CFBag
类中的函数CFBagCreateMutableCopy
的名字中同时有Create
和Copy
。他是一个创建函数,因为函数名中包含Create
。需要注意到第一个参数的类型是CFAllocatorRef
,这个参数很大程度上提示了这一点。函数中的Copy
说明了调用CFBagRef
这个参数并创建了这个参数的拷贝。它也指出源集合(Bag
为一种集合数据类型)中的元素对象发生了什么:它们被拷贝到新创建的Bag
集合中。函数名中次要的Copy
和NoCopy
指明源对象所拥有的对象如何被处理:拷贝或者不拷贝。例如:源Bag对象中的元素对象在CFBagCreateMutableCopy
函数中被拷贝。
获取规则
如果调用除了Create
和Copy
函数值外的Core Foundation函数获取到对象,比如Get
函数,你并没有拥有这个对象,所以你并不清楚这个对象的生命时长。如果你想在使用这个对象的期间抱枕跟着哥对象不被释放,必须要声明所有权。那么当你使用完这个对象有责任取消所有权。
思考CFAttributedStringGetString
函数,返回富文本字符串的字符串对象。
CFStringRef CFAttributedStringGetString (CFAttributedStringRef aStr);
如果作为参数的富文本字符串对象被释放,那么就解除了返回的字符串对象的所有权,如果返回的字符串对象只有富文本字符串对象这一个所有者,那么这个字符串对象就会被释放。如果你在富文本字符串对象被释放之后还想使用字符串对象,你必须声明所有权。所以当你使用完这个对象有责任取消所有权,否则会造成内存泄漏。
实例变量和传参
正常情况下,将一个对象作为参数传递给另一个对象(即接收者的成员属性赋值为参数对象),接收者如果需要保持参数对象的寿命,会拥有参数的所有权。
当一个函数接收了一个作为参数的对象,起初接收者并没有拥有这个参数对象,所以这个参数对象可能会随时被释放,除非接收者声明所有权。当接收者被赋新值或者被释放时,接收者有责任取消参数对象的所有权。
所有权举例
为了避免运行时错误或者内存泄漏问题,你应该始终应用Ownership Policy
无论Core Foundation对象被接收(接收对象的成员变量赋值)、被传递(作为参数)或是被返回(作为返回值)。为了理解为什么你需要对一个并非你创建的对象的声明所有权,思考后面的例子。假设你刚从一个对象获取它拥有的一个值,随后这个对象就被释放了,如果这个值得所有者只有这个对象,那么这个值因为没有所有者也随即被释放。现在你引用了一个空对象,当使用到这个空对象的时候,你的APP就会崩掉。
下面三个代码块:set函数、get函数 和 一个一直强引用Core Foundation对象,直到满足特定条件才释放的函数:
static CFStringRef title = (__bridge CFStringRef)@"abc";
void SetTitle(CFStringRef newTitle) {
CFStringRef temp = title;
title = CFStringCreateCopy(kCFAllocatorDefault , newTitle); // retainCount+1
CFRelease(temp);
}
上面的例子用了一个静态的CFStringRef
变量来保留这个被retain的CFString对象。你也可以用其他方式来保存这个对象,但是你必须把这个对象放在接收函数的作用域外。函数将title
对象赋值给本地变量temp
,随后拷贝了newTitle
对象并释放了temp
(旧的title
)对象。函数在拷贝之后才释放是为了预防传入的newTitle
和静态变量title
是一个对象。
我们需要注意到传入的newTitle
对象不仅仅只是retain,它还被Copy
了(回想一下,从所有权的角度来说Copy
和Retain
是等同的)。使用Copy
的原因是newTitle
作为参数,不希望在函数执行的过程中发生改变,即使参数是CFStringRef
类型,还是有可能指向一个CFMutableStringRef
对象(父类指针指向子类对象)。因此拷贝这个参数对象,可以得到一个不变的对象。如果你想要得到一个不变的对象,那么应该拷贝参数对象,如果只是想保留返回的对象的话,那么只需要使用Retain
就好。
get函数就比较简单了:
CFStringRef GetTitle() {
return title;
}
简单的返回这个对象,将会弱引用这个对象。换句话说,指向这个对象的指针只是被接收者的变量拷贝了一份,但是引用计数并没有改变。从集合中获取元素也同理。
下面的函数保留了从集合中获取到的对象直到不再需要这个对象。假设这个对象是不可变的。
static CFStringRef title = NULL;
void MyFunction(CFDictionaryRef dict, Boolean aFlag) {
if (!title && !aFlag) {
title = (CFStringRef)CFDictionaryGetValue(dict, CFSTR(“title”));
title = CFRetain(title);
}
/* Do something with title here. */
if (aFlag) {
CFRelease(title);
}
}
下面的例子说明想一个数组传递一个number对象。数组的回调函数(kCFTypeArrayCallBacks
)指出添加到数组中的对象都被Retain
了,所以number对象在添加到数组之后能被release
。
float myFloat = 10.523987;
CFNumberRef myNumber = CFNumberCreate(kCFAllocatorDefault,
kCFNumberFloatType, &myFloat);
CFMutableArrayRef myArray = CFArrayCreateMutable(kCFAllocatorDefault, 2, &kCFTypeArrayCallBacks);
CFArrayAppendValue(myArray, myNumber);
CFRelease(myNumber);
// code continues...
需要注意的是下面例子中有一个潜在的隐患,就是在你Release
了数组之后,依然使用数组中的元素:
CFRelease(myArray);
CFNumberRef otherNumber = // ... ;
CFComparisonResult comparison = CFNumberCompare(myNumber, otherNumber, NULL);
除非你Retain
了number元素或者数组,或是将number元素或者数组赋值给了其他的对number元素或者数组拥有所有权的对象。如果上面的条件都不满足的话,数组或者number元素没有所有者,那么将被释放。CFNumberCompare
函数中使用到被释放的对象时将会崩掉。
作为一个刚毕业的应届生,很多东西都在自己摸索,请各位前辈多多指教~