Protobuf 的使用

三方库使用

Google Protocol Buffer(简称Protobuf)是由Google推出的一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或RPC数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式

Protobuf的安装

这里推荐使用homebrew安装,如果没有安装,先安装

安装protobuf

1
$brew install protobuf

安装Protobuf Compiler

1
2
3
brew install automake 
brew install libtool
brew install protobuf

Protobuf 使用

安装完成后我们就可以直接使用了!

注意:上面只是安装了编译环境,可以帮我们将.proto文件编译成我们需要的.h和.m环境

Protobuf 导入

下面我们就可以新建一个iOS的工程了,然后使用pod方式管理protobuf

1
pod 'Protobuf', '~> 3.4.0'

编写一个proto文件

这个proto文件就相当于一个类,不过要使用protobuf的语法来定义

可以简单的这么写:

1
2
3
4
5
6
syntax = "proto3";

message ChatMessage {
string title = 1;
string body = 2;
}

注意:
使用proto3的时候不需要在变量前面加上required和optional,默认就是optional
具体的语法规则我们后面介绍

编译这个proto文件

1
protoc --plugin=/usr/local/bin/protoc-gen-objc *.proto --objc_out="./*"

第一个号的地方是你.proto文件所在的位置,
第二个
所在的地方是你编译完成之后.h和.m要导出的地方

将生成的文件添加到项目中

直接添加之后,编译一下,肯定是会报错的(除非你还活在远古的MRC)。这时候你需要将编译出来的对象文件标记为MRC
Target - BuildPhases - Compile Sources

然后我们仔细阅读,生成的.h文件会发现中的这一段:

因为我们是使用pod管理的因此我们这里使用<>导入,因此在

Target - Build Setting - Preprocessor Macros

在Debug和Release中都添加

这样我们在编译一下,Done! 这就没什么问题了!

到目前为止,我们基本上已经完成了Protobuf的所有配置,下面开始使用Protobuf了。

简单的使用

先导入我们生成的.h文件

#import "ProtobufChatmessage.pbobjc.h"

简单的创建一个的对象–序列化成data然后在解析这个data。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ChatMessage *message = [[ChatMessage alloc] init];
message.title = @"Lee";
message.body = @"Hom";

NSData *data = [message data];


NSError *error = nil;
ChatMessage *msg = [ChatMessage parseFromData:data error:&error];
if (error) {
NSLog(@"parseData Error %@",error.localizedDescription);
} else {
NSLog(@"parseData Success");
NSLog(@"ChatMessage:title=%@ body=%@",msg.title,msg.body);
}

运行结果:

1
2
2018-03-31 10:31:00.854929+0800 ProtobufDemo[5901:690009] parseData Success
2018-03-31 10:31:00.855157+0800 ProtobufDemo[5901:690009] ChatMessage:title=Lee body=Hom

至此,我们已经成功的使用到了Protobuf,最直观的感觉就是直接从二进制的NSData转变成了我们想要的模型 Cool!!

Protocol还有很多高深的内容,网上也有很多关于他的文章,喜欢的可以多看看!

Protobuf 基本语法

字段格式定义

在Protobuf中,协议是由一系列的消息组成的。因此最重要的就是定义通信时使用到的消息格式。协议中个消息格式固定了t通信双方才能理解对象的码流。

限定修饰符① | 数据类型② | 字段名称③ | = | 字段编码值④ | [字段默认值⑤]

限定修饰符

限定修饰符 主要有required\optional\repeated(required在3.0的时候已经被废除,默认是optional)

  • Optional:表示是一个可选字段,可选对于发送方,在发送消息时,可以有选择性的设置或者不设置该字段的值。对于接收方,如果能够识别可选字段就进行相应的处理,如果无法识别,则忽略该字段,消息中的其它字段正常处理。因为optional字段的特性,很多接口在升级版本中都把后来添加的字段都统一的设置为optional字段,这样老的版本无需升级程序也可以正常的与新的软件进行通信,只不过新的字段无法识别而已,因为并不是每个节点都需要新的功能,因此可以做到按需升级和平滑过渡。

  • Repeated:表示该字段可以包含0~N个元素。其特性和optional一样,但是每一次可以包含多个值。可以看作是在传递一个数组的值

数据类型

Protobuf到C++的类型映射

proto Type C++ Type Notes
double double
float float
int32 int32 使用可变长编码方式。编码负数时不够高效——如果你的字段可能含有负数,那么请使用sint32。
int64 int64 使用可变长编码方式。编码负数时不够高效——如果你的字段可能含有负数,那么请使用sint64
uint32 uint32 Uses variable-length encoding
uint64 uint64 Uses variable-length encoding.
sint32 int32 使用可变长编码方式。有符号的整型值。编码时比通常的int32高效。
sint64 int64 使用可变长编码方式。有符号的整型值。编码时比通常的int64高效。
fixed32 uint32 总是4个字节。如果数值总是比总是比228大的话,这个类型会比uint32高效
fixed64 uint64 总是8个字节。如果数值总是比总是比256大的话,这个类型会比uint64高效。
sfixed32 int32 总是4个字节
sfixed64 int64 总是8个字节
bool bool
string string 一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。
bytes string 可能包含任意顺序的字节数据。

字段名称

protobuf建议字段的命名采用以下划线分割的驼峰式。例如 first_name 而不是firstName.

字段编码值

有了该值,通信双方才能互相识别对方的字段。当然相同的编码值,其限定修饰符和数据类型必须相同。

编码值的取值范围为 1~2^32(4294967296)。

其中 1~15的编码时间和空间效率都是最高的,编码值越大,其编码的时间和空间效率就越低(相对于1-15),当然一般情况下相邻的2个值编码效率的是相同的,除非2个值恰好实在4字节,12字节,20字节等的临界区。比如15和16.

1900~2000编码值为Google protobuf 系统内部保留值,建议不要在自己的项目中使用。

protobuf 还建议把经常要传递的值把其字段编码设置为1-15之间的值。

消息中的字段的编码值无需连续,只要是合法的,并且不能在同一个消息中有字段包含相同的编码值

关于import

Protobuf接口文件可以像C语言的h文件一个,分离为多个,在需要的时候通过import导入需要对文件。其行为和C语言的#include或者iOS中的的#import的行为大致相同。

关于package

避免名称冲突,可以给每个文件指定一个package名称,对于java解析为java中的包。对于C++则解析为名称空间。

关于message

支持嵌套消息,消息可以包含另一个消息作为其字段。也可以在消息内定义一个新的消息。

关于enum

枚举的定义和C++相同,但是有一些限制。枚举值必须大于等于0的整数。使用分号(;)分隔枚举变量而不是C++语言中的逗号(,)

优缺点

优点

1
2
1,数据压缩效果好,序列化反序列速度快
2,跨平台,生成一次proto文件,多端使用

缺点

1
2
3
1,可读性行差(在代码中)
2,最增加App包体积(生成的类本身就代码很多,而且需要使用第三方库)
3,用的人少(在项目交接时,还需要学习这方面的知识)

本文的Demo,放在这里

参考文章

iOS中使用Protocol Buffers

ProtocolBuffer在iOS中的使用