為什么要用?
- 網(wǎng)絡(luò)層代碼直接按照定義好的proto 文件生成,簡(jiǎn)單方便
- 而從用戶角度來(lái)看,可以節(jié)省流量,網(wǎng)絡(luò)請(qǐng)求速度更快了
- 翁偉要求的
我們的期望
- 支持 swift,有 swift 實(shí)現(xiàn)
- 使用簡(jiǎn)單
- 方便維護(hù)
現(xiàn)實(shí)情況
- 只有 oc的 release 版本
- 需要?jiǎng)?chuàng)建 podspec 文件,通過(guò)這個(gè)文件來(lái)管理和生成 grpc 的客戶端文件以及安裝依賴庫(kù)
- 每次使用都需要?jiǎng)?chuàng)建 Service 實(shí)例
- 不支持 Int 類型,如果要使用需要添加一定的代碼進(jìn)行轉(zhuǎn)換
- 網(wǎng)絡(luò)請(qǐng)求的回調(diào)格式不符合我們里面的代碼風(fēng)格
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)
怎么辦?
鑒于上述情況,我們有了以下的方案
通過(guò) NS_REFINED_FOR_SWIFT 這個(gè)宏所標(biāo)記的函數(shù)、方法和變量在 Obj-C 代碼中可以正常使用,但當(dāng)它們橋接到 Swift 的時(shí)候,名稱前面就會(huì)加上“__”。通過(guò)在 Obj-C 代碼中使用這個(gè)宏,我們就可以在 Swift 代碼中使用相同的名稱來(lái)提供一個(gè)更便于使用的 API ,就像下面這樣:




但是,對(duì)于每一個(gè)生成的文件,極大的可能都會(huì)有一個(gè)這種轉(zhuǎn)換文件與之對(duì)應(yīng)。這會(huì)造成運(yùn)行時(shí)的體積增大,工程體積增大。
現(xiàn)在的方案swift-grpc
現(xiàn)有的資源:
grpc-swift: https://github.com/grpc/grpc-swift
swift-protobuf: https://github.com/apple/swift-protobuf
問(wèn)題:
- 沒(méi)有可用的 release 版本
- 有寫好的模板代碼,但是沒(méi)有生成工具
- 沒(méi)有集成方式的示例
- swift-protobuf生成的是 struct而不是 class
初始的想法
由于 grpc-objc 是確定可以使用的,那么是不是可以使用 swift 的代碼來(lái)完全替代生成的 oc代碼,初步看來(lái)好像是可行的:
- service的代碼按照oc的代碼的直接翻譯,message 直接使用 swift-protobuf 代替:

將上面的代碼翻譯成 swift 是一件很簡(jiǎn)單的事,這里就不說(shuō)了,值得注意的是這個(gè)地方,它需要傳入的是一個(gè) class類型, 而我們的 swift-protobuf只提供 struct, 這就尷尬了:
responseClass:[Template class]
接下來(lái)直接改 swift-protobuf 的編譯器,讓它生成 class 是不 是就可以了?經(jīng)過(guò)實(shí)踐的證明,它遠(yuǎn)遠(yuǎn)不是改一下編譯器那么簡(jiǎn)單的事情,在 swift-protobuf runtime library 中還需要我們提供一個(gè)對(duì) class 的序列化,看了下它們實(shí)現(xiàn),

完全不知道怎么寫這樣一個(gè)東西。到這里這個(gè)想法已經(jīng)進(jìn)行不下去了,那再換一種吧。
想法ing
既然有 grpc-swift,而且給出的有可運(yùn)行 example, 通過(guò)驗(yàn)證,這個(gè)代碼也是可行的(service是手動(dòng)寫的,messae部分使用 swift-protobuf 生成),可以從服務(wù)器請(qǐng)求和接收數(shù)據(jù),可以滿足我們工程中的需要。
有了上述的支持,我們現(xiàn)在只需要一個(gè) service 代碼 compiler 就可以了,做為一個(gè)沒(méi)有用過(guò) c++的怎么來(lái)改寫/編寫這樣一個(gè) compiler,就不在這里說(shuō)了,各種坑是肯定的了。
有了service 代碼 compiler之后,這個(gè)方案可以算是完成了一半了。
接下來(lái)就可以看看怎么集成到我們的工程中使用了,由于 grpc-swift 中是直接將需要的依賴庫(kù)拖到工程中使用的,但是這種方式并不是我們想要的,是不是可以使用 grpc-objc 的方式(pod install)來(lái)集成?
研究了 grpc-objc 的和 grpc-swift 之后,發(fā)現(xiàn)想要使用 grpc-swift需要 CgRPC(為了支持 swift對(duì)grpc-Core 的封裝和拓展),SgRPC(使用 swift進(jìn)行封裝)這兩個(gè)庫(kù)支持,踩了 N 多坑之后,終于將這兩個(gè)庫(kù)弄成了本地 pod repo。
接下就可以集成到我們的 ezbuy 工程里了,但是事情總是沒(méi)有那么順利,pod install 之后工程果斷編譯報(bào)錯(cuò),經(jīng)過(guò)查找最后發(fā)現(xiàn)是由于在 grpc-Core 里定義了一個(gè)和系統(tǒng)庫(kù)重名string.h 文件(他們?cè)?podspec 文件中說(shuō)是故意定義成這樣的),而第三方庫(kù)HappyDNS 中使用了#include "string.h" 這樣的一種方式(不是標(biāo)準(zhǔn)的方式)來(lái)引用系統(tǒng)庫(kù)中 string.h 文件,從而導(dǎo)致了報(bào)錯(cuò),至于錯(cuò)誤原因在這里就不說(shuō)了,修改成 #include <string.h> 這樣之后,編譯通過(guò)。
完成了這兩個(gè)庫(kù)的安裝后,終于可以進(jìn)入正題了。創(chuàng)建

我們工程的 podspec 文件來(lái)進(jìn)行集成使用了。由于前面的研究,我們生成代碼需要的是這三個(gè)工具:

由于這三個(gè)工具是經(jīng)過(guò)代碼修改生成的,所以我們必須修改 podspec 文件來(lái)指定使用這三個(gè)工具,由于沒(méi)有保存那個(gè)版本,所以就不展示了,有需要的可以聯(lián)系我。
大功告成!!2333333333
再測(cè)試一下,生成一個(gè)帶有引用文件,比如這個(gè)

pod install 之后果斷報(bào)錯(cuò)啊,

通過(guò)一番查找嘗試,原來(lái)還要指定搜索參數(shù)。然后就有了以下的shell腳本 grpc.sh(邊搜邊寫,求不吐槽)和修改后的 podspec:
grpc.sh
#! /bin/sh
#! /bin/bash
#定義需要搜索的目錄路徑,如果.proto 文件放置的位置有改變,需要更改這個(gè)地方的路徑
declare targetSearchPath=apidoc/ios_proto
if [[ ! -f "/usr/local/lib/libprotobuf.10.dylib" ]]; then
echo "請(qǐng)按照grpcInstall.md文件安裝 grpc & protobuf"
open grpcInstall.md
fi
if [[ ! -d "$targetSearchPath" ]]; then
echo "$targetSearchPath 不存在,請(qǐng)確認(rèn).proto 文件的放置路徑。"
fi
# 導(dǎo)入環(huán)境變量,以便 protoc 和 protoc-gen-swift 的使用
export PATH=$PATH:./grpc
#定義接收搜索參數(shù)的變量"-I xxxx -I xxxxx -I xxxxx"
declare protoPath=""
function getProtoSearchPath() {
fList=`ls $1`
for folder in $fList
do
temp=${1}"/"${folder}
# echo "當(dāng)前搜索的目錄是:$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" => "wangding@ezbuy.com" }
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
到這里已經(jīng)可以使用了,對(duì)于剩余的一些需要修改代碼以及 compiler 的問(wèn)題都是一此小問(wèn)題了。