南山忆的技术博客

回忆是抓不住的月光,握紧就变黑暗

0%

ProtocolBuffer 入门使用: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直接在终端执行以下命令

1
/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程序设计工具,用来产生便携式的库

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

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

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

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

1
2
3
4
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.)

1
2
3
4
5
6
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

1
2
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=”./“ 这个是输出文件的目录,./意为当前目录,这个是可以自定义的,你可以指定自己的目录路径

1
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对象

1
2
3
4
5
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文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// 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文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// 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)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    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文件的编写与定义
```objectivec
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有些不太一样,但可以作为参考。