先戴個頭盔,以下所有論述不保證正確性,請自行甄別服用。
不想看前面的屁話,要直接上代碼的,請?zhí)健竔OS App端如何實(shí)現(xiàn)和RPC服務(wù)器通信」章節(jié)
什么是RPC、gRPC、grpc-swift
要搞清楚什么是grpc-swift,
就要先搞清楚什么是gRPC,
要搞清楚什么是gRPC,
就要先搞清楚什么是RPC。
What is RPC
RPC,是Remote procedure call的簡稱,翻譯過來——「遠(yuǎn)程過程調(diào)用」。
請講人話?OK,舉個??,假如我要轉(zhuǎn)1個比特幣給你(事實(shí)上我并沒有1個比特幣,不嫌棄波卡幣/Polkadot的話,可以轉(zhuǎn)給你——最近跌好慘??),然后我就通過RPC這種「傳輸方式」轉(zhuǎn)給你。
本質(zhì)上這是一個傳輸數(shù)據(jù)的過程。所以RPC,就簡單理解成「一種傳輸數(shù)據(jù)的方式」。
對比地看,我們還有另一種更常用的方式:HTTP+REST。(不知道啥玩意兒?不要緊。就理解成是互聯(lián)網(wǎng)上另一種傳輸數(shù)據(jù)的方式就好了。)
簡單來說,HTTP+REST方式,聚焦在數(shù)據(jù)data上:發(fā)送一個請求request,然后返回數(shù)據(jù)response。
而RPC,聚焦在「方法」上——直接調(diào)用一個「方法/函數(shù)/command」——只是對比于在同一個軟件內(nèi)部調(diào)用方法,RPC中調(diào)用有點(diǎn)不太一樣,它是從電腦A,直接調(diào)用電腦B中的某個「方法」,是一個遠(yuǎn)程調(diào)用(Remote Call)。
然后這個「方法」和我們常見的「方法」一樣,會有參數(shù)、返回值。要傳輸?shù)臄?shù)據(jù),就放在參數(shù)、返回值里面,最終實(shí)現(xiàn)數(shù)據(jù)的傳輸。如下圖:

截圖出處: Comparing web API types: SOAP, REST, GraphQL and RPC
What is gRPC
OK,RPC是一種傳輸數(shù)據(jù)的方式,那gRPC又是什么?
聰明的你意識到了,這里多了一個「g」。不過,大多數(shù)人第一反應(yīng),應(yīng)該不會認(rèn)為「g」表示的是「Google」——畢竟百度是眾多宇宙中第一好用的搜索引擎。
事實(shí)上「g」表示的,正是Google(起碼大多數(shù)人是這樣認(rèn)為的。關(guān)于「g」的其他含義,下面再作補(bǔ)充),gRPC是Google主導(dǎo)的對RPC的具體實(shí)現(xiàn)。賣點(diǎn):高性能、開源、通用(支持很多語言: Supported languages)。
其中,Protocol Buffers,有需要認(rèn)識一下的??梢园阉惐瘸蒟ML、JSON,但是Protocol Buffers的數(shù)據(jù)包更小、速度更快、實(shí)現(xiàn)更簡單。
你可能會猜到,RPC還有XML-RPC,JSON-RPC這些其他的實(shí)現(xiàn)。而gRPC,更準(zhǔn)確的對標(biāo),我覺得應(yīng)該叫「Protocol Buffers-RPC」~
再回到「g」,事實(shí)上,把它理解成「Google」沒有錯,不過,經(jīng)常沒事找抽的工程師,對「g」是有另一番調(diào)侃的,詳情: GRPC Core: g_stands_for
為了讓大家有更直觀的理解,下面把互聯(lián)網(wǎng)數(shù)據(jù)傳輸?shù)囊徊糠职l(fā)展史展現(xiàn)如下:

What is grpc-swift
OK,我們有g(shù)RPC了,是不是可以開始寫iOS端的App,從「RPC后臺」拿一些數(shù)據(jù)了?
上面提到,gRPC支持多種語言,其中就有Objective-C(如果暫時不理解「支持」的含義,后面會繼續(xù)解釋)。
但是,現(xiàn)在大家都用Swift開發(fā)iOS App,所以就有了grpc-swift了。
所以,總括來看,他們的關(guān)系如下圖:
(對了,題外話:Bitcoin用的是JSON-RPC

為什么要用gRPC
OK,上面講了各種概念。那么,為什么要用gRPC呢?
(注意,我這里的問題是「為什么要用gRPC」,而不是「為什么要用RPC」)
天下武功,唯快不破
這是一條受用千年的古訓(xùn)。
gRPC用了上面提到過的Protocol Buffers,在數(shù)據(jù)傳輸過程,數(shù)據(jù)包/payload是基于二進(jìn)制/binary的。
所以,數(shù)據(jù)包的size,比JSON小很多(想象一個例子:一個55bytes,一個20bytes)。
另外,二進(jìn)制形式的數(shù)據(jù)包,CPU可以更高效地進(jìn)行「序列化」和「反序列化」。
所以,概括來說,用gRPC的小伙伴,是想榨出更多的性能。
當(dāng)然,gRPC也不是萬金油,也有自己的劣勢:瀏覽器支持有限、二進(jìn)制格式對人類不友好等等。
更多的優(yōu)劣分析,可以參考:
What is gRPC: Main Concepts, Pros and Cons, Use Cases
至于你要不要用gRPC,請自行斟酌——跟我好像沒有關(guān)系。
iOS App端如何實(shí)現(xiàn)和RPC服務(wù)器通信
好了,上面講了一大堆屁話,終于到正題了。
要寫一個iOS的App,和gRPC后臺通信。首先,我們要有一個gRPC后臺——好一句廢話。
服務(wù)端跑起來
沒有后臺經(jīng)驗(yàn)的小伙伴不需要菊花一緊,你只需要在你的終端敲入swift run HelloWorldServer這行命令,然后再輕輕敲一下回車鍵,官方GitHub的HelloWorld后臺,就會神奇般地跑起來了:
- 把grpc-swift項(xiàng)目clon下來
- cd到項(xiàng)目根目錄
- 打開終端/
Termanil,執(zhí)行swift run HelloWorldServer命令(成功后會看到終端的打印:server started on port 1234)
這樣,RPC后臺就跑起來了。從這個后臺能拿到什么數(shù)據(jù)?
首先這個后臺有一個方法sayHello()可供(App)客戶端調(diào)用,然后,假如你調(diào)用這個方法并傳入Antony作為方法的參數(shù)(準(zhǔn)確說應(yīng)該是一個Rquest對象),他會返回字符串Hello Antony!(準(zhǔn)確說應(yīng)該是一個Response對象)。如果不傳參數(shù),默認(rèn)返回Hello stranger!。
有沒有很厲害?!
如果你迫不及待,沒寫好App,就想調(diào)sayHello()方法試試看??梢裕?/p>
- 再打開一個終端
- cd到項(xiàng)目根目錄
- 執(zhí)行
swift run HelloWorldClient命令(成功后會看到打?。?code>Greeter received: Hello stranger!)
表示我們的客戶端(是一個命令行工具)調(diào)用了sayHello()并收到了后臺服務(wù)端的數(shù)據(jù)了!
RPC后臺跑起來!
.proto文件的撰寫
在寫App之前,還想介紹一下 .proto文件。
上面介紹了,我們客戶端這邊,調(diào)用了sayHello()方法,同樣地,到時候我們的App,也會調(diào)用這個方法,獲取數(shù)據(jù),而這個方法自然是用Swift語言寫的,我們需要自己寫這個方法嗎?答案是不需要。那這個方法從哪里來?
答案就是接下來介紹的 .proto文件。我們利用Protocol Buffers這個接口描述語言,來把我們的數(shù)據(jù)傳輸過程中的「數(shù)據(jù)模型」和「方法」在 .proto文件定義好,然后再通過相關(guān)指令,生成你的客戶端需要的代碼。比如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
// 事實(shí)上你可以在同一個 .proto文件,定義多個serive(按我目前理解,這樣做可以讓不同功能的APIs,組織得更有條理?)
service Greeter {
// SayHello接口方法的具體定義
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// 參數(shù)HelloRequest的定義
// 注意,這里的1,并不是給name賦值,而是標(biāo)記tag,用于序列化和反序列化時的字段匹配
// 這里的message關(guān)鍵字,可以理解成和class類似
message HelloRequest {
string name = 1;
}
// 返回值類型HelloReply的定義
message HelloReply {
string message = 1;
}
// 如果有其他的數(shù)據(jù)模型和方法,繼續(xù)添加就好。是不是挺簡單的?
具體的語法介紹: Language Guide (proto3)
這里需要說明一下, .proto文件,理論上是負(fù)責(zé)后臺的工程師去撰寫的。
可能比較nice一點(diǎn)的同事,會順便生成swift文件給你,你直接用就可以了。沒那么nice的,可能會把 .proto文件丟給你,讓你自己玩。
不過這里的最佳實(shí)踐,我相信是前后端的工程師一起討論 .proto文件中API接口的撰寫,畢竟前后端開發(fā)有差異,很難避免寫出一些不符合對方預(yù)期的API接口。
有興趣的前端小伙伴,也可以試試往helloworld.proto 文件加點(diǎn)方法,改點(diǎn)內(nèi)容,重新生成代碼,更新實(shí)現(xiàn)。感受一下后臺的開發(fā)。
接口代碼的生成
OK,現(xiàn)在我們有 .proto文件了,假如我們碰到一位沒那么nice的后臺同事,把 .proto文件直接丟過來,要怎么生成Swift代碼?
gRPC Swift 提供了一個插件/plugin,叫protoc(名字算是起得夠爛了?讓人很confusing)。詳見: protoc gRPC Swift plugin
(如果沒有安裝這個插件而運(yùn)行生成代碼的指令,報錯command not found: protoc)
插件的安裝,如果是macOS(應(yīng)該沒有人用Windows做iOS開發(fā)的?),直接在終端執(zhí)行命令brew install swift-protobuf grpc-swift,用Homebrew來安裝。
更詳細(xì)的安裝說明:Getting the protoc Plugins
(這里有個坑,一開始我搜到的是gRPC官網(wǎng)的安裝教程Protocol Buffer Compiler Installation,這個不是針對Swift的,安裝后生成代碼的時候會提示protoc-gen-grpc-swift: program not found or is not executable)
裝好后,就可以用命令來生成Swift代碼了。不過,先看看生成的代碼文件長什么樣:

可以看到,兩個文件(命名還有點(diǎn)奇怪):
-
.grpc.swift文件生成的是:API接口方法(對應(yīng)上面的SayHello方法)、Client(App端用到)、Provider(實(shí)現(xiàn)后臺時用到——后臺工程師用) -
.pb.swift文件生成的是:模型類(對應(yīng)上面的HelloRequest,HelloReply)
接著,就可以敲命令行生成代碼了,個人感覺命令行還是有點(diǎn)復(fù)雜,敲敲打打半天,才搞明白,所以畫圖說明一下(以這個目錄下的helloworld.proto文件為例。先cd到倉庫的根目錄grpc-swift):

執(zhí)行上面命令后,如無意外,就會得到helloworld.grpc.swift和helloworld.pb.swift兩個文件。
可參考: protoc gRPC Swift plugin——不過感覺還沒我講得清楚
App端請求數(shù)據(jù)
終于可以寫App端的代碼了!?。?/p>
新建一個iOS工程,獲取gRPC Swift:可以用Swift Package Manager;可以手動導(dǎo)入;也可以用CocoaPods。詳情可以看Github倉庫的README。
連接服務(wù)器,調(diào)用方法,獲取數(shù)據(jù)
接著可以連接gRPC服務(wù)器了并獲取數(shù)據(jù)了:
let group = PlatformSupport.makeEventLoopGroup(loopCount: 1)
// 創(chuàng)建一個channel
// 通過host和port,就知道連接那個服務(wù)器了
let channel = try? GRPCChannelPool.with(target: .host("localhost", port: 1234),
transportSecurity: .plaintext,
eventLoopGroup: group)
// 創(chuàng)建Client對象
// Helloworld_GreeterClient是根據(jù).proto文件生成的代碼
let greeter = Helloworld_GreeterClient(channel: channel!)
// 創(chuàng)建Request對象,作為方法的參數(shù)傳給服務(wù)器
let request = Helloworld_HelloRequest.with {
$0.name = "ANTONY"
}
// 傳入?yún)?shù),調(diào)用方法
let sayHello = greeter.sayHello(request)
do {
// 拿到方法的返回值(后臺返回的數(shù)據(jù))
let response = try sayHello.response.wait()
print("Greeter received: \(response.message)")
} catch {
print("Greeter failed: \(error)")
}
最后會看到Xcode控制臺打?。?code>Greeter received: Hello ANTONY!。這樣就完成gRPC「客戶端」和「服務(wù)器」之間的數(shù)據(jù)傳輸了。
Are you kidding me? 就這幾行代碼?你寫了3000字?
OK,別著急,后面再寫進(jìn)階一點(diǎn)的內(nèi)容。
