サンプルの説明
- .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を設定
- この処理は必須ではないです。
- ユーザーの認証情報の設定箇所を共通化するため、独自に実装しています。
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()
}
}
参考記事
-
・Xcode(Swift) + CocoaPodsでgRPCのClientを生成してServiceを呼び出すまでの流れ
-
SwiftでGRPCを動かす方法と基本的な挙動について
-
公式のサンプル