基本チュートリアル

Objective-CでのgRPCの基本的なチュートリアル紹介。

基本チュートリアル

Objective-CでのgRPCの基本的なチュートリアル紹介。

このチュートリアルでは、gRPCを扱うためのObjective-Cプログラマ向けの基本的な入門を提供します。

この例を段階的に実行することで、以下のことを学びます。

  • .protoファイルでサービスを定義します。
  • プロトコルバッファコンパイラを使用してクライアントコードを生成します。
  • Objective-C gRPC APIを使用して、サービス用のシンプルなクライアントを記述します。

これは、プロトコルバッファの簡単な知識を前提としています。このチュートリアルの例では、プロトコルバッファ言語のproto3バージョンを使用しています。詳細については、proto3言語ガイドおよびObjective-C生成コードガイドを参照してください。

gRPCを使用する理由

私たちの例は、クライアントがルート上の機能に関する情報を取得したり、ルートの要約を作成したり、サーバーや他のクライアントと交通情報などのルート情報を交換したりできる、シンプルなルートマッピングアプリケーションです。

gRPCを使用すると、1つの.protoファイルでサービスを一度定義し、gRPCがサポートする任意の言語でクライアントとサーバーを生成できます。これにより、大規模データセンター内のサーバーから個人のタブレットまで、さまざまな環境で実行できます。異なる言語や環境間の通信の複雑さはすべてgRPCによって処理されます。また、効率的なシリアライゼーション、シンプルなIDL、簡単なインターフェース更新など、プロトコルバッファを使用する利点もすべて得られます。

サンプルコードとセットアップ

チュートリアルのサンプルコードは、grpc/grpc/examples/objective-c/route_guideにあります。サンプルをダウンロードするには、次のコマンドを実行してgrpcリポジトリをクローンします。

git clone -b v1.74.0 --depth 1 --shallow-submodules https://github.com/grpc/grpc
cd grpc
git submodule update --init

次に、現在のディレクトリをexamples/objective-c/route_guideに変更します。

cd examples/objective-c/route_guide

私たちの例は、クライアントがルート上の機能に関する情報を取得したり、ルートの要約を作成したり、サーバーや他のクライアントと交通情報などのルート情報を交換したりできる、シンプルなルートマッピングアプリケーションです。

また、Cocoapodsがインストールされていること、およびクライアントライブラリコード(およびテスト用の別の言語のサーバー)を生成するための関連ツールがインストールされていることを確認してください。後者はこれらのセットアップ手順に従って取得できます。

試してみましょう!

サンプルアプリを試すには、ローカルで実行されているgRPCサーバーが必要です。このリポジトリのC++サーバーを例として、コンパイルして実行しましょう。

pushd ../../cpp/route_guide
make
./route_guide_server &
popd

これで、Cocoapodsが.protoファイル用のクライアントライブラリを生成してインストールします。

pod install

(Cocoapodsがまだコンピュータのキャッシュに持っていない場合、OpenSSLのコンパイルが必要になる場合があり、約15分かかることがあります)。

最後に、Cocoapodsによって作成されたXCodeワークスペースを開き、アプリを実行します。呼び出しコードはViewControllers.mで確認でき、結果はXCodeのログコンソールに表示されます。

次のセクションでは、このprotoサービスがどのように定義され、そこからクライアントライブラリがどのように生成され、そのライブラリを使用するアプリがどのように作成されるかを段階的に説明します。

サービスを定義する

まず、使用するサービスがどのように定義されているかを見てみましょう。gRPCのサービスとそのメソッドのリクエストおよびレスポンスタイプは、プロトコルバッファを使用して定義されます。例の完全な.protoファイルは、examples/protos/route_guide.protoで確認できます。

.protoファイルで名前付きのserviceを指定することで、サービスを定義します。

service RouteGuide {
   ...
}

次に、サービス定義内でrpcメソッドを定義し、リクエストとレスポンスのタイプを指定します。プロトコルバッファでは、4種類のサービスメソッドを定義できます。これらはすべてRouteGuideサービスで使用されています。

  • クライアントがリクエストをサーバーに送信し、後でレスポンスを受信するシンプルなRPC。通常のリモートプロシージャコールと同様です。

    // Obtains the feature at a given position.
    rpc GetFeature(Point) returns (Feature) {}
    
  • クライアントがリクエストをサーバーに送信し、レスポンスメッセージのストリームを受信するレスポンスストリーミングRPCstreamキーワードをレスポンスタイプの前に配置することで、レスポンスストリーミングメソッドを指定します。

    // Obtains the Features available within the given Rectangle.  Results are
    // streamed rather than returned at once (e.g. in a response message with a
    // repeated field), as the rectangle may cover a large area and contain a
    // huge number of features.
    rpc ListFeatures(Rectangle) returns (stream Feature) {}
    
  • クライアントがメッセージのシーケンスをサーバーに送信するリクエストストリーミングRPC。クライアントがメッセージの書き込みを完了すると、サーバーがすべてを読み取ってレスポンスを返すのを待ちます。streamキーワードをリクエストタイプの前に配置することで、リクエストストリーミングメソッドを指定します。

    // Accepts a stream of Points on a route being traversed, returning a
    // RouteSummary when traversal is completed.
    rpc RecordRoute(stream Point) returns (RouteSummary) {}
    
  • 双方が互いにメッセージのシーケンスを送信する双方向ストリーミングRPC。2つのストリームは独立して動作するため、クライアントとサーバーは好きな順序で読み書きできます。たとえば、サーバーはクライアントメッセージをすべて受信してからレスポンスを返すことも、メッセージを1つ読み取ってから1つ書き込むことも、その他の読み書きの組み合わせも可能です。各ストリーム内のメッセージの順序は保持されます。このタイプのメソッドは、リクエストとレスポンスの両方の前にstreamキーワードを配置することで指定します。

    // Accepts a stream of RouteNotes sent while a route is being traversed,
    // while receiving other RouteNotes (e.g. from other users).
    rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}
    

私たちの.protoファイルには、サービスメソッドで使用されるすべてリクエストとレスポンスの型に対応するプロトコルバッファメッセージ型定義も含まれています。たとえば、ここにPointメッセージ型があります。

// Points are represented as latitude-longitude pairs in the E7 representation
// (degrees multiplied by 10**7 and rounded to the nearest integer).
// Latitudes should be in the range +/- 90 degrees and longitude should be in
// the range +/- 180 degrees (inclusive).
message Point {
  int32 latitude = 1;
  int32 longitude = 2;
}

ファイルの上部にあるobjc_class_prefixオプションを追加することで、生成されるクラスに使用されるプレフィックスを指定できます。例:

option objc_class_prefix = "RTG";

クライアントコードの生成

次に、.protoサービス定義からgRPCクライアントインターフェースを生成する必要があります。これは、プロトコルバッファコンパイラ(protoc)と特別なgRPC Objective-Cプラグインを使用して行います。

簡単にするために、適切なプラグイン、入力、および出力でprotocを実行し、生成されたファイルのコンパイル方法を説明するPodspecファイルを提供しました。このディレクトリ(examples/objective-c/route_guide)で実行するだけです。

pod install

これは、このサンプルのXCodeプロジェクトに生成されたライブラリをインストールする前に、次を実行します。

protoc -I ../../protos --objc_out=Pods/RouteGuide --objcgrpc_out=Pods/RouteGuide ../../protos/route_guide.proto

このコマンドを実行すると、Pods/RouteGuide/の下に次のファイルが生成されます。

  • RouteGuide.pbobjc.h: 生成されたメッセージクラスを宣言するヘッダー。
  • RouteGuide.pbobjc.m: メッセージクラスの実装が含まれています。
  • RouteGuide.pbrpc.h: 生成されたサービスクラスを宣言するヘッダー。
  • RouteGuide.pbrpc.m: サービスクラスの実装が含まれています。

これらには以下が含まれます。

  • リクエストおよびレスポンスメッセージタイプをポピュレート、シリアライズ、および取得するためのすべてのプロトコルバッファコード。
  • RouteGuideサービスで定義されたメソッドをクライアントが呼び出すことができるRTGRouteGuideという名前のクラス。

提供されたPodspecファイルを使用して、他のprotoサービス定義からクライアントコードを生成することもできます。ファイル名(一致するもの)、バージョン、およびその他のメタデータを置き換えるだけです。

クライアントアプリケーションの作成

このセクションでは、RouteGuideサービス用のObjective-Cクライアントの作成について説明します。完全なサンプルクライアントコードは、examples/objective-c/route_guide/ViewControllers.mで確認できます。

サービスオブジェクトの構築

サービスメソッドを呼び出すには、まずサービスオブジェクト、つまり生成されたRTGRouteGuideクラスのインスタンスを作成する必要があります。クラスの指定されたイニシャライザは、接続したいサーバーのアドレスとポートを含むNSString *を期待します。

#import <GRPCClient/GRPCCall+Tests.h>
#import <RouteGuide/RouteGuide.pbrpc.h>
#import <GRPCClient/GRPCTransport.h>

static NSString * const kHostAddress = @"localhost:50051";
...
GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
options.transport = GRPCDefaultTransportImplList.core_insecure;

RTGRouteGuide *service = [[RTGRouteGuide alloc] initWithHost:kHostAddress callOptions:options];

サービスが安全でないトランスポートで構築されていることに注意してください。これは、クライアントのテストに使用するサーバーがTLSを使用していないためです。これは、ローカルの開発マシンで実行されるため問題ありません。ただし、最も一般的なケースは、インターネット上のgRPCサーバーに接続し、TLS over gRPCを実行することです。その場合、オプションoptions.transportを設定する必要はありません。gRPCはデフォルトで安全なTLSトランスポートを使用するためです。

サービスメソッドを呼び出す

次に、サービスメソッドの呼び出し方法を見てみましょう。ご覧のとおり、これらのメソッドはすべて非同期であるため、UIがフリーズしたり、OSによってアプリが終了されたりすることを心配せずに、アプリのメインスレッドから呼び出すことができます。

シンプルなRPC

シンプルなRPCGetFeatureの呼び出しは、Cocoaの他の非同期メソッドの呼び出しと同様に簡単です。


RTGPoint *point = [RTGPoint message];
point.latitude = 40E7;
point.longitude = -74E7;

GRPCUnaryResponseHandler *handler =
    [[GRPCUnaryResponseHandler alloc] initWithResponseHandler:
        ^(RTGFeature *response, NSError *error) {
          if (response) {
            // Successful response received
          } else {
            // RPC error
          }
        }
                                        responseDispatchQueue:nil];

[[service getFeatureWithMessage:point responseHandler:handler callOptions:nil] start];

ご覧のとおり、リクエストプロトコルバッファオブジェクト(この場合はRTGPoint)を作成してポピュレートします。次に、サービスオブジェクトのメソッドを呼び出し、リクエストと、レスポンス(またはRPCエラー)を処理するためのブロックを渡します。RPCが正常に完了すると、ハンドラブロックはnilエラー引数で呼び出され、レスポンス引数からサーバーからのレスポンス情報を読み取ることができます。代わりに、RPCエラーが発生した場合、ハンドラブロックはnilレスポンス引数で呼び出され、エラー引数から問題の詳細を読み取ることができます。

ストリーミングRPC

次に、ストリーミングメソッドを見てみましょう。ここでは、レスポンスストリーミングメソッドListFeaturesを呼び出します。これにより、クライアントアプリは地理的なRTGFeatureのストリームを受信します。


- (void)didReceiveProtoMessage(GPBMessage *)message {
  if (message) {
    NSLog(@"Found feature at %@ called %@.", response.location, response.name);
  }
}

- (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error {
  if (error) {
    NSLog(@"RPC error: %@", error);
  }
}

- (void)execRequest {
  ...
  [[service listFeaturesWithMessage:rectangle responseHandler:self callOptions:nil] start];
}

レスポンスハンドラオブジェクトを提供する代わりに、ビューコントローラーオブジェクト自体がレスポンスを処理することに注意してください。didReceiveProtoMessage:メソッドは、メッセージを受信したときに呼び出されます。これは何度でも呼び出される可能性があります。didCloseWithTrailingMetadata:メソッドは、呼び出しが完了し、gRPCステータスがサーバーから受信されたとき(または呼び出し中にエラーが発生した場合)に呼び出されます。

リクエストストリーミングメソッドRecordRouteは、クライアントからのRTGPointのストリームを期待します。このストリームは、呼び出しが開始された後にgRPC呼び出しオブジェクトに書き込むことができます。

RTGPoint *point1 = [RTGPoint message];
point.latitude = 40E7;
point.longitude = -74E7;

RTGPoint *point2 = [RTGPoint message];
point.latitude = 40E7;
point.longitude = -74E7;

GRPCUnaryResponseHandler *handler =
    [[GRPCUnaryResponseHandler alloc] initWithResponseHandler:
        ^(RTGRouteSummary *response, NSError *error) {
            if (response) {
              NSLog(@"Finished trip with %i points", response.pointCount);
              NSLog(@"Passed %i features", response.featureCount);
              NSLog(@"Travelled %i meters", response.distance);
              NSLog(@"It took %i seconds", response.elapsedTime);
            } else {
              NSLog(@"RPC error: %@", error);
            }
        }
                                        responseDispatchQueue:nil];
GRPCStreamingProtoCall *call =
    [service recordRouteWithResponseHandler:handler callOptions:nil];
[call start];
[call writeMessage:point1];
[call writeMessage:point2];
[call finish];

gRPC呼び出しオブジェクトはリクエストストリームの終了を知らないため、リクエストストリームが完了したときにユーザーはfinish:メソッドを呼び出す必要があることに注意してください。

最後に、双方向ストリーミングRPCRouteChat()を見てみましょう。双方向ストリーミングRPCを呼び出す方法は、リクエストストリーミングRPCとレスポンスストリーミングRPCの呼び出し方法の組み合わせにすぎません。


- (void)didReceiveProtoMessage(GPBMessage *)message {
  RTGRouteNote *note = (RTGRouteNote *)message;
  if (note) {
    NSLog(@"Got message %@ at %@", note.message, note.location);
  }
}

- (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error {
  if (error) {
    NSLog(@"RPC error: %@", error);
  } else {
    NSLog(@"Chat ended.");
  }
}

- (void)execRequest {
  ...
  GRPCStreamingProtoCall *call =
      [service routeChatWithResponseHandler:self callOptions:nil];
  [call start];
  [call writeMessage:note1];
  ...
  [call writeMessage:noteN];
  [call finish];
}