LoginSignup
1
0

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-07-28

サンプルの説明

  • .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
report.proto
push_notify.proto
commons.proto
chat.proto
calling.proto
block.proto
account.proto

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

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
AccountApiClient.swift
BlockApiClient.swift
CallingApiClient.swift
ChatApiClient.swift
ReportApiClient.swift
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

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

    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()
        }
    }

参考記事

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0