目的
FastCoder 是一种针对 Cocoa 对象和对象图的高性能二进制序列化格式。它被用作 NSPropertyList、NSJSONSerializer、NSKeyedArchiver/Unarchiver 和 Core Data 的替代品。
FastCoder 库的设计目标是快速、灵活且安全。
FastCoder 在读取速度上(平均而言)已经比 Cocoa 中内置的任何序列化机制都快,并且在写入速度上仅比 JSON(不支持任意对象类型)慢。文件大小小于 NSKeyedArchiver,与其他方法相当。
FastCoder 支持比 JSON 或 Plist 编码(包括 NSURL、NSValue、NSSet 和 NSOrderedSet)更多的数据类型,并且允许所有支持的对象类型用作字典的键,而不仅仅是字符串。
FastCoder 还可以自动使用属性检查来序列化您的自定义类。对于无法自动处理的情况,您可以使用 FastCoding 或 NSCoding 协议轻松实现自己的序列化。
支持的操作系统和 SDK 版本
- 支持的构建目标 - iOS 11.0 / Mac OS 10.12(Xcode 9.2)
- 最早支持的部署目标 - iOS 9.0 / Mac OS 10.10
- 最早兼容的部署目标 - iOS 4.0 / Mac OS 10.6
注意:'支持'意味着库已与该版本进行了测试。'兼容'意味着库应在该操作系统版本上运行(即它不依赖于任何不可用的 SDK 功能),但不再对其进行兼容性测试,可能需要调整或错误修复才能正常运行。
ARC 兼容性
FastCoder 与 ARC 和非 ARC 编译目标都兼容,但禁用 ARC 后性能更好,建议您将编译器标志 -fno-objc-arc
应用于 FastCoder.m 类。要这样做,请转到目标设置中的构建阶段选项卡,打开编译源代码组,双击列表中的 FastCoder.m,并在弹出窗口中输入 -fno-objc-arc
。
线程安全性
可以从多个线程安全地调用 FastCoder 编码和解码类方法。如果在不改变对象编码期间不修改该对象,还应该可以在多个线程中并发地对同一对象进行编码。
安装
要使用 FastCoder,只需将 FastCoder.h 和 .m 文件拖入项目中。
安全性
在解析时,FastCoder 解析器会检查缓冲区溢出错误,并在数据指示其尝试读取数据文件末尾之后的位置时抛出异常。这可以防止大多数类型的代码注入攻击。
尽管使用 FastCoded 文件进行代码注入是不可能的,但与 NSCoding 类似,攻击者可能会使用修改后的 FastCoded 文件在您的对象图中创建意外的类,这可能会造成潜在的攻击风险(请注意,只能通过这种方式创建已存在于您的代码库或内置 Cocoa 框架中已存在的类)。
目前,最好不要尝试从不受信任的来源加载任意 FastCoded 文件(尽管可以在应用程序沙盒内部使用它们进行数据保存)。如果您想在应用程序或用户之间交换 FastCoded 文件,请仅使用明确支持的类类型(如下所示),并使用 propertyListWithData:
加载文件,它只支持安全类型,并且会拒绝加载包含未知类的文件。FastCoding 库的将来版本将添加更复杂的白名单功能。
支持的类
FastCoding原生支持以下类类型:
NSNull
NSNumber
NSDecimalNumber
NSValue (only the following subtypes)
CGPoint/NSPoint
CGSize/NSSize
CGRect/NSRect
CGVector
NSRange
CGAffineTransform
CATransform3D
NSString
NSMutableString
NSArray
NSMutableArray
NSDictionary
NSMutableDictionary
NSSet
NSMutableSet
NSOrderedSet
NSMutableOrderedSet
NSIndexSet
NSMutableIndexSet
NSData
NSMutableData
NSDate
FastCoding还自动支持任何符合NSCoding协议的类、任何属性都是KVC兼容的类,或者显式实现了FastCoding协议的类。
FastCoder方法
FastCoder实现了以下方法:
+ (id)objectWithData:(NSData *)data;
从FastCoded数据对象构建一个对象树,并返回它。由于FastCoder目前不提供类验证或替换的机制,因此此方法不应用于加载来源不明的文件(即您无法保证文件的来源,或者文件没有被篡改)。对于应用之间的文件交换或用户可访问的文档,建议使用propertyListWithData:
方法来加载文件(尽管这不支持任意类)。
如果文件中遇到未知类类型,它将作为属性字典加载数据,稍后可以将其转换回真正的类(见下文的'引导')。尝试加载不规范或有损坏的文件将引发异常(内部捕获)并返回nil。
+ (id)propertyListWithData:(NSData *)data;
类似于objectWithData:
,但此方法仅限于加载诸如NSString
、NSNumber
、NSArray
等“安全”对象类型。尽管名字叫这个名字,但实际上此方法并不限于由NSPropertyList
支持的对象 – 例如它支持NSNull
和NSURL
– 但是对于从不可信来源加载文件来说是安全的。与普通属性列表方法不同,此方法还可以处理循环引用、别名对象和可变的容器,如NSMutableArray
。尝试加载不规范或有损坏的文件或包含不受支持类的文件将引发异常(内部捕获)并返回nil。
+ (NSData *)dataWithRootObject:(id)object;
将对象图存档为数据块,该数据块可以随后保存到文件或通过网络发送。可以使用objectWithData:
或propertyListWithData:
再次加载文件(只要它只包含支持的类类型)。
FastCoding协议
FastCoding支持使用FastCoding协议对任意对象进行编码/解码。FastCoding协议是一个非正式协议,作为NSObject的范畴实现。该协议包括以下方法
+ (NSArray *)fastCodingKeys;
此方法返回应编码/解码的对象的属性名称列表。默认实现自动检测对象的所有非虚拟(即 ivar 支持的)@属性(包括私有和只读属性),并返回它们,所以在大多数情况下不需要重写此方法。注意:如果你重写了 +fastCodingKeys
,应该只包含当前类的属性键,而不是从父类继承的属性。
- (id)awakeAfterFastCoding;
此方法在对象使用 FastCoding 协议反序列化之后调用,并且所有属性都已设置。反序列化的对象将被此方法返回的对象替换,因此你可以使用此方法修改或完全替换对象。默认实现仅返回 self
。注意:在 -awakeAfterFastCoding
中返回不同的对象可能会导致解码的(或其子项)对象中含有自引用而出现意外的行为。
- (Class)classForFastCoding;
此方法用于提供用于编码/解码对象的替代类。它与 NSCoding 的 -classForCoder
方法相同,默认情况下返回相同的值。
- (BOOL)preferFastCoding;
由于 FastCoding 自动支持 NSCoding,任何符合 NSCoding 协议的对象(除了 FastCoding 明确支持的类型)默认将使用 NSCoding 方法进行编码。这对于兼容性更好,但可能比使用 FastCoding 协议要慢得多。如果你的类同时支持 NSCoding 和 FastCoding,并且你希望 FastCoder 使用 FastCoding 协议,则重写此方法并返回 YES
(默认值为 NO
)。
- (BOOL)preferKeyedArchiver;
有时你可能发现某个类与 FastCoding 协议不正常工作。通常这只适用于内置的 Foundation 或 UIKit/AppKit 类(目前为止已知的一个案例是 AppKit 的 NSColorSpace
- 如果你找到其他任何案例,请报告它们)。在这种情况下,你可以使用 preferKeyedArchiver
在 FastCoded 存档内使用普通的 NSKeyedArchive
对这些对象进行编码。请注意,如果将 preferFastCoding
设置为 YES
,此选项没有效果。
重写默认的 FastCoding 行为
如果你希望排除你的对象中的一些属性不进行编码,你可以按以下方式之一操作
- 只使用 ivar,而不声明匹配的
@property
。 - 将 ivar 的名称更改为一个不遵守 KVC 规范的名称(即,与属性不匹配,或带有 _ 前缀的属性名称)。你可以使用
@synthesize
指令来做到这一点,例如@synthesize foo = unencodableFoo;
- 重写 +fastCodingKeys 方法
如果你希望编码未被 @property
表示的附加数据,重写 +fastCodingKeys
方法并添加你的虚拟属性的名称。你需要实现这些属性的适当设置/获取方法,否则编码/解码过程将不起作用。
如果你希望在解码后使用不同的类,可以实现 -classForFastCoding
方法,FastCoding 将将该对象编码为该类。如果你希望在解码后使用不同的对象,请使用 -awakeAfterFastCoding
方法。
如果您已经移除或重命名了一个类的属性,并且想为之前保存的FastCoder文件提供向下兼容,您应该实现一个私有setter方法来为旧的属性,然后将其映射到新对象结构中的相应位置。例如,如果旧的属性名为foo,则添加一个私有方法-setFoo:
。或者,可以覆盖方法-setValue:forUndefinedKey:
,以优雅地处理任何未知属性。
如果您希望对编码有更精确的控制,例如使用不同的键名等,则可以实现NSCoding协议。默认情况下,如果类实现了NSCoding,FastCoder会依赖于NSCoding方法来编码对象,而不是自动检测键。
自举
有时,通过使用类似JSON这样的人可读格式手动定义对象图是有用的,但是编写将JSON字典转换为正确自定义对象的递归逻辑非常繁琐且容易出错。
FastCoder有一个很棒的功能,允许您通过FastCoding格式将精心组织的JSON或Plist文件自举到一个本地对象图中。要使用JSON定义类Foo的对象,您可以使用以下结构
{
"$class": "Foo",
"someProperty1": 1
"someProperty2": "Hello World!"
}
您可以将其作为普通NSDictionary
使用NSJSONSerialization
加载。但是,当您使用FastCoder将其转换为数据时,它将检测到键并在自定义对象记录格式中保存,而不是作为字典。当再次解码生成的FastCoded数据时,它将初始化为Foo类型对象而不是字典。
如果您尝试在一个不包含名为Foo的类的应用程序中加载FastCoded文件,Foo对象将被作为普通字典加载,带有键名classpath,并在对象被再次序列化时作为自定义对象保存。这意味着可以编写可以处理任意FastCoded文件的应用程序和脚本,而无需了解或实现文件中使用的所有类(使用NSCoding
是不可能的,至少需要做更多的工作)。
别名
与普通的Plist或JSON文件相比,与NSKeyedArchive或FastCoded文件相比的另一个限制是它们不支持指针或引用。如果您想在JSON文件中的多个键指同一个对象实例,您无法做到这一点。FastCoding使用别名来解决这个问题。
与用于自举自定义对象类型的$class语法一样,FastCoding将具有名称$alias的键视为内部文件引用。$alias值是相对于文件根对象的键路径,用于指定一个现有的对象实例。例如,以下是一个JSON示例
{
"foo": {
"baz": { "text": "Hello World" },
"someProperty1": 1
},
"bar": {
"baz": { "text": "Hello World" },
"someProperty2": 2
}
}
在这里,对象foo和bar都包含对象baz。但是,如果您想foo和bar都引用相同的baz实例,可以这样做:
{
"foo": {
"baz": { "text": "Hello World" },
"someProperty1": 1
},
"bar": {
"baz": { "$alias": "foo.baz" }
"someProperty2": 2
}
}
请注意,在bar中的baz包含了对foo内部baz的别名。当保存为FastCoded文件然后重新加载时,实际上将是同一对象。无论是bar.baz别名foo.baz还是相反,FastCoder别名支持前向引用,甚至是循环引用(其中对象包含对自己的别名)。别名语法类似于keypath,但是,与常规keypath不同,您可以使用数字表示数组索引。例如,在下面的代码中,foo指向bar数组中的第二个对象“Cruel”
{
"foo": { "$alias": "bar.1" },
"bar": [ "Goodbye", "Cruel", "World" ]
}
文件结构
FastArchive格式非常简单
它包含一个由32位标识符组成的标题,后面跟着两个16位版本号(主版本和次版本号)。标题后面是三个32位整数,分别表示文件中编码的唯一对象、类和字符串的总数(这不同于块的数量)。可以使用这些值提前设置对象缓存的容量,这提供了一些性能优势。如果对象计数未设置(值为0),则缓存将动态增长。
在标题和对象计数之后,是一系列块。每个块由一个8位类型标识符组成,后面跟着0个或多个根据块类型的数据字节。
常用类型和值通过自己的块来表示,以减少文件大小和数据处理开销。复合或可变长度的类型,如字符串或集合,编码在块后的字节序列中。
从版本3.0迁移
不幸的是,由于数据对齐问题,版本3.0的文件在ARM7/ARM7s上崩溃(这在3.0.1版本中已修复)。如果您使用该版本保存了数据且需要恢复,请执行以下操作
在FastCoder.m文件中找到以下宏
#define FC_ALIGN_INPUT(type, offset) { \
unsigned long align = offset % sizeof(type); \
if (align) offset += sizeof(type) - align; }
修改如下
#define FC_ALIGN_INPUT(type, offset)
加载文件并再次保存。现在将宏改回。
发布说明
版本3.3
- 修复了解码过程中释放对象导致的 crashes with exc_bad_access
- 增加了
prefersKeyedArchive
选项,以修复编码NSColorSpace
对象时的问题 - 修复了序列化NSDate对象时的精度丢失(现在使用-timeIntervalSinceReferenceDate而不是-timeIntervalSince1970)
- 修复了在macOS上构建的错误
版本3.2.2
- 修复了在Xcode 9中的警告
- 修复了写入包含nil属性的NSCoded存档时的损坏错误
- 将tvOS支持添加到Podspec
版本3.2.1
- 修复了32位处理器上Float64类型的可能对齐问题
版本3.2
- 修复了在32位设备上CFMutableDictionary中将@YES和@1意外地视为不同键的微妙错误
- 1和0的NSNumber值不再编码为布尔值(与上述修复相关)
- 修复了读取NSCoded对象时出现的泄漏
版本3.1.1
- 修复了编码类中包含枚举块时引发的崩溃
版本3.1
- 现在支持NSDecimalNumber(之前编码为NSNumber)
- 修复了NSCoding支持中的错误
- 修复了读取编码的NSDate对象时出现的对齐问题(日期很难,好吗?)
- 修复了在启用ARC时使用FastCoding导致的崩溃(但你仍然不应该使用ARC)
版本3.0.2
- 修复了由于对齐错误而编码NSDate对象时出现的错误
- 修复了设置类和字符串计数错误的错误(轻微的性能影响)
版本3.0.1
- 启用数据对齐以修复ARM 32设备上的崩溃
- 使用3.0版本创建的文件无法加载,如果需要,可以进行手动迁移过程(见上文)
版本3.0
- 全新的文件格式,与之前相比,既更小且编码/解码速度更快
- 添加了+propertyListWithData方法,用于安全地从不受信任的来源加载文件
- 现在内部捕获解析异常 - 如果发生错误,方法将返回nil
- 与2.x版本完全向后兼容
- 修复了浮点精度错误
- 添加了对CGVector类型的支持
版本2.3
- FastCoding现在包括对NSCoding的兼容性,因此任何支持NSCoding的类现在将通过调用NSCoding方法而不是使用FastCoding协议进行编码(这稍微慢一些,但更兼容)
- 修复了NSIndexSet解码中的错误
- 修复了FCClassDefinition中的轻微内存泄漏
- FastCoding 2.3完全向后兼容(可以读取任何版本2.x文件)。只要不包含NSCoded对象,FastCoding 2.3文件可以被2.2的实现读取
版本2.2
- 现在支持编码NSIndexSet和NSMutableIndexSet
- Mac基准测试不再依赖于硬编码的json文件路径
- FastCoding 2.2完全向后兼容(可以读取2.0或2.1版本的文件)。只要不包含任何新数据类型,FastCoding 2.2文件可以被2.0或2.1的实现读取
版本2.1.9
- 如果不在.pch文件中包含CoreGraphics,则现在将其导入
- 修复了最新Xcode中出现的某些新警告
版本2.1.8
- 修复了写入数据时出现的内存泄漏
版本2.1.7
- 修复了保存时发生的崩溃
版本2.1.6
- 修复了使用ARC时的一些转换警告
版本2.1.5
- 对象编码(解码速度不受影响)的速度有显著提升
- 修复了由空字符串或集合引起的一些潜在错误
- 修复了一些轻微的内存泄漏
版本2.1.4
- 修复了编码NSURL时出现的错误
版本2.1.3
- 修复了继承类中的属性不会编码的问题
- fastCodingKeys不再包括具有非标准ivar名称的属性(这使行为与文档保持一致)
- 修复了一些编译警告
版本2.1.2
- 修复了加载引导对象时出现的崩溃
- 添加了对自定义对象编码的基准测试
版本2.1.1
- 修复了同时具有多个到返回与-awakeAfterFastCoding不同的实例的对象的别名不会正常工作的错误。这个修复改进了常见情况,但仍然有一些需要注意的事项(有关-awakeAfterFastCoding方法的详细信息,请参阅文档)。
版本2.1
- 现在支持编码NSURL和NSValue
- 不可变数组、字典和集合在编码时不再转换为可变变体
- 新增 -classForFastCoding 方法(以避免与 NSCoding 冲突)
- 修复了在 $class 字典不包含该类每个属性值时的对象引导错误
- FastCoding 2.1 完全向后兼容(可读取版本 2.0 文件)。只要不包含任何新的数据类型,FastCoding 2.1 文件可以被 2.0 实现读取
版本 2.0.1
- 修复了 NSDate 序列化错误
版本 2.0
- FastCoding 2.0 与版本 1.0 文件既不向前也不向后兼容
- 新增了自动编码任何对象类型的能力
- NSString 和 NSData 对象的可变性现在被保留
- 新增了从 plist/json 创建原生对象的引导机制
- 提高了解码性能
版本 1.1
- 如果遇到意外的 EOF,则抛出异常以改进安全性
- 当使用 ARC 时性能有所提高(但请不要使用 ARC)
- 重构为使用多态风格进行序列化(代码更清洁)
- 重构为使用函数指针数组进行解析(略有提高速度)
- 现在符合 -Weverything 警告级别
- 添加了 Cocoapods podspec 文件
版本 1.0.1
- 修复了别名逻辑中的错误
版本 1.0
- 首次发布