Protocol Buffer 入门使用: iOS

Protocol Buffer简介

Protocol Buffer是google于2008推出的一种数据交换的格式,它独立于语言,独立于平台。,google 提供了多种语言的实现,每一种实现都包含了相应语言的编译器以及库文件。由于它是一种二进制的格式,比使用 xml 和 json 进行数据交换快许多。可以把它用于分布式应用之间的数据通信或者异构环境下的数据交换。作为一种效率和兼容性都很优秀的二进制数据传输格式,可以用于诸如网络传输、配置文件、数据存储等诸多领域。现今经过多个版本迭代已经到了3.x版本
  其实说白了Protocol Buffer就是一个协议,和解释器。可以用proto文件把model或者其他数据序列化Or反序列化(把二进制数据解析为Model或者把Model转为二进制数据)。它添加了多语言的支持,在不同语言下,遵守同样的协议(使用同样的proto),这样就可以保证在不同端,数据可以交互,而且不用担心数据的序列化问题。
  下面是支持的语言:

Language Source
C++ (include C++ runtime and protoc) src
Java java
Python python
Objective-C objectivec
C# csharp
JavaNano javanano
JavaScript js
Ruby ruby
Go golang/protobuf
PHP php
Dart dart-lang/protobuf
为什么选择Protocol Buffer

(1)模式本身很不错
(2)无偿向后兼容
(3)更少的样本代码
(4)验证和可扩展性
(5)建议的语言互操作性
  详细可以参考这里选择PB的五个理由,写的很好。一门技术的选择需要多个条件,没有最好,只有最适合自己项目使用的 。所以请根据实际情况合理选择使用

前期环境准备

现在的release版本是3.3.0,安装需要用到软件包管理工具。这里推荐使用Homebrew,它是一个很好的软件包管理工具,首先安装HomeBrew直接在终端执行以下命令

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

接下来安装Protobuf Compiler,执行一下几个命令即可:
  Automake 是一种帮助『自动』产生 Makefile 文件的软件,并且让开发出来的的软件可以象 Apache,MySQL 和常见的 GNU 软件一样,程序设计者只需要写一些预先定义好的宏 (macro),提交给Automake处理后会产生一个可以供 Autoconf 使用的 Makefile.in文件。再配合利用 Autoconf产生的自动配置设置文件 configure 即可产生一份符合 GNU Makefile 惯例的 Makeifle 了.
  libtool是一种属于GNU建构系统的GNU程序设计工具,用来产生便携式的库

brew install automake 
brew install libtool 
brew install protobuf

接下来把github上的protobuf拉到本地,在 protobuf/objectivec/DevTools目录下有个shell脚本,full_mac_build.sh执行这个脚本

git clone https://github.com/google/protobuf.git
cd protobuf/objectivec/DevTools
./full_mac_build.sh

如果在执行脚本的过程中遇到以下错误,是由于没有安装libtool造成的。我装的时候遇到下面的错误,按照我现在这个步骤应该不会有错。

configure.ac:30: error: possibly undefined macro: AC_PROG_LIBTOOL
      If this token and others are legitimate, please use m4_pattern_allow.
      See the Autoconf documentation.
autoreconf: /usr/local/Cellar/autoconf/2.69/bin/autoconf failed with exit status: 1

接下来要在自己的项目中加入protobuf的库文件,这里推荐使用cocoapod来添加,在podfile文件中添加 pod 'Protobuf', '~> 3.3.0' 。当然你也可以直接把protobuf拖进自己的项目中使用。cocoapod的使用,相信大家都会,不会的话可以找google

proto的生成与使用

首先要创建.proto文件,文件名自起。后缀要用.proto,比如这里使用的 UserModel.proto。 syntax = "proto3",指定使用的proto版本,没有会报以下错误:
No syntax specified for the proto file: UserModel.proto. Please use 'syntax = "proto2";' or 'syntax = "proto3";' to specify a syntax version. (Defaulted to proto2 syntax.)

syntax = "proto3";
package first_pro;
message UserModel{
     string user_name = 1;
     string user_id = 2;
}

另外在proto3中移除了显式的optional,如果你是用了optional关键词像这样:optional string user_id = 2; 会报以下错误: 意思就是在proto3中移除了显式的optional关键字,不加默认就是optional

UserModel.proto:5:15: Explicit 'optional' labels are disallowed in the Proto3 syntax. To define 'optional' fields in Proto3, simply remove the 'optional' label, as fields are 'optional' by default.

cd到UserModel.proto所在的目录下之后执行一下命令,这里做一个解释
--plugin=/usr/local/bin/protoc-gen-objc 这一段是不能更改的,指的是使用这个目录下的protoc-gen-objc来编译,
*.proto 其中*号为通配符,意思匹配当前目录所有.proto后缀的文件,当然你也可以指定文件名
--objc_out="./" 这个是输出文件的目录,./意为当前目录,这个是可以自定义的,你可以指定自己的目录路径

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

之后会生成两个文件 UserModel.pbobjc.h 和UserModel.pbobjc.m 把这两个文件拖到项目中即可使用。comd+B,这时候你会发现报错了,这就尴尬了。查了下,原来这玩意用的是mrc,所以要在Build Phase -> Compile Sources 中在UserModel.pbobjc.m文件加上-fno-objc-arc,意思就不解释了,大家应该都懂
  下面是报错信息,意思是,arc禁止在struct中使用oc对象

protoc arc forbids object-c object in struct
//引用官方文档的说明
//Add objectivec/\*.h & objectivec/\*.m except for objectivec/GPBProtocolBuffers.m to your project.
//If the target is using ARC, remember to turn off ARC (-fno-objc-arc) for the .m files.
//The files generated by protoc for the *.proto files (\*.pbobjc.h and \*.pbobjc.m) are then also added to the target.

我把生成的两个文件也贴一下,大家可以看下(代码比较多,这个可以跳过不看)
.h

// Generated by the protocol buffer compiler.  DO NOT EDIT!
// source: UserModel.proto

// This CPP symbol can be defined to use imports that match up to the framework
// imports needed when using CocoaPods.
#if !defined(GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS)
 #define GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS 0
#endif

#if GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS
 #import <Protobuf/GPBProtocolBuffers.h>
#else
 #import "GPBProtocolBuffers.h"
#endif

#if GOOGLE_PROTOBUF_OBJC_VERSION < 30002
#error This file was generated by a newer version of protoc which is incompatible with your Protocol Buffer library sources.
#endif
#if 30002 < GOOGLE_PROTOBUF_OBJC_MIN_SUPPORTED_VERSION
#error This file was generated by an older version of protoc which is incompatible with your Protocol Buffer library sources.
#endif

// @@protoc_insertion_point(imports)

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"

CF_EXTERN_C_BEGIN

NS_ASSUME_NONNULL_BEGIN

#pragma mark - UserModelRoot

/**
 * Exposes the extension registry for this file.
 *
 * The base class provides:
 * @code
 *   + (GPBExtensionRegistry *)extensionRegistry;
 * @endcode
 * which is a @c GPBExtensionRegistry that includes all the extensions defined by
 * this file and all files that it depends on.
 **/
@interface UserModelRoot : GPBRootObject
@end

#pragma mark - UserModel

typedef GPB_ENUM(UserModel_FieldNumber) {
  UserModel_FieldNumber_UserName = 1,
  UserModel_FieldNumber_UserId = 2,
};

@interface UserModel : GPBMessage

@property(nonatomic, readwrite, copy, null_resettable) NSString *userName;

@property(nonatomic, readwrite, copy, null_resettable) NSString *userId;

@end

NS_ASSUME_NONNULL_END

CF_EXTERN_C_END

#pragma clang diagnostic pop

// @@protoc_insertion_point(global_scope)

.m

// Generated by the protocol buffer compiler.  DO NOT EDIT!
// source: UserModel.proto

// This CPP symbol can be defined to use imports that match up to the framework
// imports needed when using CocoaPods.
#if !defined(GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS)
 #define GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS 0
#endif

#if GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS
 #import <Protobuf/GPBProtocolBuffers_RuntimeSupport.h>
#else
 #import "GPBProtocolBuffers_RuntimeSupport.h"
#endif

 #import "UserModel.pbobjc.h"
// @@protoc_insertion_point(imports)

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"

#pragma mark - UserModelRoot

@implementation UserModelRoot

// No extensions in the file and no imports, so no need to generate
// +extensionRegistry.

@end

#pragma mark - UserModelRoot_FileDescriptor

static GPBFileDescriptor *UserModelRoot_FileDescriptor(void) {
  // This is called by +initialize so there is no need to worry
  // about thread safety of the singleton.
  static GPBFileDescriptor *descriptor = NULL;
  if (!descriptor) {
    GPB_DEBUG_CHECK_RUNTIME_VERSIONS();
    descriptor = [[GPBFileDescriptor alloc] initWithPackage:@"first_pro"
                                                     syntax:GPBFileSyntaxProto3];
  }
  return descriptor;
}

#pragma mark - UserModel

@implementation UserModel

@dynamic userName;
@dynamic userId;

typedef struct UserModel__storage_ {
  uint32_t _has_storage_[1];
  NSString *userName;
  NSString *userId;
} UserModel__storage_;

// This method is threadsafe because it is initially called
// in +initialize for each subclass.
+ (GPBDescriptor *)descriptor {
  static GPBDescriptor *descriptor = nil;
  if (!descriptor) {
    static GPBMessageFieldDescription fields[] = {
      {
        .name = "userName",
        .dataTypeSpecific.className = NULL,
        .number = UserModel_FieldNumber_UserName,
        .hasIndex = 0,
        .offset = (uint32_t)offsetof(UserModel__storage_, userName),
        .flags = GPBFieldOptional,
        .dataType = GPBDataTypeString,
      },
      {
        .name = "userId",
        .dataTypeSpecific.className = NULL,
        .number = UserModel_FieldNumber_UserId,
        .hasIndex = 1,
        .offset = (uint32_t)offsetof(UserModel__storage_, userId),
        .flags = GPBFieldOptional,
        .dataType = GPBDataTypeString,
      },
    };
    GPBDescriptor *localDescriptor =
        [GPBDescriptor allocDescriptorForClass:[UserModel class]
                                     rootClass:[UserModelRoot class]
                                          file:UserModelRoot_FileDescriptor()
                                        fields:fields
                                    fieldCount:(uint32_t)(sizeof(fields) / sizeof(GPBMessageFieldDescription))
                                   storageSize:sizeof(UserModel__storage_)
                                         flags:GPBDescriptorInitializationFlag_None];
    NSAssert(descriptor == nil, @"Startup recursed!");
    descriptor = localDescriptor;
  }
  return descriptor;
}

@end


#pragma clang diagnostic pop

// @@protoc_insertion_point(global_scope)

现在就可以在项目中使用了呢,这里举个简单的例子,记得引用头文件

    UserModel *model = [[UserModel alloc]init];
    model.userName = @"lee";
    model.userId = @"123456";
    
    NSData *data = model.data;
    
    UserModel *deModel = [[UserModel alloc]initWithData:data error:nil];

下面来个稍微复杂一点的.proto。嵌套型的proto,注意修饰词。使用基本同上。其实主要就是在proto文件的编写与定义

syntax = "proto3";
message Response{
    int32 result = 1;
    string result_message = 2;
    repeated Musicdata musicdata = 3;
}

message Musicdata{
    string music_name = 1;
    string mudic_url = 2;
    int64 music_id = 3;
}

具体的语法使用还有好多东西要学这里不在一一说明。贴上官方语法说明文档,这里有翻译好的,这个是2.x的,与3有些不太一样,但可以作为参考。
  欢迎批评指正。如需转载请注明出处,请尊重作者的劳动成果。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容