iOS的 gRPC 之路

3,053 阅读6分钟

作者: 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 ,就像下面这样:

Paste_Image.png

Paste_Image.png

Paste_Image.png

Paste_Image.png

但是,对于每一个生成的文件,极大的可能都会有一个这种转换文件与之对应。这会造成运行时的体积增大,工程体积增大。

###现在的方案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 代替:

Paste_Image.png
将上面的代码翻译成 swift 是一件很简单的事,这里就不说了,值得注意的是这个地方,它需要传入的是一个 class类型, 而我们的 swift-protobuf只提供 struct, 这就尴尬了:

responseClass:[Template class]

接下来直接改 swift-protobuf 的编译器,让它生成 class 是不 是就可以了?经过实践的证明,它远远不是改一下编译器那么简单的事情,在 swift-protobuf runtime library 中还需要我们提供一个对 class 的序列化,看了下它们实现,

Paste_Image.png
完全不知道怎么写这样一个东西。到这里这个想法已经进行不下去了,那再换一种吧。

####想法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> 这样之后,编译通过。

完成了这两个库的安装后,终于可以进入正题了。创建

Paste_Image.png
我们工程的 podspec 文件来进行集成使用了。由于前面的研究,我们生成代码需要的是这三个工具:

Paste_Image.png

由于这三个工具是经过代码修改生成的,所以我们必须修改 podspec 文件来指定使用这三个工具,由于没有保存那个版本,所以就不展示了,有需要的可以联系我。

大功告成!!2333333333

再测试一下,生成一个带有引用文件,比如这个

Paste_Image.png

pod install 之后果断报错啊,

Paste_Image.png

通过一番查找尝试,原来还要指定搜索参数。然后就有了以下的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" => "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

到这里已经可以使用了,对于剩余的一些需要修改代码以及 compiler 的问题都是一此小问题了。