曾静的技术博客

但行好事,莫问前程.

嗨,我是曾静 (@devzeng),目前暂居深圳。


这是我用来记录平日学习笔记的地方,欢迎您的访问.

iOS中使用Protocol Buffers

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

Protocol buffers are a language-neutral, platform-neutral extensible mechanism for serializing structured data.

Protobuf和XML相比同是数据交换协议,不过Protobuf更小、更快、也更简单。可以通过定义自己的数据结构,然后使用Protobuf的代码生成器生成代码,用生成的代码来读写这个数据结构。Protobuf具有如下几个优点:

(1)“向后”兼容性好。不必担心因为消息结构的改变而造成的大规模的代码重构或者迁移的问题,因为添加新的消息中的字段并不会引起已经发布的程序的任何改变。

(2)语义更清晰。Protobuf使用.proto文件描述数据交换的格式,然后Protobuf编译器会将.proto文件编译生成对应的数据访问类以对Protobuf数据进行序列化、反序列化操作),无需解释器之类的东西。

(3)简单易学。使用Protobuf无需学习复杂的文档对象模型,它拥有良好的文档和示例,对于喜欢简单事物的人们而言,Protobuf比其他的技术更加有吸引力。

Protobuf开源并托管在Github,项目地址是:https://github.com/google/protobuf/。目前Google提供了三种语言的实现:java、c++ 和python,每一种实现都包含了相应语言的编译器以及库文件。

###Protocol协议格式说明

要通信,必须有协议,否则双方无法理解对方的码流。在Protobuf中,协议是由一系列的消息组成的。因此最重要的就是定义通信时使用到的消息格式。消息由至少一个字段组合而成,类似于C语言中的结构。每个字段都有一定的格式。字段格式:

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

#####①.限定修饰符包含required\optional\repeated

  • Required: 表示是一个必须字段,必须相对于发送方,在发送消息之前必须设置该字段的值,对于接收方,必须能够识别该字段的意思。发送之前没有设置required字段或者无法识别required字段都会引发编解码异常,导致消息被丢弃。

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

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

#####②.数据类型。

Protobuf定义了一套基本数据类型。几乎都可以映射到C++\Java\Objective-C等语言的基础数据类型。数据类型如下图所示:

protobuf_field_type.png

#####③.字段名称

字段名称的命名与C、C++、Java和Objective-C等语言的变量命名方式几乎是相同的。Protobuf建议字段的命名采用以下划线分割的驼峰式。例如first_name而不是firstName

#####④.字段编码值

有了该值,通信双方才能互相识别对方的字段。当然相同的编码值,其限定修饰符和数据类型必须相同。编码值的取值范围为 1~2^32(4294967296)。

其中 1~15的编码时间和空间效率都是最高的,编码值越大,其编码的时间和空间效率就越低(相对于1-15),当然一般情况下相邻的2个值编码效率的是相同的,除非2个值恰好实在4字节,12字节,20字节等的临界区。比如15和16。1900~2000编码值为Protobuf系统内部保留值,建议不要在自己的项目中使用。Protobuf还建议把经常要传递的值把其字段编码设置为1-15之间的值。消息中的字段的编码值无需连续,只要是合法的,并且不能在同一个消息中有字段包含相同的编码值。

建议:项目投入运营以后涉及到版本升级时的新增消息字段全部使用optional或者repeated,尽量不使用required。如果使用了required,需要全网统一升级,如果使用optional或者repeated可以平滑升级。

#####⑤.默认值。

当在传递数据时,对于required数据类型,如果用户没有设置值,则使用默认值传递到对端。当接受数据是,对于optional字段,如果没有接收到optional字段,则设置为默认值。

#####⑥.关于import

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

#####⑦.关于package

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

#####⑧.关于message

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

#####⑨.关于enum

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

###安装Protobuf Compiler

目前Protobuf稳定的版本是v2.6.1,最新的版本是v3.0.0-beta-3.1估计很快就会推出3.0的正式版本了。本文所有的操作都是基于2.6.1的版本。

####1.检查是否安装Homebrew:

brew -v

如果没有安装可以通过下面的方式进行安装:

ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

####2.安装Protobuf Compiler

brew install automake 
brew install libtool 
brew install protobuf

####3.创建Protobuf Compiler的符号链接(可选步骤)

ln -s /usr/local/Cellar/protobuf/2.6.1/bin/protoc /usr/local/bin

####4.安装Objective-C的扩展

git clone https://github.com/alexeyxo/protobuf-objc.git
cd protobuf-objc
./scripts/build.sh

###集成Protobuf Runtime

推荐使用CocoaPods的方式进行集成,在Podfile中加入下面的代码:

pod 'ProtocolBuffers', '~> 1.9.10'

###示例

####1.创建.proto文件

option java_package = "com.devzeng.statistics.proto";
package Statistics;

message AppContextMessage {
	required string net_connetion_type = 1;    //网络连接类型,可选值是wifi/wwan/unknown
	required string screen_resolution  = 2;    //屏幕分辨率
	optional string device_token		= 3;	//推送设备号
	optional string mac_address			= 4;	//mac地址
	optional string carrier_name 		= 5;	//运营商名称
	optional string device_model 		= 6;	//设备型号
	optional string wifi_name 			= 7;	//wifi名称
	optional string device_name 		= 8;	//设备名称
	required string device_type 		= 9;	//设备类型,可选值是iPhone/iPad/Android
	required string system_version 		= 10;	//系统版本
	required string device_uuid 		= 11;	//设备号
	optional string gps 				= 12;	//经纬度(latitude,longitude)
	required string app_version 		= 13;	//当前安装的APP版本号
}

一个比较好的习惯是认真对待proto文件的文件名。比如将命名规则定于如下: packageName.MessageName.proto

在上例中,package 名字叫做Statistics,定义了一个消息AppContextMessage。那么可以可以命名为Statistics.AppContextMessage.proto

####2.使用Protobuf编译器生成平台相关的代码

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

####3.数据的序列化

AppContextMessageBuilder *builder = [AppContextMessage builder];
[builder setNetConnetionType:@"wifi"];
[builder setScreenResolution:@""];
[builder setMacAddress:@""];
[builder setCarrierName:@"中国联通"];
[builder setDeviceModel:@""];
[builder setWifiName:@"my_wifi(8c:21:a:44:f0:c)"];
[builder setDeviceType:@"iPhone"];
[builder setSystemVersion:@""];
[builder setDeviceUuid:@""];
[builder setDeviceName:@""];
[builder setGps:@""];
[builder setAppVersion:@"1.0.1_20"];
NSData *data = [builder build].data;

###参考资料

1.《Protobuf’s Documentation》

2.《Google Protocol Buffer 的使用和原理》

3.《Protocol Buffer搭建及示例》

4.《Protocol Buffers for Objective-C》

最近的文章

iOS中KV数据库LevelDB的编译和使用

LevelDB是Google公司重量级工程师Jeff Dean和Sanjay Ghemawat发起的开源项目。LevelDB是一个持久化存储的KV系统,和Redis这种内存型的KV系统不同,LevelDB不会像Redis一样狂吃内存,而是将大部分数据存储到磁盘上。目前能够支持billion级别的数据量,在这个数量级别下还有着非常高的性能,主要归功于它的良好的设计。LevelDB开源并托管在GitHub上,项目的地址是:https://github.com/google/leveldb。有个...…

iOS继续阅读
更早的文章

在iOS项目中使用WebP格式图片

WebP是Google开发的一种旨在加快图片加载速度的图片格式。图片压缩体积大约只有JPEG的2/3,并能节省大量的服务器带宽资源和数据空间。Facebook Ebay等知名网站已经开始测试并使用WebP格式。下图是Google已经和正在部署的WebP的产品。与JPEG相同,WebP是一种有损压缩。Google表示这种格式的主要优势在于高效率。他们发现,“在质量相同的情况下,WebP格式图像的体积要比JPEG格式图像小40%,美中不足的是,WebP格式图像的编码时间“比JPEG格式图像长8...…

iOS继续阅读