什么是Protocol Buffer,這里就不多做介紹了驰吓,有興趣自行百度即可。
什么時(shí)候使用
PB相比JSON和XML雖然有流量和轉(zhuǎn)換效率方面的優(yōu)勢(shì),但使用起來(lái)并沒有JSON和XML方便辆布,或者說(shuō),對(duì)于如何具體在實(shí)際項(xiàng)目中替換JSON和XML茶鉴,相關(guān)文章較少(基本上都是到生成.pb.h和.pb.m文件就結(jié)束了...)锋玲。
環(huán)境安裝
1.安裝Homebrew(相信使用過pod的老司機(jī)都已經(jīng)安裝了,這里略過)
2.安裝protobuf編譯所需工具
brew install automake
brew install libtool
brew install protobuf
如果已有automake涵叮,但不是最新版本惭蹂,使用update進(jìn)行更新:
brew update automake
添加依賴庫(kù)
PB的git托管地址為https://github.com/google/protobuf
使用pod可以直接添加:
pod 'Protobuf'
使用PB創(chuàng)建Model
1.創(chuàng)建.proto文件
proto3語(yǔ)法可以參考Protocol Buffers 3.0 技術(shù)手冊(cè)這篇文章。
下面我們創(chuàng)建一個(gè)簡(jiǎn)單的DemoMessage.proto文件:
使用Xcode新建Empty
文件割粮,命名為DemoMessage.proto
盾碗,然后文件內(nèi)容如下:
syntax = "proto3";
message DemoMessage {
string query = 1;
int32 page_number = 2;
int32 result_per_page = 3;
}
2.生成對(duì)應(yīng)的iOS文件
在終端中,cd到DemoMessage.proto所在文件夾舀瓢,然后使用如下命令:
// OC
protoc DemoMessage.proto --objc_out="./"
// Swift
protoc DemoMessage.proto --swift_out="./"
就會(huì)在這個(gè)文件夾下生成對(duì)應(yīng)的.m和.h文件了廷雅,然后添加到項(xiàng)目中就可以使用了。需要注意的是,OC生成的文件是不支持ARC的榜轿,需要在Compile Sources中添加-fno-objc-arc
(這點(diǎn)讓我有些困擾幽歼,如果有誰(shuí)知道怎么能夠生成arc的代碼,留言請(qǐng)告訴我)谬盐。
3.使用方法
打開步驟2生成的文件甸私,可以看到它繼承于GPBMessage,并且生成了步驟1中定義的屬性飞傀,以及對(duì)應(yīng)的descriptor
方法皇型,你可以像正常的Model一樣使用它:
#import "DemoMessage.pbobjc.h"
//...
DemoMessage *message = [DemoMessage new];
message.query = @"query";
NSLog(@"%@",message.query);
然后打開GPBMessage,看看里面有那些方法供我們使用:
/**
類方法:
**/
/// 返回一個(gè)autoreleased的對(duì)象
+ (instancetype)message;
/// 通過解析提供的data數(shù)據(jù)創(chuàng)建一個(gè)新實(shí)例砸烦。如果發(fā)生錯(cuò)誤弃鸦,則該方法返回nil,并在errorPtr中返回錯(cuò)誤幢痘。
+ (instancetype)parseFromData:(NSData *)data error:(NSError **)errorPtr;
/// 同上唬格,extensionRegistry是用于查找擴(kuò)展的擴(kuò)展注冊(cè)表。
+ (instancetype)parseFromData:(NSData *)data
extensionRegistry:(nullable GPBExtensionRegistry *)extensionRegistry
error:(NSError **)errorPtr;
/// 根據(jù)傳入的GPBCodedInputStream數(shù)據(jù)新建一個(gè)實(shí)例颜说,其他同上
+ (instancetype)parseFromCodedInputStream:(GPBCodedInputStream *)input
extensionRegistry:
(nullable GPBExtensionRegistry *)extensionRegistry
error:(NSError **)errorPtr;
+ (instancetype)parseDelimitedFromCodedInputStream:(GPBCodedInputStream *)input
extensionRegistry:
(nullable GPBExtensionRegistry *)extensionRegistry
error:(NSError **)errorPtr;
/// 返回類的描述器
+ (GPBDescriptor *)descriptor;
/**
對(duì)象方法:
**/
/// 根據(jù)Data初始化
- (instancetype)initWithData:(NSData *)data
extensionRegistry:(nullable GPBExtensionRegistry *)extensionRegistry
error:(NSError **)errorPtr;
/// 根據(jù)GPBCodedInputStream初始化
- (instancetype)initWithCodedInputStream:(GPBCodedInputStream *)input
extensionRegistry:
(nullable GPBExtensionRegistry *)extensionRegistry
error:(NSError **)errorPtr;
/// 解析data數(shù)據(jù)并合并到當(dāng)前對(duì)象中
- (void)mergeFromData:(NSData *)data
extensionRegistry:(nullable GPBExtensionRegistry *)extensionRegistry;
/// 將相同類型的對(duì)象的字段合并到當(dāng)前對(duì)象
- (void)mergeFrom:(GPBMessage *)other;
/// 將當(dāng)前對(duì)象寫入GPBCodedOutputStream中
- (void)writeToCodedOutputStream:(GPBCodedOutputStream *)output;
/// 將當(dāng)前對(duì)象寫入NSOutputStream中购岗,并以制表符為分割
- (void)writeDelimitedToOutputStream:(NSOutputStream *)output;
/// 將當(dāng)前對(duì)象序列化為NSData
- (nullable NSData *)data;
/// 將當(dāng)前對(duì)象序列化為NSData,并以制表符為分割
- (NSData *)delimitedData;
/// 门粪,
/**
* 獲取序列化后對(duì)象的大小喊积, 注意并不是文件的大小。
* 下面這樣拼接頭文件是有誤的:
*
* size_t size = [aMsg serializedSize];
* NSMutableData *foo = [NSMutableData dataWithCapacity:size + sizeof(size)];
* [foo writeSize:size];
* [foo appendData:[aMsg data]];
*
* 應(yīng)該使用:
*
* NSData *data = [aMsg data];
* NSUInteger size = [aMsg length];
* NSMutableData *foo = [NSMutableData dataWithCapacity:size + sizeof(size)];
* [foo writeSize:size];
* [foo appendData:data];
*
* @return 二進(jìn)制表示中對(duì)象的大小玄妈。
**/
- (size_t)serializedSize;
/// 獲取對(duì)象的描述器(該方法的實(shí)現(xiàn)中設(shè)置各屬性的索引)
- (GPBDescriptor *)descriptor;
/// 獲取包含當(dāng)前對(duì)象中設(shè)置的索引描述符的數(shù)組乾吻。
- (NSArray *)extensionsCurrentlySet;
/// 檢查對(duì)象中是否存在與給定索引描述符匹配的索引集。
- (BOOL)hasExtension:(GPBExtensionDescriptor *)extension;
/// 獲取此對(duì)象的給定索引的值拟蜻。
- (id)getExtension:(GPBExtensionDescriptor *)extension;
/// 設(shè)置此對(duì)象的給定索引的值绎签。
- (void)setExtension:(GPBExtensionDescriptor *)extension
value:(nullable id)value;
/// 為對(duì)象添加索引
- (void)addExtension:(GPBExtensionDescriptor *)extension value:(id)value;
/// 將給定索引的值替換為此消息的擴(kuò)展的給定值。 這僅適用于重復(fù)的字段索引瞭郑。 如果該字段是重復(fù)的POD類型辜御,則該值應(yīng)該是NSNumber鸭你。
- (void)setExtension:(GPBExtensionDescriptor *)extension
index:(NSUInteger)index
value:(id)value;
/// 刪除對(duì)應(yīng)索引
- (void)clearExtension:(GPBExtensionDescriptor *)extension;
/// 將此對(duì)象的所有字段重置為默認(rèn)值屈张。
- (void)clear;
看完上面的方法,對(duì)其結(jié)構(gòu)就有了比較清楚的了解袱巨。
4.與服務(wù)端的通信
要使用ProtocolBuffer與服務(wù)器通信阁谆,可以使用正常的HTTP請(qǐng)求方式。
接收數(shù)據(jù)
服務(wù)端返回ProtocolBuffer序列化后的Data愉老,直接使用parseFromData:error:
方法轉(zhuǎn)換成對(duì)應(yīng)的對(duì)象即可(需要注意的是场绿,服務(wù)端與客戶端最好是使用同一個(gè)版本.proto文件生成的文件來(lái)進(jìn)行序列化與反序列化)
傳參
使用ProtocolBuffer與服務(wù)器的交互,有以下幾種方案:
- 直接使用正常的POST操作嫉入,向服務(wù)器傳參焰盗,這樣只是返回值是ProtocolBuffer璧尸,好處是可以直接使用原來(lái)的網(wǎng)絡(luò)請(qǐng)求框架,基本上不需要修改請(qǐng)求的代碼熬拒。
- 將對(duì)象轉(zhuǎn)成NSData爷光,然后使用直接將其作為
parameters
參數(shù),這種方案需要稍微修改請(qǐng)求的代碼,改動(dòng)不大澎粟,但能節(jié)省發(fā)送請(qǐng)求是的少許流量蛀序。
使用gRPC
gRPC是由Google主導(dǎo)開發(fā)的RPC框架,使用HTTP/2協(xié)議并用ProtoBuf作為序列化工具活烙。
如果是項(xiàng)目已決定用PB當(dāng)作序列化徐裸,那么使用gRPC提供的一整套方案將會(huì)是一個(gè)很方便的選擇。
具體的使用教程可以參考下面的文章:
gRPC初體驗(yàn)
gRPC 官方文檔中文版