Xcode(Swift) + CocoaPodsでgRPCのClientを生成してServiceを呼び出すまでの流れ

# About

iOSアプリから[gRPC](http://www.grpc.io/)のServiceを呼び出す実験をしてみようと思ったのですが、CocoaPodsを利用した導入についての情報が意外とまとまっていなかったので、その流れをまとめておきます。

**Protocol Buffersの定義から、Clientライブラリが自動的に生成される**様子をお楽しみ下さい。

# 前提

* gRPC自体の説明は割愛します。[gRPC公式サイト](http://www.grpc.io/)などを参照して下さい。
* Xcode 7.3.1
* CocoaPods 1.0.1
* gRPC 1.0.0
* サンプルコード
    * iOSアプリ: https://github.com/hosopy/grpc-mobile-demo-ios
    * gRPCサーバ: https://github.com/hosopy/grpc-mobile-demo-server

プロジェクト名を`GrpcMobileDemo`とし、以下のような構成からスタートすることを想定します。

```sh
.
├── GrpcMobileDemo
├── GrpcMobileDemo.xcodeproj
├── GrpcMobileDemo.xcworkspace
├── Podfile
└── Pods
```

# 流れ

## 1. ローカルにgRPC ClientのPodを定義する

仕組みは後述するとして、gRPC Serviceに対するClientとして機能するPodを、ローカルに定義します。

* `RemoteClient`という名前でディレクトリを作成し、`podspec`とgRPCのサービスが定義された`proto`を配置します。
    * ここでは`RemoteClient`という名前にしましたが、名前は自由です。衝突しないお好きな名前をどうぞ。

```sh
.
├── GrpcMobileDemo
├── GrpcMobileDemo.xcodeproj
├── GrpcMobileDemo.xcworkspace
├── Podfile
├── Pods
└── RemoteClient
    ├── RemoteClient.podspec
    └── helloworld.proto
```

`RemoteClient.podspec`は次のような内容です。`authors`や`homepage`などは、ご自身の値を設定して下さい。

```ruby:RemoteClient/RemoteClient.podspec
Pod::Spec.new do |s|
  s.name     = 'RemoteClient'
  s.version  = '0.0.1'
  s.license  = 'MIT'
  s.authors  = { '<your user name>' => '<your email address>' }
  s.homepage = '<your homepage>'
  s.summary = 'gRPC test client'
  s.source = { :git => '<your git repository>' }

  s.ios.deployment_target = '7.1'
  s.osx.deployment_target = '10.9'

  # Run protoc with the Objective-C and gRPC plugins to generate protocol messages and gRPC clients.
  s.dependency "!ProtoCompiler-gRPCPlugin", "~> 1.0.0"

  # 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 = "#{pods_root}/!ProtoCompiler"
  protoc = "#{protoc_dir}/protoc"
  plugin = "#{pods_root}/!ProtoCompiler-gRPCPlugin/grpc_objective_c_plugin"

  s.prepare_command = <<-CMD
    #{protoc} \
        --plugin=protoc-gen-grpc=#{plugin} \
        --objc_out=. \
        --grpc_out=. \
        -I . \
        -I #{protoc_dir} \
        *.proto
  CMD

  s.subspec 'Messages' do |ms|
    ms.source_files = '*.pbobjc.{h,m}'
    ms.header_mappings_dir = '.'
    ms.requires_arc = false
    ms.dependency 'Protobuf'
    # This is needed by all pods that depend on Protobuf:
    ms.pod_target_xcconfig = {
      'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=1',
    }
  end

  s.subspec 'Services' do |ss|
    ss.source_files = '*.pbrpc.{h,m}'
    ss.header_mappings_dir = '.'
    ss.requires_arc = true
    ss.dependency 'gRPC-ProtoRPC'
    ss.dependency "#{s.name}/Messages"
  end
end
```

gRPCのサービスとして、[gRPCのサンプルにあるhelloworld.proto](https://github.com/grpc/grpc/blob/master/examples/protos/helloworld.proto)を写経したものを配置してみました。

```protobuf:RemoteClient/helloworld.proto
syntax = "proto3";

option java_multiple_files = true;
option java_package = "com.hosopy.grpcdemo.helloworld";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";

package helloworld;

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}
```

## 2. 定義したPodをPodfileで参照する

先ほど定義したローカルのPodを、`Podfile`で参照します。

```ruby:Podfile
target 'GrpcMobileDemo' do
  # Comment this line if you're not using Swift and don't want to use dynamic frameworks
  use_frameworks!

  # Pods for GrpcMobileDemo
  pod 'RemoteClient', path: './RemoteClient'

  target 'GrpcMobileDemoTests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'GrpcMobileDemoUITests' do
    inherit! :search_paths
    # Pods for testing
  end

end
```

## 3. pod install

あとは`pod install`するだけで完了です。

```sh
$ pod install
```

`RemoteClient/RemoteClient.podspec`で定義された`prepare_command`によって、`pod install`のプロセスで`protoc`が自動的に実行され、Objective-Cのファイルが自動生成されていることが分かります。

```sh
.
├── GrpcMobileDemo
├── GrpcMobileDemo.xcodeproj
├── GrpcMobileDemo.xcworkspace
├── Podfile
├── Pods
└── RemoteClient
    ├── Helloworld.pbobjc.h
    ├── Helloworld.pbobjc.m
    ├── Helloworld.pbrpc.h
    ├── Helloworld.pbrpc.m
    ├── RemoteClient.podspec
    └── helloworld.proto
```

Xcodeのプロジェクトでもファイルが確認できますね。

<img width="692" alt="xcode.png" src="https://qiita-image-store.s3.amazonaws.com/0/1489/4eae3d01-aadc-c425-9964-c61a00952cdf.png">

## 4. Serviceの呼び出し

以上で準備は完了なので、あとは生成されたClientを利用してServiceを呼び出すだけです。
以下は、`localhost:50051`に非SSLで接続してServiceを呼び出すサンプルです。

```swift:GrpcMobileDemo/HelloWorldViewController.swift
import Foundation
import UIKit
import RemoteClient

class HelloWorldViewController: UIViewController {
    ...

    @IBAction func requestButtonPressed(sender: UIButton) {
        // For debug
        // 非SSLでの接続には必要
        GRPCCall.useInsecureConnectionsForHost("localhost:50051")

        // Call RPC
        let request = HLWHelloRequest()
        request.name = "neko"

        let client = HLWGreeter(host: "localhost:50051")
        client.sayHelloWithRequest(request) { response, error in
            // response is HLWHelloReply
            if let response = response {
                self.labelStatus.text = response.message
            } else {
                print("Error \(error)")
            }
        }
    }
}
```

なお、実験に使用したgRPCのサーバは、[hosopy/grpc-mobile-demo-server](https://github.com/hosopy/grpc-mobile-demo-server)で公開していますが、Dockerですぐに起動できます。

```sh
$ docker run -i -t -p 50051:50051 hosopy/grpc-mobile-demo-server
```

# 参考

* https://github.com/grpc/grpc/tree/master/src/objective-c
* https://github.com/grpc/grpc/tree/master/src/objective-c/examples/SwiftSample