1 Plist 存储格式
Plist 文件有3种存储格式:
xmlJSON- 二进制
使用plutil -convert <fmt>命令可以在这3种格式之间相互转换。
<fmt>分别对应上面3种存储格式:
-
xml1 -
json -
binary1
同时如果输出到控制台,<fmt>还可以选择是按照OC还是Swift的方式进行输出:
bash
// 按照 OC 输出
MacBook-Pro:~ user$ plutil -convert objc -o - xxxx.plist
/// Generated from xxxx.plist
__attribute__((visibility("hidden")))
NSDictionary * const xxxx = @{
@"BuildVersion" : @"50",
@"CFBundleShortVersionString" : @"4.13",
@"CFBundleVersion" : @"3146.1.2",
@"ProjectName" : @"Notes",
@"SourceVersion" : @"3146001002000000",
};
// 按照 Swift 输出
MacBook-Pro:~ user$ plutil -convert swift -o - xxxx.plist
/// Generated from xxxx.plist
let xxxx = [
"BuildVersion" : "50",
"CFBundleShortVersionString" : "4.13",
"CFBundleVersion" : "3146.1.2",
"ProjectName" : "Notes",
"SourceVersion" : "3146001002000000",
]
上面命令中的-o -表示要输出到控制台。
2 Plist 二进制格式
Plist二进制格式描述在苹果CF代码库中的CFBinaryPList.c中。
Plist二进制格式整体如下:
从图上可以看到,Header和Trailer的大小是固定的。
Objects和Offset-Table部分是可变的。
下面给出了一个二进制Plist的内容,相关部分已经用不同颜色进行了区分:
将二进制Plist打印为xml输出到控制台的内容为:
bash
MacBook-Pro:~ user$ plutil -convert xml1 -o - xxxx.plist
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildVersion</key>
<string>50</string>
<key>CFBundleShortVersionString</key>
<string>4.13</string>
<key>CFBundleVersion</key>
<string>3146.1.2</string>
<key>ProjectName</key>
<string>Notes</string>
<key>SourceVersion</key>
<string>3146001002000000</string>
</dict>
</plist>
可以将xml内容和二进制内容对照着看。
2.1 Header
Header总共有8字节,定义如下:
c
// ForFoundationOnly.h
typedef struct {
uint8_t _magic[6];
uint8_t _version[2];
} CFBinaryPlistHeader;
2.1.1 magic
magic固定为plist。
2.1.2 version
plist常见的version是00。
同时还有15和16。
版本15和16都没有文档说明。
2.2 Trailer
Trailer固定位于二进制Plist的尾部,总共32字节。
Trailer的定义如下:
c
// ForFoundationOnly.h
typedef struct {
uint8_t _unused[5];
uint8_t _sortVersion;
uint8_t _offsetIntSize;
uint8_t _objectRefSize;
uint64_t _numObjects;
uint64_t _topObject;
uint64_t _offsetTableOffset;
} CFBinaryPlistTrailer;
2.2.1 Unused
Trailer开始的头5字节没有使用,始终是0。
2.2.2 SortVersion
SortVersion表是Plist中的Key是否排序,占用1字节。
0表示未排序。
2.2.3 OffsetIntSize
OffsetIntSize表示Offset-Table中每一项占用的字节数。
在上面例子中可以看到,OffsetIntSize为1,表示Offset-Table中每项只占用1字节。
2.2.4 ObjectRefSize
ObjectRefSize表示Dict和Array中引用的对象索引占用的字节数。
在上面例子中ObjectRefSize为1,表示Dict和Array中引用对象的索引占用1字节。
以Objects区域的第1个字节d5为例。
d5的高4bit代表数据类型,d代表Dict。
d5的低4bit在Dict场景代表key-value对的个数,也就是这个Dict有5个key-value对。
d5后面0x01-0x05每1个字节代表key在Offset-Table中的索引。
0x06-0x0a每1个字节代表value在Offset-Table中的索引。
如果ObjectRefSize为2,那么这些索引就会占用2个字节。
2.2.5 Num-Objects
Num-Objects代表二进制Plist中的对象个数。
在上面例子中,有一个Dict,这个Dict有5个key-value对,因此总共有11个对象。
2.2.6 Top-Object
Top-Object表示根对象偏移量,一般都为0。
在上面例子中,根对象就是plist节点。
2.2.7 Offset-Table Offset
Offset-Table Offset表示Offset-Table的偏移量。
在上面例子中,Offset-Table位于0x93处。
2.2.8 Offset-Table
Offset-Table中的每一项存储对象在Plist文件中的偏移量。
Offset-Table的项从0开始计数。
上面例子中Offset-Table的第0项为0x08。
Plist二进制偏移0x08处的值是d5。
d5代表了数据类型。
d5高4bit代表这是一个Dict。
d5低4bit代表这个Dict有5个key-value对。
2.2.9 Objects
Objects区域就是二进制Plist的数据区。
3 Plist 二进制中的数据类型
常见的Plist二进制中的数据类型如下:
0x00
0x00代表null。
0x08
0x08代表布尔值false。
0x09
0x09代表布尔值true。
0x1n
0x1n表示是int类型。
0x1n中的n可以取0~7,表示这个int占用2^n个字节,这些字节是大端在前排列。
比如0x11 0x20 0x30,那么n=1,表示这个int占用2个字节,为0x2030。
0x2n
0x2n表示是real类型。
0x2n中的n可以取值0~7,表示这个real类型占用2^n个字节,这些字节是大端在前排列。
0x5n
0x5n表示ASCII字符串。
0x5n的n取值0-f,表示这个ASCII字符串占用的字节数。
ASCII字符串的内容接在0x5n的后面。
但是如果n=f,那么后面的1个字节表示一个int类型,定义了这个ASCII字符串占用的字节数。
比如有一个字节序列0x5f 10 0f。
0x5f表示这是一个ASCII字符串。
10按照上面int的定义,它后面的2^0=1个字节定义了这个整数,也就是0xf。
那么,整个ASCII字符串就占用0xf个字节。
an
an表示是Array类型。
an的n取值0-f,表示这个Array中元素的个数。
Array中每个元素在Offset-Table中的索引直接接在an后面。
如果n=f,那么就和0x5n中的一样。
dn
dn表示是Dict类型。
dn的n取值0-f,表示这个Dict的key-value对个数。
Dict后面先跟key在Offset-Table中的索引,接着跟着value在Offset-Table中的索引。
如果n=f,那么就和0x5n中的一样
更多详细的类型参看CFBinaryPList.c。