iOS: gRPC(Protocol Buffers) + ReactiveSwiftのサンプル

# サンプルの説明
- .protoファイルをcocoapodsを利用してビルドする方法です。(grpc-swiftではありません。)
- 生成されたObjective-Cのコードを、Swiftから利用します。
- gRPCでAPIにアクセスする処理をReactiveSwiftのSignalProducer, Signalを返すようにラップしています。

# サンプルコードのリポジトリ
https://github.com/yusuke-imagawa/iOS_gRPC_ReactiveSwift_sample
.protoファイル, gRPC, ReactiveSwiftの連携部分だけを実装しています。
UIは実装していません。

# 使い方
- pod installを実行。

# cocoapodsで.protoファイルから、コードを生成するための設定

## .protoファイル
[user.proto](https://github.com/yusuke-imagawa/iOS_gRPC_ReactiveSwift_sample/blob/master/RemoteClient/proto/user.proto)
[report.proto](https://github.com/yusuke-imagawa/iOS_gRPC_ReactiveSwift_sample/blob/master/RemoteClient/proto/report.proto)
[push_notify.proto](https://github.com/yusuke-imagawa/iOS_gRPC_ReactiveSwift_sample/blob/master/RemoteClient/proto/push_notify.proto)
[commons.proto](https://github.com/yusuke-imagawa/iOS_gRPC_ReactiveSwift_sample/blob/master/RemoteClient/proto/commons.proto)
[chat.proto](https://github.com/yusuke-imagawa/iOS_gRPC_ReactiveSwift_sample/blob/master/RemoteClient/proto/chat.proto)
[calling.proto](https://github.com/yusuke-imagawa/iOS_gRPC_ReactiveSwift_sample/blob/master/RemoteClient/proto/calling.proto)
[block.proto](https://github.com/yusuke-imagawa/iOS_gRPC_ReactiveSwift_sample/blob/master/RemoteClient/proto/block.proto)
[account.proto](https://github.com/yusuke-imagawa/iOS_gRPC_ReactiveSwift_sample/blob/master/RemoteClient/proto/account.proto)

## [Podfile](https://github.com/yusuke-imagawa/iOS_gRPC_ReactiveSwift_sample/blob/master/Podfile)

```
# Uncomment the next line to define a global platform for your project
platform :ios, '13.2'

target 'TalkingSns' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  # Pods for Talking
  pod 'RealmSwift', '4.4.0'
  
  # GRPC_Client
  pod 'RemoteClient', path: './RemoteClient'
  
  pod 'ReactiveSwift', '~> 6.1'
  pod 'ReactiveCocoa', '~> 10.1'
  
  pod 'CocoaLumberjack/Swift'

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

  target 'TalkingSnsUITests' do
    # Pods for testing
  end

end
```

## [RemoteClient/RemoteClient.podspec](https://github.com/yusuke-imagawa/iOS_gRPC_ReactiveSwift_sample/blob/master/RemoteClient/RemoteClient.podspec)

```
Pod::Spec.new do |s|
  s.name     = "RemoteClient"
  # .protoファイルの更新時に、versionを変更して pod install する。
  s.version  = "0.0.21"
  s.license  = "New BSD"
  s.authors  = { 'imagawa' => 'test@example.com' }
  s.homepage = "http://example.com"
  s.summary = "grpc client"
  s.source = { :git => 'https://github.com/yusuke-imagawa/talking-ios.git' }

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

  # Base directory where the .proto files are.
  src = "./proto"

  # Run protoc with the Objective-C and gRPC plugins to generate protocol messages and gRPC clients.
  s.dependency "!ProtoCompiler-gRPCPlugin", "~> 1.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"

  # Directory where the generated files will be placed.
  # dir = "#{pods_root}/#{s.name}"

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

  # Files generated by protoc
  s.subspec "Messages" do |ms|
    ms.source_files = "src/*.pbobjc.{h,m}", "src/**/*.pbobjc.{h,m}"
    ms.header_mappings_dir = '.'
    ms.requires_arc = false
    # The generated files depend on the protobuf runtime.
    ms.dependency "Protobuf"
  end

  # Files generated by the gRPC plugin
  s.subspec "Services" do |ss|
    ss.source_files = "src/*.pbrpc.{h,m}", "src/**/*.pbrpc.{h,m}"
    ss.header_mappings_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"
  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
```

# 各APIのクライアント
[PushNotifyApiClient.swift](https://github.com/yusuke-imagawa/iOS_gRPC_ReactiveSwift_sample/blob/master/TalkingSns/Model/GrpcAPI/Client/PushNotifyApiClient.swift)
[AccountApiClient.swift](https://github.com/yusuke-imagawa/iOS_gRPC_ReactiveSwift_sample/blob/master/TalkingSns/Model/GrpcAPI/Client/AccountApiClient.swift)
[BlockApiClient.swift](https://github.com/yusuke-imagawa/iOS_gRPC_ReactiveSwift_sample/blob/master/TalkingSns/Model/GrpcAPI/Client/BlockApiClient.swift)
[CallingApiClient.swift](https://github.com/yusuke-imagawa/iOS_gRPC_ReactiveSwift_sample/blob/master/TalkingSns/Model/GrpcAPI/Client/CallingApiClient.swift)
[ChatApiClient.swift](https://github.com/yusuke-imagawa/iOS_gRPC_ReactiveSwift_sample/blob/master/TalkingSns/Model/GrpcAPI/Client/ChatApiClient.swift)
[ReportApiClient.swift](https://github.com/yusuke-imagawa/iOS_gRPC_ReactiveSwift_sample/blob/master/TalkingSns/Model/GrpcAPI/Client/ReportApiClient.swift)
[UsersApiService.swift](https://github.com/yusuke-imagawa/iOS_gRPC_ReactiveSwift_sample/blob/master/TalkingSns/Model/GrpcAPI/Client/UsersApiService.swift)

例：ReportApiClient

```
import RemoteClient
import ReactiveSwift

class ReportApiClient {
    
    static let shared = ReportApiClient()
    
    private init() {}

    private var service: ReportService? {
        return GrpcServiceLoader.shared.getReportService()
    }
    
    func reportUser(toUserId: Int) -> SignalProducer<Bool, Error> {

        return SignalProducer<Bool, Error> {
            (observer, lifetime) in
                
            let request = PostReportRequest()
            request.toUserId = Int64(toUserId)
            
            self.service?.rpcToPostReport(with: request) {
                (response: GPBEmpty?, error: Error?) in
                if let error = error {
                    observer.send(error: error)
                    return
                }
                observer.send(value: true)
                observer.sendCompleted()
            }.startWithHeaders()
        }
    }
}
```


# request時にユーザー認証用のheaderを設定
- この処理は必須ではないです。
- ユーザーの認証情報の設定箇所を共通化するため、独自に実装しています。

[GRPCProtoCall+extension.swift](https://github.com/yusuke-imagawa/iOS_gRPC_ReactiveSwift_sample/blob/master/TalkingSns/Common/Extension/GRPCProtoCall%2Bextension.swift)

```
extension GRPCProtoCall {

    func startWithHeaders() {
        if let userId = CurrentUserService.getCurrentUserId(),
            let apiToken = CurrentUserService.getApiToken() {
            requestHeaders.addEntries(from: ["user_id":String(userId)])
            requestHeaders.addEntries(from: ["api_token":apiToken])
        }
        start()
    }
}
```

request時に startWithHeaders() を呼び出す。
[BlockApiClient.swift](https://github.com/yusuke-imagawa/iOS_gRPC_ReactiveSwift_sample/blob/master/TalkingSns/Model/GrpcAPI/Client/BlockApiClient.swift)

```
    func block(toUserId: Int) -> SignalProducer<Bool, Error> {

        return SignalProducer<Bool, Error> {
            (observer, lifetime) in
            
            let request = BlockRequest()
            request.toUserId = Int64(toUserId)
            
            self.service?.rpcToBlock(with: request) { (response: GPBEmpty?, error: Error?) in
                if let error = error {
                    observer.send(error: error)
                    return
                }
                observer.send(value: true)
                observer.sendCompleted()
            }.startWithHeaders()
        }
    }
```

## 参考記事
- ・Xcode(Swift) + CocoaPodsでgRPCのClientを生成してServiceを呼び出すまでの流れ
    - https://qiita.com/hosopy/items/957af2887e73923305df

- SwiftでGRPCを動かす方法と基本的な挙動について
    - https://qiita.com/syou007/items/a9de47aa1a45260cdc3f#proto%E3%81%8B%E3%82%89%E3%82%B3%E3%83%BC%E3%83%89%E3%81%AE%E4%BD%9C%E6%88%90

- 公式のサンプル
    - https://github.com/grpc/grpc/tree/master/examples/objective-c/helloworld