[iOS]CoreFoundation - Ownership Policy

Ownership Policy

偶然间看到这篇文档,所以记录下来。文档出处:Ownership Policy
当我们在APP中使用CoreFoundation API时,为了避免内存泄漏,在获取对象或者创建对象时应该遵守CoreFoundation的相关规定。

基础

在使用CoreFoundation时,Ownership Policy不仅仅帮助我们理解内存管理,同时还帮助我们理解对象的所属关系。对象可能会有不止一个拥有者,它依靠引用计数(retain count)来记录拥有者的数量,一旦引用计数为0(没有拥有者),这个对象就会被释放。CoreFoundation规定了下面三条规则来进行对象的创建和处理。
* 如果你创建了这个对象(直接创建或者间接拷贝已经存在的对象),那么就拥有这个对象。
* 如果你从别处获取了一个对象,你并没有拥有这个对象,所以为了避免对象被释放,可以使用CFRetain函数来是引用计数+1,成为这个对象的所有者。
* 如果你是这个对象的拥有者,那么在使用完这个对象之后必须使用CFRelease函数解除所有权,否则会造成内存泄漏。

通常命名方式

当你使用CoreFoundation时,有很多方式引用一个对象。按照CoreFoundation的Ownership Policy,你需要知道调用函数返回的对象是否被你拥有,这样才能进行相应的内存管理操作。简单来说,如果一个函数的函数名包含Create或者Copy,那么你拥有这个函数返回的对象。如果函数的函数名包含Get,那么你并没有拥有这个返回的对象。创建规则(The Create Rule)获取规则(The Get Rule)非常细节地解释了这种命名方式。

重点:Cocoa为内存管理提供了一个非常相似的命名方式,CoreFoundation的函数命名方式,
尤其是函数名中带有`Create`的,仅用于C函数返回CoreFoundation对象。
Objective-C的方法命名方式由Cocoa管理,无论方法返回Cocoa对象还是CoreFOundation对象。

创建规则

CoreFoundation函数的函数名中包含如下名字指明了你拥有函数返回的对象:
* 函数名中包含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的名字中同时有CreateCopy。他是一个创建函数,因为函数名中包含Create。需要注意到第一个参数的类型是CFAllocatorRef,这个参数很大程度上提示了这一点。函数中的Copy说明了调用CFBagRef这个参数并创建了这个参数的拷贝。它也指出源集合(Bag为一种集合数据类型)中的元素对象发生了什么:它们被拷贝到新创建的Bag集合中。函数名中次要的CopyNoCopy指明源对象所拥有的对象如何被处理:拷贝或者不拷贝。例如:源Bag对象中的元素对象在CFBagCreateMutableCopy函数中被拷贝。

获取规则

如果调用除了CreateCopy函数值外的CoreFoundation函数获取到对象,比如Get函数,你并没有拥有这个对象,所以你并不清楚这个对象的生命时长。如果你想在使用这个对象的期间抱枕跟着哥对象不被释放,必须要声明所有权。那么当你使用完这个对象有责任取消所有权。
思考CFAttributedStringGetString函数,返回富文本字符串的字符串对象。

CFStringRef CFAttributedStringGetString (CFAttributedStringRef aStr);

如果作为参数的富文本字符串对象被释放,那么就解除了返回的字符串对象的所有权,如果返回的字符串对象只有富文本字符串对象这一个所有者,那么这个字符串对象就会被释放。如果你在富文本字符串对象被释放之后还想使用字符串对象,你必须声明所有权。所以当你使用完这个对象有责任取消所有权,否则会造成内存泄漏。

实例变量和传参

正常情况下,将一个对象作为参数传递给另一个对象(即接收者的成员属性赋值为参数对象),接收者如果需要保持参数对象的寿命,会拥有参数的所有权。
当一个函数接收了一个作为参数的对象,起初接收者并没有拥有这个参数对象,所以这个参数对象可能会随时被释放,除非接收者声明所有权。当接收者被赋新值或者被释放时,接收者有责任取消参数对象的所有权。

所有权举例

为了避免运行时错误或者内存泄漏问题,你应该始终应用Ownership Policy无论CoreFoundation对象被接收(接收对象的成员变量赋值)、被传递(作为参数)或是被返回(作为返回值)。为了理解为什么你需要对一个并非你创建的对象的声明所有权,思考后面的例子。假设你刚从一个对象获取它拥有的一个值,随后这个对象就被释放了,如果这个值得所有者只有这个对象,那么这个值因为没有所有者也随即被释放。现在你引用了一个空对象,当使用到这个空对象的时候,你的APP就会崩掉。

下面三个代码块:set函数、get函数 和 一个一直强引用CoreFoundation对象,直到满足特定条件才释放的函数:

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了(回想一下,从所有权的角度来说CopyRetain是等同的)。使用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函数中使用到被释放的对象时将会崩掉。


作为一个刚毕业的应届生,很多东西都在自己摸索,请各位前辈多多指教~

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值