grpc-swift入门

先戴个头盔,以下所有论述不保证正确性,请自行甄别服用。

不想看前面的屁话,要直接上代码的,请跳到「iOS App端如何实现和RPC服务器通信」章节

什么是RPC、gRPC、grpc-swift

要搞清楚什么是grpc-swift

就要先搞清楚什么是gRPC

要搞清楚什么是gRPC,

就要先搞清楚什么是RPC

What is RPC

RPC,是Remote procedure call的简称,翻译过来——「远程过程调用」。

请讲人话?OK,举个🌰,假如我要转1个比特币给你(事实上我并没有1个比特币,不嫌弃波卡幣/Polkadot的话,可以转给你——最近跌好惨🫠),然后我就通过RPC这种「传输方式」转给你。

本质上这是一个传输数据的过程。所以RPC,就简单理解成「一种传输数据的方式」。

对比地看,我们还有另一种更常用的方式:HTTP+REST。(不知道啥玩意儿?不要紧。就理解成是互联网上另一种传输数据的方式就好了。)

简单来说,HTTP+REST方式,聚焦在数据data上:发送一个请求request,然后返回数据response

而RPC,聚焦在「方法」上——直接调用一个「方法/函数/command」——只是对比于在同一个软件内部调用方法,RPC中调用有点不太一样,它是从电脑A,直接调用电脑B中的某个「方法」,是一个远程调用(Remote Call)。

然后这个「方法」和我们常见的「方法」一样,会有参数、返回值。要传输的数据,就放在参数、返回值里面,最终实现数据的传输。如下图:


RPC的数据传输过程

截图出处: Comparing web API types: SOAP, REST, GraphQL and RPC

What is gRPC

OK,RPC是一种传输数据的方式,那gRPC又是什么?

聪明的你意识到了,这里多了一个「g」。不过,大多数人第一反应,应该不会认为「g」表示的是「Google」——毕竟百度是众多宇宙中第一好用的搜索引擎。

事实上「g」表示的,正是Google(起码大多数人是这样认为的。关于「g」的其他含义,下面再作补充),gRPC是Google主导的对RPC的具体实现。卖点:高性能、开源、通用(支持很多语言: Supported languages)。

其中,Protocol Buffers,有需要认识一下的。可以把它类比成XML、JSON,但是Protocol Buffers的数据包更小、速度更快、实现更简单。

你可能会猜到,RPC还有XML-RPCJSON-RPC这些其他的实现。而gRPC,更准确的对标,我觉得应该叫「Protocol Buffers-RPC」~

再回到「g」,事实上,把它理解成「Google」没有错,不过,经常没事找抽的工程师,对「g」是有另一番调侃的,详情: GRPC Core: g_stands_for

为了让大家有更直观的理解,下面把互联网数据传输的一部分发展史展现如下:


history

What is grpc-swift

OK,我们有gRPC了,是不是可以开始写iOS端的App,从「RPC后台」拿一些数据了?
上面提到,gRPC支持多种语言,其中就有Objective-C(如果暂时不理解「支持」的含义,后面会继续解释)。

但是,现在大家都用Swift开发iOS App,所以就有了grpc-swift了。

所以,总括来看,他们的关系如下图:
(对了,题外话:Bitcoin用的是JSON-RPC

RPC关系图

为什么要用gRPC

OK,上面讲了各种概念。那么,为什么要用gRPC呢?
(注意,我这里的问题是「为什么要用gRPC」,而不是「为什么要用RPC」)

天下武功,唯快不破

这是一条受用千年的古训。

gRPC用了上面提到过的Protocol Buffers,在数据传输过程,数据包/payload是基于二进制/binary的。
所以,数据包的size,比JSON小很多(想象一个例子:一个55bytes,一个20bytes)。
另外,二进制形式的数据包,CPU可以更高效地进行「序列化」和「反序列化」。

所以,概括来说,用gRPC的小伙伴,是想榨出更多的性能。

当然,gRPC也不是万金油,也有自己的劣势:浏览器支持有限、二进制格式对人类不友好等等。

更多的优劣分析,可以参考:
What is gRPC: Main Concepts, Pros and Cons, Use Cases

至于你要不要用gRPC,请自行斟酌——跟我好像没有关系。

iOS App端如何实现和RPC服务器通信

好了,上面讲了一大堆屁话,终于到正题了。

要写一个iOS的App,和gRPC后台通信。首先,我们要有一个gRPC后台——好一句废话。

服务端跑起来

没有后台经验的小伙伴不需要菊花一紧,你只需要在你的终端敲入swift run HelloWorldServer这行命令,然后再轻轻敲一下回车键,官方GitHub的HelloWorld后台,就会神奇般地跑起来了:

  • grpc-swift项目clon下来
  • cd到项目根目录
  • 打开终端/Termanil,执行swift run HelloWorldServer命令(成功后会看到终端的打印:server started on port 1234

这样,RPC后台就跑起来了。从这个后台能拿到什么数据?
首先这个后台有一个方法sayHello()可供(App)客户端调用,然后,假如你调用这个方法并传入Antony作为方法的参数(准确说应该是一个Rquest对象),他会返回字符串Hello Antony!(准确说应该是一个Response对象)。如果不传参数,默认返回Hello stranger!
有没有很厉害?!

如果你迫不及待,没写好App,就想调sayHello()方法试试看。可以:

  • 再打开一个终端
  • cd到项目根目录
  • 执行swift run HelloWorldClient命令(成功后会看到打印:Greeter received: Hello stranger!
    表示我们的客户端(是一个命令行工具)调用了sayHello()并收到了后台服务端的数据了!
    RPC后台跑起来!

.proto文件的撰写

在写App之前,还想介绍一下 .proto文件。

上面介绍了,我们客户端这边,调用了sayHello()方法,同样地,到时候我们的App,也会调用这个方法,获取数据,而这个方法自然是用Swift语言写的,我们需要自己写这个方法吗?答案是不需要。那这个方法从哪里来?

答案就是接下来介绍的 .proto文件。我们利用Protocol Buffers这个接口描述语言,来把我们的数据传输过程中的「数据模型」和「方法」在 .proto文件定义好,然后再通过相关指令,生成你的客户端需要的代码。比如iOS的Swift、Android的Kotlin等等。
(上面说过的「gRPC支持多种语言」,就是这个意思。)

下面是仓库中的helloworld.proto 文件

// Protocol Buffers有proto2版本,这里表明,我们用的是比较新的proto3版本
syntax = "proto3";

// 下面的option,是生成代码时候的一些配置
option java_multiple_files = true; // 生成的Java代码,是否分成多个文件
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW"; // 生成的Objective-C代码的前缀是什么

// 「包名」。想象一下,你在这里定义、最后生成的「类」和「方法」,有可能会和你原来App的「类」、「方法」重名。
// 这里加一个package的名称,避免「命名冲突」
package helloworld;

// 定义一个service
// 事实上你可以在同一个 .proto文件,定义多个serive(按我目前理解,这样做可以让不同功能的APIs,组织得更有条理?)
service Greeter {
  // SayHello接口方法的具体定义
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// 参数HelloRequest的定义
// 注意,这里的1,并不是给name赋值,而是标记tag,用于序列化和反序列化时的字段匹配
// 这里的message关键字,可以理解成和class类似
message HelloRequest {
  string name = 1;
}

// 返回值类型HelloReply的定义
message HelloReply {
  string message = 1;
}

// 如果有其他的数据模型和方法,继续添加就好。是不是挺简单的?

具体的语法介绍: Language Guide (proto3)

这里需要说明一下, .proto文件,理论上是负责后台的工程师去撰写的。
可能比较nice一点的同事,会顺便生成swift文件给你,你直接用就可以了。没那么nice的,可能会把 .proto文件丢给你,让你自己玩。

不过这里的最佳实践,我相信是前后端的工程师一起讨论 .proto文件中API接口的撰写,毕竟前后端开发有差异,很难避免写出一些不符合对方预期的API接口。

有兴趣的前端小伙伴,也可以试试往helloworld.proto 文件加点方法,改点内容,重新生成代码,更新实现。感受一下后台的开发。

接口代码的生成

OK,现在我们有 .proto文件了,假如我们碰到一位没那么nice的后台同事,把 .proto文件直接丢过来,要怎么生成Swift代码?

gRPC Swift 提供了一个插件/plugin,叫protoc(名字算是起得够烂了?让人很confusing)。详见: protoc gRPC Swift plugin
(如果没有安装这个插件而运行生成代码的指令,报错command not found: protoc

插件的安装,如果是macOS(应该没有人用Windows做iOS开发的?),直接在终端执行命令brew install swift-protobuf grpc-swift,用Homebrew来安装。
更详细的安装说明:Getting the protoc Plugins

(这里有个坑,一开始我搜到的是gRPC官网的安装教程Protocol Buffer Compiler Installation,这个不是针对Swift的,安装后生成代码的时候会提示protoc-gen-grpc-swift: program not found or is not executable

装好后,就可以用命令来生成Swift代码了。不过,先看看生成的代码文件长什么样:


Generated Swift Files

可以看到,两个文件(命名还有点奇怪):

  • .grpc.swift文件生成的是:API接口方法(对应上面的SayHello方法)、Client(App端用到)、Provider(实现后台时用到——后台工程师用)
  • .pb.swift文件生成的是:模型类(对应上面的HelloRequestHelloReply

接着,就可以敲命令行生成代码了,个人感觉命令行还是有点复杂,敲敲打打半天,才搞明白,所以画图说明一下(以这个目录下的helloworld.proto文件为例。先cd到仓库的根目录grpc-swift):

代码生成指令说明

执行上面命令后,如无意外,就会得到helloworld.grpc.swifthelloworld.pb.swift两个文件。

可参考: protoc gRPC Swift plugin——不过感觉还没我讲得清楚

App端请求数据

终于可以写App端的代码了!!!

新建一个iOS工程,获取gRPC Swift:可以用Swift Package Manager;可以手动导入;也可以用CocoaPods。详情可以看Github仓库的README

连接服务器,调用方法,获取数据

接着可以连接gRPC服务器了并获取数据了:

let group = PlatformSupport.makeEventLoopGroup(loopCount: 1)

// 创建一个channel
// 通过host和port,就知道连接那个服务器了
let channel = try? GRPCChannelPool.with(target: .host("localhost", port: 1234),
                                        transportSecurity: .plaintext,
                                        eventLoopGroup: group)

// 创建Client对象
// Helloworld_GreeterClient是根据.proto文件生成的代码
let greeter = Helloworld_GreeterClient(channel: channel!)

// 创建Request对象,作为方法的参数传给服务器
let request = Helloworld_HelloRequest.with {
    $0.name = "ANTONY"
}

// 传入参数,调用方法
let sayHello = greeter.sayHello(request)

do {
    // 拿到方法的返回值(后台返回的数据)
    let response = try sayHello.response.wait()
    print("Greeter received: \(response.message)")
} catch {
    print("Greeter failed: \(error)")
}

最后会看到Xcode控制台打印:Greeter received: Hello ANTONY!。这样就完成gRPC「客户端」和「服务器」之间的数据传输了。

Are you kidding me? 就这几行代码?你写了3000字?

OK,别着急,后面再写进阶一点的内容。

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

推荐阅读更多精彩内容