[iOS] Core Foundation - Ownership Policy

偶然间看到这篇文档,所以记录下来。文档出处: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函数中使用到被释放的对象时将会崩掉。


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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容

  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,121评论 29 470
  • 1.项目经验 2.基础问题 3.指南认识 4.解决思路 ios开发三大块: 1.Oc基础 2.CocoaTouch...
    阳光的大男孩儿阅读 4,967评论 0 13
  • 37.cocoa内存管理规则 1)当你使用new,alloc或copy方法创建一个对象时,该对象的保留计数器值为1...
    如风家的秘密阅读 827评论 0 4
  • 虞集《至正改元辛巳寒食日示弟及诸子侄》原诗、注释、翻译、赏析 【原文】:至正改元辛巳寒食日示弟及诸子侄虞集江山信美...
    xcy无名阅读 147评论 0 0
  • 关于向新疆麦盖提县进行爱心图书捐赠的倡议书 同学们: 阅读是人生成长中最为重要的活动之一,是人类获取知识和信息的首...
    郭宝江_科学靠谱阅读 374评论 0 0