CoreFoundation框架详细解析(三) —— 内存管理(一)

版本记录

版本号 时间
V1.0 2017.10.06

前言

Core Foundation框架(CoreFoundation.framework)是一组C语言接口,它们为iOS应用程序提供基本数据管理和服务功能。接下来我们就详细的解析这个框架。感兴趣的可以看我上面写的几篇。
1. CoreFoundation框架详细解析(一) —— 基本概览
2. CoreFoundation框架详细解析(二) —— 设计概念

Introduction - 简介

对于管理内存,Core Foundation使用分配器,引用计数机制以及由函数名称提示的对象所有权策略。 本主题介绍了创建,复制,保留和释放对象的相关技术。

内存管理是有效和高效地使用Core Foundation的基础。 本文档对于使用Core Foundation的所有开发人员来说都是必不可少的。

1. Organization of This Document - 文章组织

以下概念和任务讨论内置支持Core Foundation提供的用于管理对象的内存分配和释放:

如果您需要自定义您的分配器,请阅读:

要了解更多有关字节排序和交换的信息,请参阅:

2. 其他参考

你也许感兴趣:


Allocators - 分配器

在操作系统服务中,Core Foundation大要的是内存分配。它为此目的使用分配器。

分配器是为您分配和释放内存的不透明对象。您无需直接为Core Foundation对象分配,重新分配或释放内存,而且很少需要。将分配器传递给创建对象的函数,这些函数在其名称中嵌入了Create,例如CFStringCreateWithPascalString。创建函数使用分配器为其创建的对象分配内存。

分配器通过其生命周期与对象相关联。如果需要重新分配内存,则该对象使用分配器进行此目的,并且当对象需要释放时,分配器将用于对象的释放。分配器也用于创建最初创建的对象所需的任何对象。一些函数还允许您传递分配器用于特殊目的,例如释放临时缓冲区的内存。

Core Foundation允许您创建自己的自定义分配器。 Core Foundation还提供了一个系统分配器,并初始将此分配器设置为当前线程的默认分配器。 (每个线程有一个默认分配器。)您可以在代码中随时将自定义分配器设置为线程的默认分配器。然而,系统分配器是一个很好的通用分配器,对于几乎所有情况都应该是足够的。在特殊情况下可能需要自定义分配器,例如在Mac OS 9中的某些情况下,或者在性能出现问题时作为批量分配器。除了这些罕见的场合,您不应使用自定义分配器或将其设置为默认值,尤其是对于库。

有关分配器的更多信息,具体来说,有关创建自定义分配器的信息,请参阅Creating Custom Allocators


Ownership Policy - 所有权政策

使用Core Foundation的应用程序不断访问和创建和处理对象。 为了确保不泄漏内存,Core Foundation定义了获取和创建对象的规则。

1. Fundamentals - 基本

在Core Foundation应用程序中尝试了解内存管理时,不要以内存管理本身来思考,而是以对象所有权为依据。 对象可能有一个或多个所有者;它使用保留计数记录拥有者的数量。 如果对象没有所有者(如果其保留计数下降到零),则它被处理(被释放)。 Core Foundation定义了对象所有权和处置的以下规则。

  • 如果您创建对象(直接或通过制作另一个对象的副本 - 请参阅The Create Rule),您拥有它。
  • 如果你从别的地方得到一个对象,你就不拥有它。 如果要防止它被处置,您必须将自己添加为所有者(使用CFRetain)。
  • 如果您是对象的所有者,则必须在完成使用后放弃所有权(使用CFRelease)。

2. Naming Conventions - 命名约定

有很多方法可以使用Core Foundation获取对象的引用。根据Core Foundation所有权政策,您需要知道您是否拥有一个函数返回的对象,以便您知道在内存管理方面采取了哪些措施。 Core Foundation已经为其函数建立了一个命名约定,允许您确定是否拥有由函数返回的对象。简而言之,如果函数名称包含单词CreateCopy,那么您拥有该对象。如果函数名称包含单词Get,则不拥有该对象。这些规则将在The Create RuleThe Get Rule中有更详细的解释。

重要提示Cocoa定义了一组类似于内存管理的命名约定(参见Advanced Memory Management Programming Guide)。 Core Foundation的命名约定,特别是使用单词create,仅适用于返回Core Foundation对象的C函数。 Objective-C方法的命名约定由Cocoa约定管理,不管该方法是返回Core Foundation还是Cocoa对象。

3. The Create Rule - 创建规则

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,并创建时区对象属性的副本。 (注意,这不同于获取属性本身 - 请参阅The Get Rule。)同样,您拥有此对象,您有责任放弃所有权。 第三个函数CFBundleCreate在其名称中包含单词Create,但文档规定它可能返回现有的CFBundle。 然而,再一次,你拥有这个对象,无论是否创建了一个新对象。 如果现有的对象被返回,则其保留计数将被递增,因此您有责任放弃所有权。

下一个示例可能看起来更复杂,但它仍然遵循相同的简单规则。

/* 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参数并生成对象的副本。 它还指的是源集合的元素对象会发生什么:它们被复制到新创建的包中。 函数名称的CopyNoCopy子字符串表示如何处理某些源对象拥有的对象,即它们是否被复制。

4. The Get Rule - 获取规则

如果您收到来自任何Core Foundation函数的对象,而不是创建或复制函数(例如Get函数),则您不拥有该对象,并且无法确定对象的使用寿命。 如果要确保在使用它时不会处理这样的对象,则必须声明所有权(使用CFRetain函数)。 完成后,您将负责放弃所有权。

考虑CFAttributedStringGetString函数,它返回属性字符串的后备字符串。

CFStringRef CFAttributedStringGetString (CFAttributedStringRef aStr);

如果归因的字符串被释放,它将放弃支持字符串的所有权。 如果归因的字符串是支持字符串唯一的所有者,那么支持字符串现在没有所有者,它本身就被释放。 如果您在归因字符串被处理后需要访问支持字符串,则必须声明所有权(使用CFRetain) - 或者复制它。 然后,您必须放弃所有权(使用CFRelease),否则会创建内存泄漏。

5. Instance Variables and Passing Parameters - 实例变量和传递参数

基本规则的推论是,当您将对象传递给另一个对象(作为函数参数)时,应该期望接收者如果需要维护传递的对象,则可以拥有所传递的对象。

要理解这一点,把自己放在接收对象的实现者的位置。 当函数接收到一个对象作为参数时,接收方最初不拥有该对象。 因此,该对象可以在任何时候被释放 - 除非接收者拥有if(使用CFRetain)的所有权。 当接收方完成对象时,或者是因为它被替换了一个新值,或者因为接收方本身被释放 - 接收方负责放弃所有权(使用CFRelease)。

6. Ownership Examples - 所有权举例

为了防止运行时错误和内存泄漏,您应该确保在Core Foundation对象被接收,传递或返回的地方始终应用Core Foundation所有权策略。 要了解为什么可能需要成为您没有创建的对象的所有者,请考虑此示例。 假设你从另一个对象获取一个值。 如果值的containing对象随后被释放,它放弃对contained对象的所有权。 如果包含对象是该值的唯一所有者,则该值不具有所有者,并且也被释放。 您现在可以引用一个被释放的对象,如果您尝试使用它,您的应用程序将崩溃。

以下代码片段说明了三种常见的情况:一个Set访问器函数,一个Get访问器函数和一个保存在Core Foundation对象上的函数,直到满足某个条件。 首先Set函数:

static CFStringRef title = NULL;
void SetTitle(CFStringRef newTitle) {
    CFStringRef temp = title;
    title = CFStringCreateCopy(kCFAllocatorDefault , newTitle);
    CFRelease(temp);
}

上述示例使用静态CFStringRef变量来保存保留的CFString对象。您可以使用其他方式来存储它,但是您必须将其放在当然不在本地接收函数的某个地方。该函数将当前标题分配给本地变量,然后再复制新标题并释放旧标题。如果传入的CFString对象与当前持有的对象相同,它将在复制后释放。

请注意,在上述示例中,对象被复制而不是简单地保留。 (回想一下,从所有权角度看,这些是等效的 - 参见Fundamentals。)原因是title属性可能被认为是属性。这是不应该改变的东西,除非通过访问器方法。即使参数键入CFStringRef,也可能会传入对CFMutableString对象的引用,这将允许值外部更改的可能性。因此,您复制对象,以便在持有该对象时不会更改该对象。如果对象是或可以是可变的,您应该复制一个对象,并且需要您自己的唯一版本。如果对象被认为是关系,那么你应该保留它。

相应的Get函数要简单得多:

CFStringRef GetTitle() {
    return title;
}

简单的返回一个对象,你将返回一个对它的弱引用。 换句话说,指针值被复制到接收器的变量,但引用计数不变。 返回集合中的元素时也会发生相同的情况。

以下函数retain从集合中检索到的对象,直到它不再需要它,然后释放它。 假定该对象是不可变的。

static CFStringRef title = NULL;
void MyFunction(CFDictionary 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对象传递给数组。 数组的回调指定添加到集合中的对象被retained(集合拥有它们),因此该number可以在添加到数组后被释放。

float myFloat = 10.523987;
CFNumberRef myNumber = CFNumberCreate(kCFAllocatorDefault,
                                    kCFNumberFloatType, &myFloat);
CFMutableArrayRef myArray = CFArrayCreateMutable(kCFAllocatorDefault, 2, &kCFTypeArrayCallBacks);
CFArrayAppendValue(myArray, myNumber);
CFRelease(myNumber);
// code continues...

请注意,如果(a)您释放阵列,并且(b)在释放数组后继续使用number变量,则存在潜在的陷阱:

CFRelease(myArray);
CFNumberRef otherNumber = // ... ;
CFComparisonResult comparison = CFNumberCompare(myNumber, otherNumber, NULL);

除非保留number或数组,否则传递给维护其所有权的其他对象,代码将在比较函数中失败。 如果没有其他对象拥有数组或number,当数组被释放时,它也被释放,因此释放其内容。 在这种情况下,这也将导致number的释放,因此比较函数将在释放的对象上运行,从而崩溃。

后记

未完,待续~~~

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

推荐阅读更多精彩内容