Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 1 | #gRPC Basics: Objective-C |
| 2 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 3 | This tutorial provides a basic Objective-C programmer's introduction to working with gRPC. By |
| 4 | walking through this example you'll learn how to: |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 5 | |
| 6 | - Define a service in a .proto file. |
| 7 | - Generate client code using the protocol buffer compiler. |
| 8 | - Use the Objective-C gRPC API to write a simple client for your service. |
| 9 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 10 | It assumes a passing familiarity with [protocol buffers](https://developers.google.com/protocol-buffers/docs/overview). |
| 11 | Note that the example in this tutorial uses the proto3 version of the protocol buffers language, |
| 12 | which is currently in alpha release: you can find out more in the [proto3 language guide](https://developers.google.com/protocol-buffers/docs/proto3) |
| 13 | and see the [release notes](https://github.com/google/protobuf/releases) for the new version in the |
| 14 | protocol buffers Github repository. |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 15 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 16 | This isn't a comprehensive guide to using gRPC in Objective-C: more reference documentation is |
| 17 | coming soon. |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 18 | |
| 19 | - [Why use gRPC?](#why-grpc) |
| 20 | - [Example code and setup](#setup) |
| 21 | - [Try it out!](#try) |
| 22 | - [Defining the service](#proto) |
| 23 | - [Generating client code](#protoc) |
| 24 | - [Creating the client](#client) |
| 25 | |
| 26 | <a name="why-grpc"></a> |
| 27 | ## Why use gRPC? |
| 28 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 29 | With gRPC you can define your service once in a .proto file and implement clients and servers in any |
| 30 | of gRPC's supported languages, which in turn can be run in environments ranging from servers inside |
| 31 | Google to your own tablet - all the complexity of communication between different languages and |
| 32 | environments is handled for you by gRPC. You also get all the advantages of working with protocol |
| 33 | buffers, including efficient serialization, a simple IDL, and easy interface updating. |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 34 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 35 | gRPC and proto3 are specially suited for mobile clients: gRPC is implemented on top of HTTP/2, which |
| 36 | results in network bandwidth savings over using HTTP/1.1. Serialization and parsing of the proto |
| 37 | binary format is more efficient than the equivalent JSON, resulting in CPU and battery savings. And |
| 38 | proto3 uses a runtime that has been optimized over the years at Google to keep code size to a |
| 39 | minimum. The latter is important in Objective-C, because the ability of the compiler to strip unused |
| 40 | code is limited by the dynamic nature of the language. |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 41 | |
| 42 | |
| 43 | <a name="setup"></a> |
| 44 | ## Example code and setup |
| 45 | |
Stanley Cheung | 56debcb | 2015-08-31 12:17:34 -0700 | [diff] [blame] | 46 | The example code for our tutorial is in [examples/objective-c/route_guide](.). |
Stanley Cheung | 0a26821 | 2015-08-27 14:38:38 -0700 | [diff] [blame] | 47 | To download the example, clone this repository by running the following command: |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 48 | ```shell |
Stanley Cheung | 0a26821 | 2015-08-27 14:38:38 -0700 | [diff] [blame] | 49 | $ git clone https://github.com/grpc/grpc.git |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 50 | ``` |
| 51 | |
Stanley Cheung | 0a26821 | 2015-08-27 14:38:38 -0700 | [diff] [blame] | 52 | Then change your current directory to `examples/objective-c/route_guide`: |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 53 | ```shell |
Stanley Cheung | 0a26821 | 2015-08-27 14:38:38 -0700 | [diff] [blame] | 54 | $ cd examples/objective-c/route_guide |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 55 | ``` |
| 56 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 57 | Our example is a simple route mapping application that lets clients get information about features |
| 58 | on their route, create a summary of their route, and exchange route information such as traffic |
| 59 | updates with the server and other clients. |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 60 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 61 | You also should have [Cocoapods](https://cocoapods.org/#install) installed, as well as the relevant |
| 62 | tools to generate the client library code (and a server in another language, for testing). You can |
| 63 | obtain the latter by following [these setup instructions](https://github.com/grpc/homebrew-grpc). |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 64 | |
| 65 | |
| 66 | <a name="try"></a> |
| 67 | ## Try it out! |
| 68 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 69 | To try the sample app, we need a gRPC server running locally. Let's compile and run, for example, |
| 70 | the C++ server in this repository: |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 71 | |
| 72 | ```shell |
| 73 | $ pushd ../../cpp/route_guide |
| 74 | $ make |
| 75 | $ ./route_guide_server & |
| 76 | $ popd |
| 77 | ``` |
| 78 | |
| 79 | Now have Cocoapods generate and install the client library for our .proto files: |
| 80 | |
| 81 | ```shell |
| 82 | $ pod install |
| 83 | ``` |
| 84 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 85 | (This might have to compile OpenSSL, which takes around 15 minutes if Cocoapods doesn't have it yet |
| 86 | on your computer's cache). |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 87 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 88 | Finally, open the XCode workspace created by Cocoapods, and run the app. You can check the calling |
| 89 | code in `ViewControllers.m` and see the results in XCode's log console. |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 90 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 91 | The next sections guide you step-by-step through how this proto service is defined, how to generate |
| 92 | a client library from it, and how to create an app that uses that library. |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 93 | |
| 94 | |
| 95 | <a name="proto"></a> |
| 96 | ## Defining the service |
| 97 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 98 | First let's look at how the service we're using is defined. A gRPC *service* and its method |
| 99 | *request* and *response* types using [protocol buffers](https://developers.google.com/protocol-buffers/docs/overview). |
Stanley Cheung | 56debcb | 2015-08-31 12:17:34 -0700 | [diff] [blame] | 100 | You can see the complete .proto file for our example in [`examples/protos/route_guide.proto`](../../protos/route_guide.proto). |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 101 | |
| 102 | To define a service, you specify a named `service` in your .proto file: |
| 103 | |
| 104 | ```protobuf |
| 105 | service RouteGuide { |
| 106 | ... |
| 107 | } |
| 108 | ``` |
| 109 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 110 | Then you define `rpc` methods inside your service definition, specifying their request and response |
| 111 | types. Protocol buffers let you define four kinds of service method, all of which are used in the |
| 112 | `RouteGuide` service: |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 113 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 114 | - A *simple RPC* where the client sends a request to the server and receives a response later, just |
| 115 | like a normal remote procedure call. |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 116 | ```protobuf |
| 117 | // Obtains the feature at a given position. |
| 118 | rpc GetFeature(Point) returns (Feature) {} |
| 119 | ``` |
| 120 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 121 | - A *response-streaming RPC* where the client sends a request to the server and gets back a stream |
| 122 | of response messages. You specify a response-streaming method by placing the `stream` keyword before |
| 123 | the *response* type. |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 124 | ```protobuf |
| 125 | // Obtains the Features available within the given Rectangle. Results are |
| 126 | // streamed rather than returned at once (e.g. in a response message with a |
| 127 | // repeated field), as the rectangle may cover a large area and contain a |
| 128 | // huge number of features. |
| 129 | rpc ListFeatures(Rectangle) returns (stream Feature) {} |
| 130 | ``` |
| 131 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 132 | - A *request-streaming RPC* where the client sends a sequence of messages to the server. Once the |
| 133 | client has finished writing the messages, it waits for the server to read them all and return its |
| 134 | response. You specify a request-streaming method by placing the `stream` keyword before the |
| 135 | *request* type. |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 136 | ```protobuf |
| 137 | // Accepts a stream of Points on a route being traversed, returning a |
| 138 | // RouteSummary when traversal is completed. |
| 139 | rpc RecordRoute(stream Point) returns (RouteSummary) {} |
| 140 | ``` |
| 141 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 142 | - A *bidirectional streaming RPC* where both sides send a sequence of messages to the other. The two |
| 143 | streams operate independently, so clients and servers can read and write in whatever order they |
| 144 | like: for example, the server could wait to receive all the client messages before writing its |
| 145 | responses, or it could alternately read a message then write a message, or some other combination of |
| 146 | reads and writes. The order of messages in each stream is preserved. You specify this type of method |
| 147 | by placing the `stream` keyword before both the request and the response. |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 148 | ```protobuf |
| 149 | // Accepts a stream of RouteNotes sent while a route is being traversed, |
| 150 | // while receiving other RouteNotes (e.g. from other users). |
| 151 | rpc RouteChat(stream RouteNote) returns (stream RouteNote) {} |
| 152 | ``` |
| 153 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 154 | Our .proto file also contains protocol buffer message type definitions for all the request and |
| 155 | response types used in our service methods - for example, here's the `Point` message type: |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 156 | ```protobuf |
| 157 | // Points are represented as latitude-longitude pairs in the E7 representation |
| 158 | // (degrees multiplied by 10**7 and rounded to the nearest integer). |
| 159 | // Latitudes should be in the range +/- 90 degrees and longitude should be in |
| 160 | // the range +/- 180 degrees (inclusive). |
| 161 | message Point { |
| 162 | int32 latitude = 1; |
| 163 | int32 longitude = 2; |
| 164 | } |
| 165 | ``` |
| 166 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 167 | You can specify a prefix to be used for your generated classes by adding the `objc_class_prefix` |
| 168 | option at the top of the file. For example: |
Jorge Canizales | 69cd3ef | 2015-06-08 23:40:01 -0700 | [diff] [blame] | 169 | ```protobuf |
| 170 | option objc_class_prefix = "RTG"; |
| 171 | ``` |
| 172 | |
| 173 | |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 174 | <a name="protoc"></a> |
| 175 | ## Generating client code |
| 176 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 177 | Next we need to generate the gRPC client interfaces from our .proto service definition. We do this |
| 178 | using the protocol buffer compiler (`protoc`) with a special gRPC Objective-C plugin. |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 179 | |
Stanley Cheung | 56debcb | 2015-08-31 12:17:34 -0700 | [diff] [blame] | 180 | For simplicity, we've provided a [Podspec file](RouteGuide.podspec) |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 181 | that runs `protoc` for you with the appropriate plugin, input, and output, and describes how to |
Stanley Cheung | 0a26821 | 2015-08-27 14:38:38 -0700 | [diff] [blame] | 182 | compile the generated files. You just need to run in this directory (`examples/objective-c/route_guide`): |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 183 | |
| 184 | ```shell |
| 185 | $ pod install |
| 186 | ``` |
| 187 | |
| 188 | which, before installing the generated library in the XCode project of this sample, runs: |
| 189 | |
| 190 | ```shell |
| 191 | $ protoc -I ../../protos --objc_out=Pods/RouteGuide --objcgrpc_out=Pods/RouteGuide ../../protos/route_guide.proto |
| 192 | ``` |
| 193 | |
Jorge Canizales | 3e420f2 | 2015-06-09 10:18:23 -0700 | [diff] [blame] | 194 | Running this command generates the following files under `Pods/RouteGuide/`: |
Jorge Canizales | 69cd3ef | 2015-06-08 23:40:01 -0700 | [diff] [blame] | 195 | - `RouteGuide.pbobjc.h`, the header which declares your generated message classes. |
| 196 | - `RouteGuide.pbobjc.m`, which contains the implementation of your message classes. |
| 197 | - `RouteGuide.pbrpc.h`, the header which declares your generated service classes. |
| 198 | - `RouteGuide.pbrpc.m`, which contains the implementation of your service classes. |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 199 | |
| 200 | These contain: |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 201 | - All the protocol buffer code to populate, serialize, and retrieve our request and response message |
| 202 | types. |
| 203 | - A class called `RTGRouteGuide` that lets clients call the methods defined in the `RouteGuide` |
| 204 | service. |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 205 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 206 | You can also use the provided Podspec file to generate client code from any other proto service |
| 207 | definition; just replace the name (matching the file name), version, and other metadata. |
Jorge Canizales | 69cd3ef | 2015-06-08 23:40:01 -0700 | [diff] [blame] | 208 | |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 209 | |
| 210 | <a name="client"></a> |
| 211 | ## Creating the client |
| 212 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 213 | In this section, we'll look at creating an Objective-C client for our `RouteGuide` service. You can |
Stanley Cheung | 56debcb | 2015-08-31 12:17:34 -0700 | [diff] [blame] | 214 | see our complete example client code in [ViewControllers.m](ViewControllers.m). |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 215 | (Note: In your apps, for maintainability and readability reasons, you shouldn't put all of your view |
| 216 | controllers in a single file; it's done here only to simplify the learning process). |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 217 | |
Jorge Canizales | 69cd3ef | 2015-06-08 23:40:01 -0700 | [diff] [blame] | 218 | ### Constructing a client object |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 219 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 220 | To call service methods, we first need to create a client object, an instance of the generated |
| 221 | `RTGRouteGuide` class. The designated initializer of the class expects a `NSString *` with the |
| 222 | server address and port we want to connect to: |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 223 | |
| 224 | ```objective-c |
| 225 | #import <RouteGuide/RouteGuide.pbrpc.h> |
| 226 | |
| 227 | static NSString * const kHostAddress = @"http://localhost:50051"; |
| 228 | |
| 229 | ... |
| 230 | |
| 231 | RTGRouteGuide *client = [[RTGRouteGuide alloc] initWithHost:kHostAddress]; |
| 232 | ``` |
| 233 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 234 | Notice that we've specified the HTTP scheme in the host address. This is because the server we will |
| 235 | be using to test our client doesn't use [TLS](http://en.wikipedia.org/wiki/Transport_Layer_Security). |
| 236 | This is fine because it will be running locally on our development machine. The most common case, |
| 237 | though, is connecting with a gRPC server on the internet, running gRPC over TLS. For that case, the |
| 238 | HTTPS scheme can be specified (or no scheme at all, as HTTPS is the default value). The default |
| 239 | value of the port is that of the scheme selected: 443 for HTTPS and 80 for HTTP. |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 240 | |
| 241 | |
| 242 | ### Calling service methods |
| 243 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 244 | Now let's look at how we call our service methods. As you will see, all these methods are |
| 245 | asynchronous, so you can call them from the main thread of your app without worrying about freezing |
| 246 | your UI or the OS killing your app. |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 247 | |
| 248 | #### Simple RPC |
| 249 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 250 | Calling the simple RPC `GetFeature` is nearly as straightforward as calling any other asynchronous |
| 251 | method on Cocoa. |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 252 | |
| 253 | ```objective-c |
| 254 | RTGPoint *point = [RTGPoint message]; |
| 255 | point.latitude = 40E7; |
| 256 | point.longitude = -74E7; |
| 257 | |
| 258 | [client getFeatureWithRequest:point handler:^(RTGFeature *response, NSError *error) { |
| 259 | if (response) { |
| 260 | // Successful response received |
| 261 | } else { |
| 262 | // RPC error |
| 263 | } |
| 264 | }]; |
| 265 | ``` |
| 266 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 267 | As you can see, we create and populate a request protocol buffer object (in our case `RTGPoint`). |
| 268 | Then, we call the method on the client object, passing it the request, and a block to handle the |
| 269 | response (or any RPC error). If the RPC finishes successfully, the handler block is called with a |
| 270 | `nil` error argument, and we can read the response information from the server from the response |
| 271 | argument. If, instead, some RPC error happens, the handler block is called with a `nil` response |
| 272 | argument, and we can read the details of the problem from the error argument. |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 273 | |
| 274 | ```objective-c |
| 275 | NSLog(@"Found feature called %@ at %@.", response.name, response.location); |
| 276 | ``` |
| 277 | |
| 278 | #### Streaming RPCs |
| 279 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 280 | Now let's look at our streaming methods. Here's where we call the response-streaming method |
| 281 | `ListFeatures`, which results in our client receiving a stream of geographical `RTGFeature`s: |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 282 | |
| 283 | ```objective-c |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 284 | [client listFeaturesWithRequest:rectangle |
| 285 | eventHandler:^(BOOL done, RTGFeature *response, NSError *error) { |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 286 | if (response) { |
| 287 | // Element of the stream of responses received |
| 288 | } else if (error) { |
| 289 | // RPC error; the stream is over. |
| 290 | } |
| 291 | if (done) { |
| 292 | // The stream is over (all the responses were received, or an error occured). Do any cleanup. |
| 293 | } |
| 294 | }]; |
| 295 | ``` |
| 296 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 297 | Notice how the signature of the `eventHandler` block now includes a `BOOL done` parameter. The |
| 298 | `eventHandler` block can be called any number of times; only on the last call is the `done` argument |
| 299 | value set to `YES`. If an error occurs, the RPC finishes and the block is called with the arguments |
| 300 | `(YES, nil, error)`. |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 301 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 302 | The request-streaming method `RecordRoute` expects a stream of `RTGPoint`s from the cient. This |
| 303 | stream is passed to the method as an object of class `GRXWriter`. The simplest way to create one is |
| 304 | to initialize one from a `NSArray` object: |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 305 | |
| 306 | |
| 307 | ```objective-c |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 308 | #import <RxLibrary/GRXWriter+Immediate.h> |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 309 | |
| 310 | ... |
| 311 | |
| 312 | RTGPoint *point1 = [RTGPoint message]; |
| 313 | point.latitude = 40E7; |
| 314 | point.longitude = -74E7; |
| 315 | |
| 316 | RTGPoint *point2 = [RTGPoint message]; |
| 317 | point.latitude = 40E7; |
| 318 | point.longitude = -74E7; |
| 319 | |
| 320 | GRXWriter *locationsWriter = [GRXWriter writerWithContainer:@[point1, point2]]; |
| 321 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 322 | [client recordRouteWithRequestsWriter:locationsWriter |
| 323 | handler:^(RTGRouteSummary *response, NSError *error) { |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 324 | if (response) { |
| 325 | NSLog(@"Finished trip with %i points", response.pointCount); |
| 326 | NSLog(@"Passed %i features", response.featureCount); |
| 327 | NSLog(@"Travelled %i meters", response.distance); |
| 328 | NSLog(@"It took %i seconds", response.elapsedTime); |
| 329 | } else { |
| 330 | NSLog(@"RPC error: %@", error); |
| 331 | } |
| 332 | }]; |
| 333 | |
| 334 | ``` |
| 335 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 336 | The `GRXWriter` class is generic enough to allow for asynchronous streams, streams of future values, |
| 337 | or even infinite streams. |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 338 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 339 | Finally, let's look at our bidirectional streaming RPC `RouteChat()`. The way to call a |
| 340 | bidirectional streaming RPC is just a combination of how to call request-streaming RPCs and |
| 341 | response-streaming RPCs. |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 342 | |
| 343 | ```objective-c |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 344 | [client routeChatWithRequestsWriter:notesWriter |
| 345 | eventHandler:^(BOOL done, RTGRouteNote *note, NSError *error) { |
Jorge Canizales | 5436cf1 | 2015-06-08 23:06:54 -0700 | [diff] [blame] | 346 | if (note) { |
| 347 | NSLog(@"Got message %@ at %@", note.message, note.location); |
| 348 | } else if (error) { |
| 349 | NSLog(@"RPC error: %@", error); |
| 350 | } |
| 351 | if (done) { |
| 352 | NSLog(@"Chat ended."); |
| 353 | } |
| 354 | }]; |
| 355 | ``` |
| 356 | |
Jorge Canizales | 54f4d53 | 2015-07-17 17:53:06 -0700 | [diff] [blame] | 357 | The semantics for the handler block and the `GRXWriter` argument here are exactly the same as for |
| 358 | our request-streaming and response-streaming methods. Although both client and server will always |
| 359 | get the other's messages in the order they were written, the two streams operate completely |
| 360 | independently. |