SwiftでGRPCを動かす方法と基本的な挙動について

## はじめに
GRPCを扱おう事になって色々調べてもあまり文献がない。
そもそもGRPCの通信ができない、ヘッダーの設定方法や鍵認証通信方法など・・・とにかく色々ハマったのでこれから触る人のために一通りの事を書いておく。

## GRPCとは
Googleが提供するPRC。
リモートプロシージャコール。
早い話がネットワーク越しのコンピューターへのアクセスをまるで自分のコンピュータのメソッドを叩いているように通信を行うもの。
APIという言葉に置き換えても話は十分通じます。

## 選定

### grpc-swift
初めは[grpc-swift](https://github.com/grpc/grpc-swift)で構築しようとした。
構築も楽そうだし、初心者にも敷居が低いと思ったのだが・・・。
やめた。

その理由は鍵を使った認証が出来なかったからだ。(検証日:2019年3月頃)
コードは正しくて、証明書も正しいのにエラーが発生するという状況が1日続いたのでやめた。
ここでエラーが発生する時点で、他でもエラーが発生するかもしれないと思って断念。

その時には気がつかなかったけれど、今振り返ってみると`Proto`ファイルの管理も自前で行う必要がありそうだと思ったので断念。

### Google本家
本家の[Grpc](https://github.com/grpc/grpc/tree/master/src/objective-c)を敬遠したのは二つの理由がある。

1. Swift版がない
2. 構築が面倒臭そう

`grpc-swift`で構築する場合はあっという間だった。
鍵認証がなくてライトなプロジェクトなら間違いなく`grpc-swift`をオススメする。
これは難しそうだ・・・。

と感じる敷居の高さがあった。
ただ、構築が終われば管理は大変ではありません。

僕にとっての敷居が高かった理由は`Podspec`を作ったことがない為でした。

### 構築方法
構築するためには自前でPodを作る必要があった。
そのために使用するのが`podspec`。

これをプロジェクトに配備します。
公式ページにあるものを配備します。
基本的にはルートに配備した方が楽ですが、別に階層の中に配備しても大丈夫です。

ここではファイル名を`test.podspec`にしていますが、何でも良いです。
ただし、ファイル名は後でプロジェクト内の`import文`に記載するので`プロジェクト名+GRPC.podspec`などにしておくとわかりやすいと思います。

以下に使う時に困りそうな箇所をコメントで記載しておきます。
記載がない場所は変更の必要がない箇所 or 僕が把握してない箇所になります。

```ruby:test.podspec
Pod::Spec.new do |s|
  s.name     = '<Podspec file name>'
  s.version  = '0.0.1' # 後述しますが、protoファイルを変更するたびにこのヴァージョンを上げてください。
  s.license  = '...' # Apache License, Version 2.0とか社内のルールに従ってください。通常は社外に公開しないと思うので何でも良いです。
  s.authors  = { '<your name>' => '<your email>' } # 何でも良いです。 ex) 'gRPC' => 'hoge@gmail.com'
  s.homepage = '...' # 何でも良いです。 ex) https://hoge.com
  s.summary = '...' # 何でも良いです。 ex) 社名
  s.source = { :git => 'https://github.com/...' } # 社内のリポジトリを使わない場合はGoogleのGithubを指定します。 ex) https://github.com/grpc/grpc.git

  s.ios.deployment_target = '7.1'  # 自分のアプリバージョンに合わせてください。
  s.osx.deployment_target = '10.9'

  # Base directory where the .proto files are.
  src = '.' # 後述するprotoファイルの置き場所です。ルート直下におくことはないはずです。 ex) proto

  # We'll use protoc with the gRPC plugin.
  s.dependency '!ProtoCompiler-gRPCPlugin', '~> 1.0'

  # Pods directory corresponding to this app's Podfile, relative to the location of this podspec.
  pods_root = '<path to your Podfile>/Pods' # Podsファイルの場所。通常は`pods_root = 'Pods'`になるはず。

  # Path where Cocoapods downloads protoc and the gRPC plugin.
  protoc_dir = "#{pods_root}/!ProtoCompiler"
  protoc = "#{protoc_dir}/protoc"
  plugin = "#{pods_root}/!ProtoCompiler-gRPCPlugin/grpc_objective_c_plugin"

  # Directory where you want the generated files to be placed. This is an example.
  dir = "#{pods_root}/#{s.name}"

  # Run protoc with the Objective-C and gRPC plugins to generate protocol messages and gRPC clients.
  # You can run this command manually if you later change your protos and need to regenerate.
  # Alternatively, you can advance the version of this podspec and run `pod update`.
  # 階層化したい場合は以下のコードを変更する必要があります。(後述
  s.prepare_command = <<-CMD
    mkdir -p #{dir}
    #{protoc} \
        --plugin=protoc-gen-grpc=#{plugin} \
        --objc_out=#{dir} \
        --grpc_out=#{dir} \
        -I #{src} \
        -I #{protoc_dir} \
        #{src}/*.proto
  CMD

  # The --objc_out plugin generates a pair of .pbobjc.h/.pbobjc.m files for each .proto file.
  s.subspec 'Messages' do |ms|
    ms.source_files = "#{dir}/*.pbobjc.{h,m}"
    ms.header_mappings_dir = dir
    ms.requires_arc = false
    # The generated files depend on the protobuf runtime.
    ms.dependency 'Protobuf'
  end

  # The --objcgrpc_out plugin generates a pair of .pbrpc.h/.pbrpc.m files for each .proto file with
  # a service defined.
  s.subspec 'Services' do |ss|
    ss.source_files = "#{dir}/*.pbrpc.{h,m}"
    ss.header_mappings_dir = dir
    ss.requires_arc = true
    # The generated files depend on the gRPC runtime, and on the files generated by `--objc_out`.
    ss.dependency 'gRPC-ProtoRPC'
    ss.dependency "#{s.name}/Messages"
  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
```

これをプロジェクトに配置して`Podfile`に以下を記載すれば準備は完了です。

```ruby
# podspecのファイル名とパスを記載する事。
pod 'test', :path => '.'
```

あとは以下のコマンドを叩けば完成ですが、今はまだ叩かないでおきましょう。

```
$> pod install
```

## GRPCの仕組み
GRPCはどの環境からでもリモートの資産（サーバー）にアクセスできるようにすることが狙いです。
そのために`proto`という設計図のようなものを用意してあります。

例えば、クライアントが`Ruby`の場合は`proto`ファイルから`Ruby`のコードを自動的に作成してくれます。
今回は`Swift(Objective-c)`なので`proto`ファイルから`Swift(Objective-c)`のコードを作ってもらう必要があります。

その仕組みが上記で作った`podfile`になります。

上記の例では`proto`フォルダに`*.proto`ファイルを配置する事を想定していますので、お手元にある`*.proto`ファイルを`proto`フォルダに全てコピーしてください。

そして以下を実行しましょう。

```
$> pod install
```

### `.proto`ファイルはプロジェクトに入れる必要がある？
`.proto`ファイルは`Swift(Objective-c)`のプログラムができてしまえば不要です。
なので、XCodeのプロジェクトに含める必要はありません。

しかし、gitなどのバージョン管理に含めておかないと複数人で開発する時には`podコマンド`で失敗してしまいます。
なので、バージョン管理には含めておくと良いでしょう。

### 階層化について
もし`proto`ファイルを階層化構造で管理したい場合は上記の`podspec`を一部変更する必要があります。
以下に例を記載しておきます。

```ruby
  s.prepare_command = <<-CMD
    mkdir -p #{dir}
    #{protoc} \
        --plugin=protoc-gen-grpc=#{plugin} \
        --objc_out=#{dir} \
        --grpc_out=#{dir} \
        -I #{src} \
        -I #{protoc_dir} \
        #{src}*.proto #{src}*/*.proto # ここを変更
        
    # この行を追加
    sed -i '' 's/\\(.import "\\).*\\/\\(.*"\\)/\\1\\2/' #{dir}/*/*.h #{dir}/*/*.m
  CMD
```

これは２階層にした場合の例です。
`sed -i`をしているのはpodによってできたobjective-cのファイルがコンパイル時に全てルート直下に移動するため`import`のパスがずれてしまうための施策です。
おまじないのようなものです。気になる人はその行をコメントアウトして実行してみてください。

## protoからコードの作成
以下のようなファイルを`proto/`配下に配置してしてください。

```proto:test.proto
syntax = "proto3";

package sample;
option objc_class_prefix = "GRPC";

service SampleService {
  rpc Test (SampleRequest) returns (SampleResponse);
}

message SampleRequest {
  string text = 1;
}

message SampleResponse {
  string result = 1;
}
```

その後`podspec`ファイルのバージョンを上げてください。

```ruby
s.version  = '0.0.2' # protoファイルの中身が変わるたびにバージョンを上げてください。
```

そして`pod install`を行なってください。

### 接頭区について
protoファイルの中に

```
option objc_class_prefix = "GRPC";
```

と書いています。
これを書いておくと`proto`からできた`Swift`のクラスを使う時に接頭区をつけてくれます。
今回は`GRPC`をつけているので、`Proto`から作成されたクラスには全て`GRPC`がつくためわかりやすくなります。
（クラス名が競合することもなくなると思います。）

## Swiftコードのサンプル
では実際にコードを実行します。
これらのコードは`pod install`を行なった時点で作られています。（実態はobjective-cですがSwift用のクラスも作られています。）
サーバーは建っているものとします。

```swift
import test // podspecのファイル名

class Test() {
  func run() {
    // サービスにアクセスするクラスを準備します。
    let client = GRPCSampleService(host: "test.server:443")
    // アクセスする際に使うクラスを生成します。
    let request = GRPCSampleRequest()
    request.text = "サーバーへ送りたい文字"
    // 実際にアクセスします。
    client.test(with: request) { (response, error) in
      // レスポンスが帰ってきます。エラーハンドリングなどは必要に応じて行なってください。
      if let response = response {
        print(response.result)
      }
    }
  }
}
```

これで一通りの挙動は完成です。
あとは簡単にトピックを紹介します。

### エラーハンドリング
書くまでもないことですが、困る人もいるかもしれないのでサンプルを書いておきます。

```swift
if let error = error as NSError? {
  let alert = UIAlertController(title: "通信エラー", message: "通信に失敗しました、\n(\(error.localizedDescription))", preferredStyle: .alert)
}
let defaultAction: UIAlertAction = UIAlertAction(title: "OK", style: .default) { (_) in
  // OKを押された場合の処理
}
alert.addAction(defaultAction)
viewController?.present(alert, animated: true)
```

### 鍵認証
鍵はGRPCの通信全てに行うことができます。
(リクエスト別に鍵を使う必要がなかったので、その辺は調査していません。）

```swift
// 鍵ファイルを読み込む（ca.crtファイルの場合)
if let certUrl = Bundle.main.url(forResource: "ca", withExtension: "crt") {
  let certificate = try String(contentsOf: certUrl, encoding: .utf8)
  try! GRPCCall.setTLSPEMRootCerts(certificate, forHost: ConnectionString)
}
```

### GRPCリクエストにヘッダーをつける。
この記事を書こうと思ったのは、この処理の文献がなかったから。
なのに気がつけば他の内容の方がはるかに多いという・・・。

まぁ、他ではまっている人の助けになれば良いな。

```swift
client.test(with: request) { (response, error) in
  // レスポンスが帰ってきます。エラーハンドリングなどは必要に応じて行なってください。
  if let response = response {
    print(response.result)
  }
}
```

上記ではこのように呼び出していたが、これを以下のように変更する。

```swift
let protoCall = client.rpcToTest(with: request) { (response, error) in
  // レスポンスが帰ってきます。エラーハンドリングなどは必要に応じて行なってください。
  if let response = response {
    print(response.result)
  }
}
// ここにヘッダーリクエストを登録。他にもレスポンスなどの登録もできる？(未確認だがメソッドはあった)
protoCall.requestHeaders.addEntries(from: ["paramName":"value"])
// 実行します。
protoCall.start()
```

`pod install`を行うと`rpcTo`が付いたメソッドも用意されていて、これを使うことで`GRPCCall`の部分へアクセスすることができるようになる。
これがわからなくて半日ほど`google`を調べたり`objective-c`のコードを追ったりしてしまった・・・。
（答えはすぐそこにあったのに気がつかなかった・・・）

## 最後に
自分がハマった事を文章にする事大切。
誰かの役に立ちますように（願望

※上記のコードは全て未検証です。間違ってたら適時修正お願いします。