侧边栏壁纸
博主头像
落叶人生博主等级

走进秋风,寻找秋天的落叶

  • 累计撰写 130555 篇文章
  • 累计创建 28 个标签
  • 累计收到 9 条评论
标签搜索

目 录CONTENT

文章目录

[iOS] Core Foundation - Ownership Policy

2022-06-30 星期四 / 0 评论 / 0 点赞 / 27 阅读 / 14239 字

写在最前,这是我去年年初翻译的文章,发布在简书上,现在同样发布在编程圈上,配合引用计数系列文章使用。偶然间看到这篇文档,所以记录下来。文档出处:Ownership Policy当我们在APP中使用Co

.

写在最前,这是我去年年初翻译的文章,发布在简书上,现在同样发布在..上,配合引用计数系列文章使用。

.

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

获取规则

如果调用除了CreateCopy函数值外的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了(回想一下,从所有权的角度来说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函数中使用到被释放的对象时将会崩掉。


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

.
.

广告 广告

评论区