CoreFoundation框架详细解析(四) —— 内存管理(二)

版本记录

版本号 时间
V1.0 2017.10.07

前言

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

Core Foundation Object Lifecycle Management - Core Foundation对象生命周期管理

感兴趣的可以看一下这里

Core Foundation对象的使用寿命取决于其引用计数 - 希望对象持久存在的客户端数量的内部计数。 在Core Foundation中创建或复制对象时,其引用计数设置为1。 随后的客户端可以通过调用CFRetain来声明对象的所有权,CFRetain会增加引用计数。 后来,当你没有更多的使用对象,你调用CFRelease。 当引用计数达到0时,对象的分配器释放对象的内存。

1. Retaining Object References - 保留对象引用

要增加Core Foundation对象的引用计数,请将对该对象的引用传递给CFRetain函数的参数:

/* myString is a CFStringRef received from elsewhere */

myString = (CFStringRef)CFRetain(myString);

2. Releasing Object References - 释放对象引用

要减少Core Foundation对象的引用计数,请将对该对象的引用传递给CFRelease函数的参数:

CFRelease(myString);

重要提示:您不应该直接释放Core Foundation对象(例如,通过对它调用free)。 完成对象后,调用CFRelease功能,Core Foundation将正确处理它。

3. Copying Object References - 复制对象引用

当您复制对象时,生成的对象的引用计数为1,而不考虑原始对象的引用计数。 有关复制对象的更多信息,请参阅Copy Functions

4. Determining an Object's Retain Count - 确定对象的保留计数

如果您想知道Core Foundation对象的当前引用计数,则将对该对象的引用传递给CFGetRetainCount函数的参数:

CFIndex count = CFGetRetainCount(myString);

但是请注意,除了调试之外,通常不需要确定Core Foundation对象的引用计数。 如果您发现自己需要知道对象的保留计数,请检查您是否正确遵守所有权政策规则(请参阅Ownership Policy)。


Copy Functions - 复制函数

通常,当使用=运算符将一个变量的值分配给另一个变量时,会发生标准复制操作(也可能称为简单赋值)。例如,表达式myInt2 = myInt1会使myInt1的整数内容从myInt1使用的内存复制到myInt2使用的内存中。在复制操作之后,两个独立的内存区域包含相同的值。但是,如果您尝试以这种方式复制Core Foundation对象,请注意,您不会重复对象本身,仅仅是对对象的引用。

例如,Core Foundation的新用户可能会认为要创建CFString对象的副本,她将使用表达式myCFString2 = myCFString1。同样,此表达式实际上并不复制字符串数据。因为myCFString1和myCFString2都必须具有CFStringRef类型,所以此表达式仅复制对该对象的引用。复制操作后,您有两份CFString的引用。这种类型的副本非常快,因为只有引用是重复的,但重要的是要记住以这种方式复制可变对象是危险的。与使用全局变量的程序一样,如果应用程序的一部分使用引用的副本更改对象,那么程序的其他具有该引用副本的部分就无法知道数据已更改。

如果要复制对象,则必须使用Core Foundation提供的函数之一专门用于此目的。继续使用CFString示例,您将使用CFStringCreateCopy创建一个包含与原始数据相同的数据的全新CFString对象。具有CreateCopy函数的Core Foundation类型还提供了可以修改的对象的副本CreateMutableCopy的变体。

1. Shallow Copy - 浅拷贝

复制复合对象,可以包含其他对象的集合对象等对象也必须小心处理。正如您所期望的,使用=运算符对这些对象执行副本会导致对象引用的重复。与CFStringCFData这样的简单对象相反,为复合对象(如CFArray和CFSet)提供的CreateCopy函数实际上会执行浅拷贝。在这些对象的情况下,浅层复制意味着创建新的集合对象,但是原始集合的内容不会被复制 - 只有对象引用被复制到新的容器。如果您有一个不可变的数组,并且您想对其进行重新排序,则此类型的副本很有用。在这种情况下,您不想复制所有包含的对象,因为不需要更改它们,以及为什么要使用额外的内存?您只需要更改包含的对象集。与使用简单类型复制对象引用相同的风险也适用。

2. Deep Copy - 深拷贝

当您要创建一个全新的复合对象时,必须执行深层复制。 深层复制复制复合对象及其所有对象的内容。 Core Foundation的当前版本包括执行属性列表深度复制的函数(请参阅CFPropertyListCreateDeepCopy)。 如果要创建其他结构的深层副本,则可以通过递归递减到复合对象并逐个复制其所有内容来执行深层副本。 当复合对象可以递归时,请注意实现此功能 - 它们可以直接或间接包含对其自身的引用 - 这可能导致递归循环。


Byte Ordering - 字节排序

微处理器架构通常使用两种不同的方法将多字节数字数据的各个字节存储在存储器中。这种差异被称为byte orderingendian nature。大多数情况下,您的计算机的端序格式可以被安全地忽略,但在某些情况下,它变得至关重要。 OS X提供了一种将数据的一种端形式转化为另外一种端模式的各种函数。

Intel x86处理器首先存储最低有效字节的双字节整数,后跟最高有效字节。这称为小端字节排序。其他CPU(如PowerPC CPU)首先存储其最高有效字节的双字节整数,后跟其最低有效字节。这被称为大字节字节排序。大多数时候,您的计算机的端序格式可以安全地忽略,但在某些情况下,它变得至关重要。例如,如果您尝试从与您的端点性质不同的计算机上创建的文件读取数据,则字节排序的差异可能会产生不正确的结果。从网络读取数据时也会发生同样的问题。

术语:术语big-endianlittle-endian来自Jonathan Swift的十八世纪讽刺Gulliver的旅行。 Blefuscu帝国的主体被分为两个派系:从大端开始吃蛋的人和从小端起吃蛋的人。

给出一个讨论端格式问题的具体例子,考虑一个简单的C结构的例子,它定义了两个四字节整数,如Listing 1所示。

// Listing 1  Example data structure

struct {
    UInt32 int1;
    UInt32  int2;
} aStruct;

假设Listing 2中所示的代码用于初始化Listing 1所示的结构。

// Listing 2  Initializing the example structure

ExampleStruct   aStruct;
 
aStruct.int1 = 0x01020304;
aStruct.int2 = 0x05060708;

考虑Figure 1中的图表,其中显示了大端处理器或内存系统如何组织示例数据。 在大端系统中,物理内存被组织,每个字节的地址从最高到最低的。

Figure 1 Example data in big-endian format

请注意,这些字段存储在左侧的更高有效字节和右侧较少有效字节。 这意味着地址字段Int1的最高有效字节的地址是0x98,而地址0x9B对应于Int1的最低有效字节。

图2中的图表显示了一个小端系统如何组织数据。

Figure 2 Example data in little-endian format

请注意,每个字段的最低地址现在对应于最低有效字节,而不是最高有效字节。如果要在小端系统上打印Int1的值,您将看到尽管以不同的字节顺序存储,但它仍然被正确解释为十进制值16909060。

现在假设由Listing 2所示的代码初始化的示例数据值是在小端系统上生成并保存到磁盘。假设数据以字节地址顺序写入磁盘。当通过大端系统从磁盘读取时,数据将再次布置在存储器中,如Figure 2所示。问题是数据仍然是小端字节顺序,即使它是在大型端系统。该差异导致值被错误评估。在本示例中,Int1域的十进制值应为16909060,但是由于字节排序不正确,因此它被评估为67305985.这种现象称为字节交换,一般发生在当一个端格式的数据被使用其他字符串格式时。

不幸的是,这是一般情况下无法解决的问题。原因是您交换的方式取决于数据的格式。字符串通常不会被交换,长字交换四字节到端,字交换两个字节端到端。因此,需要交换数据的任何程序必须知道数据类型,源数据端序和主机端序。

CFByteOrder.h中的函数允许您对双字节和四字节整数以及浮点值进行字节交换。适当使用这些功能可以帮助您确保程序操作的数据正确。有关使用这些函数的详细信息,请参阅Byte Swapping 部分。 请注意,Core Foundation的字节交换函数仅适用于OS X。


Using Allocators in Creation Functions - 在创建函数中使用分配器

每个Core Foundation不透明类型都有一个或多个创建函数,该函数创建并返回以特定方式初始化的该类型的对象。所有创建函数都将其作为第一个参数作为对分配器对象(CFAllocatorRef)的引用。某些函数也可能具有用于专门分配和释放目的的分配器参数。

分配器参考参数有几个选项:

  • 你可以传递常量kCFAllocatorSystemDefault,这指定了通用系统分配器(它是初始默认分配器)。
  • 您可以传递NULL来指定当前的默认分配器(可能是自定义分配器或通用系统分配器)。这与传递kCFAllocatorDefault相同。
  • 您可以传递常量kCFAllocatorNull,该常量指示不分配的分配器,尝试使用它是错误的。一些创建函数具有用于重新分配或释放后备存储的特殊分配器的参数,通过为参数指定kCFAllocatorNull,可以防止自动重新分配或释放。
  • 您可以使用CFGetAllocator函数获得另一个Core Foundation对象使用的分配器的引用。通过使用相同的分配器分配它们,可以将相关对象放入内存zone中。
  • 您可以传递对自定义分配器的引用(请参阅Creating Custom Allocators)。

如果要使用自定义分配器,并且要使其成为默认分配器,建议首先使用CFAllocatorGetDefault函数获取对当前默认分配器的引用,并将其存储在局部变量中。完成使用自定义分配器后,使用CFAllocatorSetDefault函数将存储的分配器重置为默认分配器。


Using the Allocator Context - 使用分配器上下文

Core Foundation中的每个分配器都有一个上下文。 上下文是定义对象的操作环境的结构,通常由函数指针组成。 分配器的上下文由CFAllocatorContext结构定义。 除了函数指针之外,结构还包含版本号和用户定义数据的字段。

// Listing 1  The CFAllocatorContext structure

typedef struct {
    CFIndex version;
    void * info;
    const void *(*retain)(const void *info);
    void (*release)(const void *info);
    CFStringRef (*copyDescription)(const void *info);
    void * (*allocate)(CFIndex size, CFOptionFlags hint, void *info);
    void * (*reallocate)(void *ptr, CFIndex newsize, CFOptionFlags hint, void *info);
    void (*deallocate)(void *ptr, void *info);
    CFIndex (*preferredSize)(CFIndex size, CFOptionFlags hint, void *info);
} CFAllocatorContext;

info字段包含分配器的任何特别定义的数据。 例如,分配器可以使用info字段来跟踪特殊的分配。

重要提示:对于当前版本,不要将version字段的值设置为0以外的任何值。

如果在分配器上下文(info字段)中有一些用户定义的数据,请使用CFAllocatorGetContext函数获取分配器的CFAllocatorContext结构。 然后根据需要评估或处理数据。 以下代码提供了一个示例:

// Listing 2  Getting the allocator context and user-defined data
 
static int numOutstandingAllocations(CFAllocatorRef alloc) {
    CFAllocatorContext context;
    context.version = 0;
    CFAllocatorGetContext(alloc, &context);
    return (*(int *)(context.info));
}

其他Core Foundation函数调用在分配器上下文中定义的与内存相关的回调,并将一个无类型的指针取回或返回到一个内存块(void *):

  • CFAllocatorAllocate,分配一个内存块。
  • CFAllocatorReallocate,重新分配一块内存。
  • CFAllocatorDeallocate,取消分配一块内存。
  • CFAllocatorGetPreferredSizeForSize,根据给定的一个请求,给出了可能被分配的内存大小。

Creating Custom Allocators - 创建自定义分配器

要创建自定义分配器,首先声明并初始化CFAllocatorContext类型的结构。 将版本字段初始化为0,并将任何所需的数据(如控制信息)分配并分配给info字段。 此结构的其他字段是在下面的Implementing Allocator Callbacks中描述的函数指针。

一旦将适当的值分配给CFAllocatorContext结构的字段,则调用CFAllocatorCreate函数来创建allocator对象。 该函数的第二个参数是指向结构的指针。 此函数的第一个参数标识用于为新对象分配内存的分配器。 如果要在CFAllocateContext结构中为此使用allocate回调,请为第一个参数指定kCFAllocatorUseContext常量。 如果要使用默认分配器,请在此参数中指定NULL

// Listing 1  Creating a custom allocator

static CFAllocatorRef myAllocator(void) {

static CFAllocatorRef allocator = NULL;

if (!allocator) {

CFAllocatorContext context =

{0, NULL, NULL, (void *)free, NULL,

myAlloc, myRealloc, myDealloc, NULL};

context.info = malloc(sizeof(int));

allocator = CFAllocatorCreate(NULL, &context);

}

return allocator;

}

1. Implementing Allocator Callbacks - 实现分配器回调

CFAllocatorContext结构有七个定义回调函数的字段。 如果创建自定义分配器,则必须至少实现allocate函数。 分配器回调应该是线程安全的,如果回调函数调用其他函数,它们也应该是重入的。

保留,释放和复制描述回调都以CFA1locatorContext结构的info字段为单参数。 键入为void *,此字段指向您为分配器定义的任何数据,例如包含控制信息的结构体。

Retain 回调:

const void *(*retain)(const void *info);

info中保留您为分配器上下文定义的数据。 这只有在数据是Core Foundation对象时才有意义。 您可以将此函数指针设置为NULL

Release回调

void (*release)(const void *info);

Release(或free)您为分配器上下文定义的数据。 您可以将此函数指针设置为NULL,但这样做可能会导致内存泄漏。

Copy Description回调:

CFStringRef (*copyDescription)(const void *info);

返回对描述您的分配器的CFString的引用,特别是用户定义数据的某些特性。 您可以将此函数指针设置为NULL,在这种情况下,Core Foundation将提供基本描述。

Allocate回调

void *   (*allocate)(CFIndex size, CFOptionFlags hint, void *info);

分配至少size字节的内存块,并返回指向块开头的指针。 hint参数是一个你现在应该不使用的位域。 size参数应该始终大于0,如果不是,或者发生分配问题,返回NULL。 此回调可能不为NULL

Reallocate回调

void *   (*reallocate)(void *ptr, CFIndex newsize, CFOptionFlags hint, void *info);

将由ptr指向的内存块的大小更改为由newsize指定的大小,并将指针返回到较大的内存块。 在任何重新分配失败时返回NULL,使旧的内存块不变。 请注意,ptr参数永远不会为NULL,而newsize将始终大于0 - 除非满足这两个条件,否则不使用该回调。

将旧内存块的内容保持不变,直到较小的新尺寸或旧尺寸。 如果ptr参数不是先前由分配器分配的内存块,则结果未定义;异常程序终止可能发生。 hint参数是一个你现在应该不使用的位域。 如果将此回调设置为NULL,则当它尝试使用该分配器时,CFAllocatorReallocate函数在大多数情况下返回NULL。

Deallocate回调

void   (*deallocate)(void *ptr, void *info);

使ptr指向的内存块可用于分配器的后续重用,但不可用于程序的继续使用。 ptr参数不能为NULL,如果ptr参数不是先前由allocator分配的内存块,则结果未定义,异常程序终止可能发生。 您可以将此回调设置为NULL,在这种情况下,CFAllocatorDeallocate函数不起作用。

Preferred Size回调

CFIndex   (*preferredSize)(CFIndex size, CFOptionFlags hint, void *info);

返回分配器可能分配的实际大小,给出对size大小的内存块的请求。 hint参数是一个你现在应该不使用的位域。


Byte Swapping - 字节交换

如果您需要找到主机字节顺序,您可以使用函数CFByteOrderGetCurrent。 可能的返回值为CFByteOrderUnknownCFByteOrderLittleEndianCFByteOrderBigEndian

1. Byte Swapping Integers - 字节交换整数

Core Foundation为字节交换提供了三个优化的基本功能 - CFSwapInt16CFSwapInt32CFSwapInt64。 所有其他交换函数都使用这些原语完成他们的工作。 一般来说,您不需要直接使用这些原语。

尽管原始交换功能无条件交换,但是较高级别的交换功能是以不需要字节交换的方式进行定义的,换句话说,当源和主机字节顺序相同时,它们不会执行任何操作。 对于整数类型,这些函数采用CFSwapXXXBigToHostCFSwapXXXLittleToHostCFSwapXXXHostToBigCFSwapXXXHostToLittle的格式,其中XXX是诸如Int32的数据类型。 例如,如果您在一个小端点机器上读取数据为网络字节顺序(big-endian)的网络中的16位整数值,则可以使用函数CFSwapInt16BigToHostListing 1演示了这个过程。

// Listing 1  Swapping a 16 bit Integer

SInt16  bigEndian16;
SInt16  swapped16;
 
// Swap a 16 bit value read from network.
swapped16 = CFSwapInt16BigToHost(bigEndian16);

Byte Ordering部分介绍了一个简单的C结构示例,该C结构创建并保存到小端机上的磁盘,然后从大端机器上的磁盘读取。 为了纠正这种情况,您必须交换每个字段中的字节。 Listing 2中的代码演示了如何使用Core Foundation字节交换函数来完成此操作。

// Listing 2  Byte swapping fields in a C structure

// Byte swap the values if necessary.
aStruct.int1 = CFSwapInt32LittleToHost(aStruct.int1)
aStruct.int2 = CFSwapInt32LittleToHost(aStruct.int2)

假设一个大端的架构,Listing 2中使用的函数将交换每个字段中的字节。 Figure 1显示了字段交换对aStruct.int1字段的影响。 请注意,字节交换代码在小端机上运行时不会执行任何操作。 编译器应优化代码并保留数据不变。

Figure 1 Four-byte little-endian to big-endian swap

2. Byte Swapping Floating-Point Values - 字节交换浮点值

即使在单个平台上,浮点值也可以有许多不同的表示形式。 除非你非常小心,否则尝试在平台边界上传递浮点值会让人无尽的头疼。 为了帮助您处理浮点数,Core Foundation定义了一组函数和两个特殊的数据类型以及整数交换函数。 这些函数允许您对32位和64位浮点值进行编码,以便稍后对其进行解码,并在必要时进行字节交换。 Listing 3 显示了如何编码64位浮点数,Listing 4显示了如何解码它。

// Listing 3  Encoding a Floating Point Value
Float64 myFloat64;

CFSwappedFloat64 swappedFloat;

// Encode the floating-point value.

swappedFloat = CFConvertFloat64HostToSwapped(myFloat64);
// Listing 4  Decoding a floating-point value

Float64             myFloat64;
CFSwappedFloat64    swappedFloat;
 
// Decode the floating-point value.
myFloat64 = CFConvertFloat64SwappedToHost(swappedFloat);

数据类型CFSwappedFloat32CFSwappedFloat64在规范表示中包含浮点值。 CFSwappedFloat本身不是浮点数,不应该直接用作浮点数。 然而,您可以发送一个到另一个进程,保存到磁盘或通过网络发送。 由于格式是通过转换函数转换为规范格式的,因此不需要显式交换API。 如果需要,在格式转换过程中,会为您处理字节交换。

后记

未完,待续~~~

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

推荐阅读更多精彩内容