版本记录
版本号 | 时间 |
---|---|
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 - 浅拷贝
复制复合对象,可以包含其他对象的集合对象等对象也必须小心处理。正如您所期望的,使用=
运算符对这些对象执行副本会导致对象引用的重复。与CFString
和CFData
这样的简单对象相反,为复合对象(如CFArray和CFSet)提供的CreateCopy
函数实际上会执行浅拷贝。在这些对象的情况下,浅层复制意味着创建新的集合对象,但是原始集合的内容不会被复制 - 只有对象引用被复制到新的容器。如果您有一个不可变的数组,并且您想对其进行重新排序,则此类型的副本很有用。在这种情况下,您不想复制所有包含的对象,因为不需要更改它们,以及为什么要使用额外的内存?您只需要更改包含的对象集。与使用简单类型复制对象引用相同的风险也适用。
2. Deep Copy - 深拷贝
当您要创建一个全新的复合对象时,必须执行深层复制。 深层复制复制复合对象及其所有对象的内容。 Core Foundation的当前版本包括执行属性列表深度复制的函数(请参阅CFPropertyListCreateDeepCopy)。 如果要创建其他结构的深层副本,则可以通过递归递减到复合对象并逐个复制其所有内容来执行深层副本。 当复合对象可以递归时,请注意实现此功能 - 它们可以直接或间接包含对其自身的引用 - 这可能导致递归循环。
Byte Ordering - 字节排序
微处理器架构通常使用两种不同的方法将多字节数字数据的各个字节存储在存储器中。这种差异被称为byte ordering
或endian nature
。大多数情况下,您的计算机的端序格式可以被安全地忽略,但在某些情况下,它变得至关重要。 OS X提供了一种将数据的一种端形式转化为另外一种端模式的各种函数。
Intel x86
处理器首先存储最低有效字节的双字节整数,后跟最高有效字节。这称为小端字节排序。其他CPU(如PowerPC CPU)首先存储其最高有效字节的双字节整数,后跟其最低有效字节。这被称为大字节字节排序。大多数时候,您的计算机的端序格式可以安全地忽略,但在某些情况下,它变得至关重要。例如,如果您尝试从与您的端点性质不同的计算机上创建的文件读取数据,则字节排序的差异可能会产生不正确的结果。从网络读取数据时也会发生同样的问题。
术语:术语big-endian
和little-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中的图表,其中显示了大端处理器或内存系统如何组织示例数据。 在大端系统中,物理内存被组织,每个字节的地址从最高到最低的。
请注意,这些字段存储在左侧的更高有效字节和右侧较少有效字节。 这意味着地址字段Int1
的最高有效字节的地址是0x98
,而地址0x9B
对应于Int1
的最低有效字节。
图2中的图表显示了一个小端系统如何组织数据。
请注意,每个字段的最低地址现在对应于最低有效字节,而不是最高有效字节。如果要在小端系统上打印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
。 可能的返回值为CFByteOrderUnknown
,CFByteOrderLittleEndian
和CFByteOrderBigEndian
。
1. Byte Swapping Integers - 字节交换整数
Core Foundation为字节交换提供了三个优化的基本功能 - CFSwapInt16
,CFSwapInt32
和CFSwapInt64
。 所有其他交换函数都使用这些原语完成他们的工作。 一般来说,您不需要直接使用这些原语。
尽管原始交换功能无条件交换,但是较高级别的交换功能是以不需要字节交换的方式进行定义的,换句话说,当源和主机字节顺序相同时,它们不会执行任何操作。 对于整数类型,这些函数采用CFSwapXXXBigToHost
和CFSwapXXXLittleToHost
,CFSwapXXXHostToBig
和CFSwapXXXHostToLittle
的格式,其中XXX
是诸如Int32
的数据类型。 例如,如果您在一个小端点机器上读取数据为网络字节顺序(big-endian)的网络中的16位整数值,则可以使用函数CFSwapInt16BigToHost
。 Listing 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
字段的影响。 请注意,字节交换代码在小端机上运行时不会执行任何操作。 编译器应优化代码并保留数据不变。
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);
数据类型CFSwappedFloat32
和CFSwappedFloat64
在规范表示中包含浮点值。 CFSwappedFloat
本身不是浮点数,不应该直接用作浮点数。 然而,您可以发送一个到另一个进程,保存到磁盘或通过网络发送。 由于格式是通过转换函数转换为规范格式的,因此不需要显式交换API。 如果需要,在格式转换过程中,会为您处理字节交换。
后记
未完,待续~~~