iOS的 gRPC 之路
作者: iOS 團隊 - 王玎
##為什麼要用?
- 網路層程式碼直接按照定義好的proto 檔案生成,簡單方便
- 而從使用者角度來看,可以節省流量,網路請求速度更快了
- 翁偉要求的
##我們的期望
- 支援 swift,有 swift 實現
- 使用簡單
- 方便維護
##現實情況
- 只有 oc的 release 版本
- 需要建立 podspec 檔案,通過這個檔案來管理和生成 grpc 的客戶端檔案以及安裝依賴庫
- 每次使用都需要建立 Service 例項
- 不支援 Int 型別,如果要使用需要新增一定的程式碼進行轉換
- 網路請求的回撥格式不符合我們裡面的程式碼風格
grpc的:[service unaryCallWithRequest:request handler:^(RMTSimpleResponse *response, NSError *error) { if (response) { NSLog(@"Finished successfully with response:\n%@", response); } else if (error) { NSLog(@"Finished with error: %@", error); } }];
我們的:public class func getHomePageShowArea(success: @escaping ([TRHomePageArea]) -> Void, failure: ((Error) -> Void)? = nil)
##怎麼辦? ###鑑於上述情況,我們有了以下的方案 通過 NS_REFINED_FOR_SWIFT 這個巨集所標記的函式、方法和變數在 Obj-C 程式碼中可以正常使用,但當它們橋接到 Swift 的時候,名稱前面就會加上“__”。通過在 Obj-C 程式碼中使用這個巨集,我們就可以在 Swift 程式碼中使用相同的名稱來提供一個更便於使用的 API ,就像下面這樣:




但是,對於每一個生成的檔案,極大的可能都會有一個這種轉換檔案與之對應。這會造成執行時的體積增大,工程體積增大。
###現在的方案swift-grpc
現有的資源: grpc-swift: github.com/grpc/grpc-s… swift-protobuf: github.com/apple/swift…
問題:
- 沒有可用的 release 版本
- 有寫好的模板程式碼,但是沒有生成工具
- 沒有整合方式的示例
- swift-protobuf生成的是 struct而不是 class
####初始的想法
由於 grpc-objc 是確定可以使用的,那麼是不是可以使用 swift 的程式碼來完全替代生成的 oc程式碼,初步看來好像是可行的:
- service的程式碼按照oc的程式碼的直接翻譯,message 直接使用 swift-protobuf 代替:

將上面的程式碼翻譯成 swift 是一件很簡單的事,這裡就不說了,值得注意的是這個地方,它需要傳入的是一個 class型別, 而我們的 swift-protobuf只提供 struct, 這就尷尬了:
responseClass:[Template class]
接下來直接改 swift-protobuf 的編譯器,讓它生成 class 是不 是就可以了?經過實踐的證明,它遠遠不是改一下編譯器那麼簡單的事情,在 swift-protobuf runtime library 中還需要我們提供一個對 class 的序列化,看了下它們實現,

完全不知道怎麼寫這樣一個東西。到這裡這個想法已經進行不下去了,那再換一種吧。
####想法ing
既然有 grpc-swift,而且給出的有可執行 example, 通過驗證,這個程式碼也是可行的(service是手動寫的,messae部分使用 swift-protobuf 生成),可以從伺服器請求和接收資料,可以滿足我們工程中的需要。
有了上述的支援,我們現在只需要一個 service 程式碼 compiler 就可以了,做為一個沒有用過 c++的怎麼來改寫/編寫這樣一個 compiler,就不在這裡說了,各種坑是肯定的了。
有了service 程式碼 compiler之後,這個方案可以算是完成了一半了。
接下來就可以看看怎麼整合到我們的工程中使用了,由於 grpc-swift 中是直接將需要的依賴庫拖到工程中使用的,但是這種方式並不是我們想要的,是不是可以使用 grpc-objc 的方式(pod install)來整合?
研究了 grpc-objc 的和 grpc-swift 之後,發現想要使用 grpc-swift需要 CgRPC(為了支援 swift對grpc-Core 的封裝和拓展),SgRPC(使用 swift進行封裝)這兩個庫支援,踩了 N 多坑之後,終於將這兩個庫弄成了本地 pod repo。
接下就可以整合到我們的 ezbuy 工程裡了,但是事情總是沒有那麼順利,pod install 之後工程果斷編譯報錯,經過查詢最後發現是由於在 grpc-Core 裡定義了一個和系統庫重名string.h 檔案(他們在 podspec 檔案中說是故意定義成這樣的),而第三方庫HappyDNS 中使用了#include "string.h" 這樣的一種方式(不是標準的方式)來引用系統庫中 string.h 檔案,從而導致了報錯,至於錯誤原因在這裡就不說了,修改成 #include <string.h> 這樣之後,編譯通過。
完成了這兩個庫的安裝後,終於可以進入正題了。建立

我們工程的 podspec 檔案來進行整合使用了。由於前面的研究,我們生成程式碼需要的是這三個工具:

由於這三個工具是經過程式碼修改生成的,所以我們必須修改 podspec 檔案來指定使用這三個工具,由於沒有儲存那個版本,所以就不展示了,有需要的可以聯絡我。
大功告成!!2333333333
再測試一下,生成一個帶有引用檔案,比如這個

pod install 之後果斷報錯啊,

通過一番查詢嘗試,原來還要指定搜尋引數。然後就有了以下的shell指令碼grpc.sh(邊搜邊寫,求不吐槽)和修改後的 podspec:
#####grpc.sh
#! /bin/sh #! /bin/bash #定義需要搜尋的目錄路徑,如果.proto 檔案放置的位置有改變,需要更改這個地方的路徑 declare targetSearchPath=apidoc/ios_proto if [[ ! -f "/usr/local/lib/libprotobuf.10.dylib" ]]; then echo "請按照grpcInstall.md檔案安裝 grpc & protobuf" open grpcInstall.md fi if [[ ! -d "$targetSearchPath" ]]; then echo "$targetSearchPath 不存在,請確認.proto 檔案的放置路徑。" fi # 匯入環境變數,以便 protoc 和 protoc-gen-swift 的使用 export PATH=$PATH:./grpc #定義接收搜尋引數的變數"-I xxxx -I xxxxx -I xxxxx" declare protoPath="" function getProtoSearchPath() { fList=`ls $1` for folder in $fList do temp=${1}"/"${folder} # echo "當前搜尋的目錄是:$temp" if [[ -d $temp ]]; then protoPath=${protoPath}" -I $temp" # echo "The directory is>> $protoPath" getProtoSearchPath $temp $protoPath fi done } getProtoSearchPath $targetSearchPath #Path where protoc and gRPC plugin protoc="grpc/protoc" plugin="grpc/grpc_swift_plugin" #name of pod spec name="ezbuyGRPC" pod_root=Pods # Directory where the generated files will be placed. generated_path=$pod_root"/"$name mkdir -p $generated_path protoPath=$protoPath" -I $targetSearchPath" p_command="${protoc} --plugin=protoc-gen-grpc=$plugin --swift_out=$generated_path --grpc_out=$generated_path $protoPath $targetSearchPath/*/*.proto" echo $p_command eval $p_command 複製程式碼
#####ezbuyGRPC.podspec
# #Be sure to run `pod spec lint ezbuyGRPC.podspec' to ensure this is a #valid spec and to remove all comments including this before submitting the spec. # #To learn more about Podspec attributes see http://docs.cocoapods.org/specification.html #To see working Podspecs in the CocoaPods repo see https://github.com/CocoaPods/Specs/ # Pod::Spec.new do |s| s.name= "ezbuyGRPC" s.version= "0.0.3" s.summary= "This is useful to install grpc easily." s.description= <<-DESC Use 'pod install' to generate proto files. When you change the proto file and want to use 'pod install', you should change the property of version in this file. DESC s.homepage= "http://www.grpc.io/" s.author= { "wangding" => "[email protected]" } s.ios.deployment_target = "8.0" s.osx.deployment_target = "10.9" s.source= { :path => "."} # Base directory where the .proto files are. # src = "apidoc/proto/*" # Pods directory corresponding to this app's Podfile, relative to the location of this podspec. pods_root = 'Pods' # Path where Cocoapods downloads protoc and the gRPC plugin. # protoc_dir = "." # protoc = "#{protoc_dir}/protoc" # plugin = "./grpc_swift_plugin" # swift_protoc_plugin = "protoc-gen-swift" # Directory where the generated files will be placed. dir = "#{pods_root}/#{s.name}" # s.prepare_command = <<-CMD #rm -f /usr/local/bin/#{swift_protoc_plugin} #cp #{swift_protoc_plugin} /usr/local/bin/ #mkdir -p #{dir} ##{protoc} \ #--plugin=protoc-gen-grpc=#{plugin} \ #--swift_out=#{dir} \ #--grpc_out=#{dir} \ #-I #{src} \ #-I #{protoc_dir} \ #-I #{src}/* \ ##{src}/*.proto # CMD s.prepare_command = <<-CMD chmod 777 grpc/grpc.sh ./grpc/grpc.sh CMD # Files generated by protoc s.subspec "Messages" do |ms| ms.source_files = "#{dir}/*.pb.swift" ms.header_mappings_dir = dir ms.requires_arc = true # The generated files depend on the protobuf runtime. The version is 0.9.24 ms.dependency "SwiftProtobuf" end # Files generated by the gRPC plugin s.subspec "Services" do |ss| ss.source_files = "#{dir}/*.pbrpc.swift" ss.header_mappings_dir = dir ss.requires_arc = true # The generated files depend on the gRPC runtime, and on the files generated by protoc. # ss.dependency "gRPC-ProtoRPC" ss.dependency "#{s.name}/Messages" ss.dependency "SwiftGRPC" ss.dependency "GRPCError" end # s.pod_target_xcconfig = { # # This is needed by all pods that depend on Protobuf: # 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1', # # This is needed by all pods that depend on gRPC-RxLibrary: # 'CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES' => 'YES', # } end 複製程式碼
到這裡已經可以使用了,對於剩餘的一些需要修改程式碼以及 compiler 的問題都是一此小問題了。