blob: 15864c01f4b8b0b0c3c451ed186f7639edcaed2d [file] [log] [blame] [view]
Jorge Canizales5436cf12015-06-08 23:06:54 -07001#gRPC Basics: Objective-C
2
Jorge Canizales54f4d532015-07-17 17:53:06 -07003This tutorial provides a basic Objective-C programmer's introduction to working with gRPC. By
4walking through this example you'll learn how to:
Jorge Canizales5436cf12015-06-08 23:06:54 -07005
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 Canizales54f4d532015-07-17 17:53:06 -070010It assumes a passing familiarity with [protocol buffers](https://developers.google.com/protocol-buffers/docs/overview).
11Note that the example in this tutorial uses the proto3 version of the protocol buffers language,
12which is currently in alpha release: you can find out more in the [proto3 language guide](https://developers.google.com/protocol-buffers/docs/proto3)
13and see the [release notes](https://github.com/google/protobuf/releases) for the new version in the
14protocol buffers Github repository.
Jorge Canizales5436cf12015-06-08 23:06:54 -070015
Jorge Canizales54f4d532015-07-17 17:53:06 -070016This isn't a comprehensive guide to using gRPC in Objective-C: more reference documentation is
17coming soon.
Jorge Canizales5436cf12015-06-08 23:06:54 -070018
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 Canizales54f4d532015-07-17 17:53:06 -070029With gRPC you can define your service once in a .proto file and implement clients and servers in any
30of gRPC's supported languages, which in turn can be run in environments ranging from servers inside
31Google to your own tablet - all the complexity of communication between different languages and
32environments is handled for you by gRPC. You also get all the advantages of working with protocol
33buffers, including efficient serialization, a simple IDL, and easy interface updating.
Jorge Canizales5436cf12015-06-08 23:06:54 -070034
Jorge Canizales54f4d532015-07-17 17:53:06 -070035gRPC and proto3 are specially suited for mobile clients: gRPC is implemented on top of HTTP/2, which
36results in network bandwidth savings over using HTTP/1.1. Serialization and parsing of the proto
37binary format is more efficient than the equivalent JSON, resulting in CPU and battery savings. And
38proto3 uses a runtime that has been optimized over the years at Google to keep code size to a
39minimum. The latter is important in Objective-C, because the ability of the compiler to strip unused
40code is limited by the dynamic nature of the language.
Jorge Canizales5436cf12015-06-08 23:06:54 -070041
42
43<a name="setup"></a>
44## Example code and setup
45
Stanley Cheung56debcb2015-08-31 12:17:34 -070046The example code for our tutorial is in [examples/objective-c/route_guide](.).
Stanley Cheung0a268212015-08-27 14:38:38 -070047To download the example, clone this repository by running the following command:
Jorge Canizales5436cf12015-06-08 23:06:54 -070048```shell
Stanley Cheung0a268212015-08-27 14:38:38 -070049$ git clone https://github.com/grpc/grpc.git
Jorge Canizales5436cf12015-06-08 23:06:54 -070050```
51
Stanley Cheung0a268212015-08-27 14:38:38 -070052Then change your current directory to `examples/objective-c/route_guide`:
Jorge Canizales5436cf12015-06-08 23:06:54 -070053```shell
Stanley Cheung0a268212015-08-27 14:38:38 -070054$ cd examples/objective-c/route_guide
Jorge Canizales5436cf12015-06-08 23:06:54 -070055```
56
Jorge Canizales54f4d532015-07-17 17:53:06 -070057Our example is a simple route mapping application that lets clients get information about features
58on their route, create a summary of their route, and exchange route information such as traffic
59updates with the server and other clients.
Jorge Canizales5436cf12015-06-08 23:06:54 -070060
Jorge Canizales54f4d532015-07-17 17:53:06 -070061You also should have [Cocoapods](https://cocoapods.org/#install) installed, as well as the relevant
62tools to generate the client library code (and a server in another language, for testing). You can
63obtain the latter by following [these setup instructions](https://github.com/grpc/homebrew-grpc).
Jorge Canizales5436cf12015-06-08 23:06:54 -070064
65
66<a name="try"></a>
67## Try it out!
68
Jorge Canizales54f4d532015-07-17 17:53:06 -070069To try the sample app, we need a gRPC server running locally. Let's compile and run, for example,
70the C++ server in this repository:
Jorge Canizales5436cf12015-06-08 23:06:54 -070071
72```shell
73$ pushd ../../cpp/route_guide
74$ make
75$ ./route_guide_server &
76$ popd
77```
78
79Now have Cocoapods generate and install the client library for our .proto files:
80
81```shell
82$ pod install
83```
84
Jorge Canizales54f4d532015-07-17 17:53:06 -070085(This might have to compile OpenSSL, which takes around 15 minutes if Cocoapods doesn't have it yet
86on your computer's cache).
Jorge Canizales5436cf12015-06-08 23:06:54 -070087
Jorge Canizales54f4d532015-07-17 17:53:06 -070088Finally, open the XCode workspace created by Cocoapods, and run the app. You can check the calling
89code in `ViewControllers.m` and see the results in XCode's log console.
Jorge Canizales5436cf12015-06-08 23:06:54 -070090
Jorge Canizales54f4d532015-07-17 17:53:06 -070091The next sections guide you step-by-step through how this proto service is defined, how to generate
92a client library from it, and how to create an app that uses that library.
Jorge Canizales5436cf12015-06-08 23:06:54 -070093
94
95<a name="proto"></a>
96## Defining the service
97
Jorge Canizales54f4d532015-07-17 17:53:06 -070098First 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 Cheung56debcb2015-08-31 12:17:34 -0700100You can see the complete .proto file for our example in [`examples/protos/route_guide.proto`](../../protos/route_guide.proto).
Jorge Canizales5436cf12015-06-08 23:06:54 -0700101
102To define a service, you specify a named `service` in your .proto file:
103
104```protobuf
105service RouteGuide {
106 ...
107}
108```
109
Jorge Canizales54f4d532015-07-17 17:53:06 -0700110Then you define `rpc` methods inside your service definition, specifying their request and response
111types. Protocol buffers let you define four kinds of service method, all of which are used in the
112`RouteGuide` service:
Jorge Canizales5436cf12015-06-08 23:06:54 -0700113
Jorge Canizales54f4d532015-07-17 17:53:06 -0700114- A *simple RPC* where the client sends a request to the server and receives a response later, just
115like a normal remote procedure call.
Jorge Canizales5436cf12015-06-08 23:06:54 -0700116```protobuf
117 // Obtains the feature at a given position.
118 rpc GetFeature(Point) returns (Feature) {}
119```
120
Jorge Canizales54f4d532015-07-17 17:53:06 -0700121- A *response-streaming RPC* where the client sends a request to the server and gets back a stream
122of response messages. You specify a response-streaming method by placing the `stream` keyword before
123the *response* type.
Jorge Canizales5436cf12015-06-08 23:06:54 -0700124```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 Canizales54f4d532015-07-17 17:53:06 -0700132- A *request-streaming RPC* where the client sends a sequence of messages to the server. Once the
133client has finished writing the messages, it waits for the server to read them all and return its
134response. You specify a request-streaming method by placing the `stream` keyword before the
135*request* type.
Jorge Canizales5436cf12015-06-08 23:06:54 -0700136```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 Canizales54f4d532015-07-17 17:53:06 -0700142- A *bidirectional streaming RPC* where both sides send a sequence of messages to the other. The two
143streams operate independently, so clients and servers can read and write in whatever order they
144like: for example, the server could wait to receive all the client messages before writing its
145responses, or it could alternately read a message then write a message, or some other combination of
146reads and writes. The order of messages in each stream is preserved. You specify this type of method
147by placing the `stream` keyword before both the request and the response.
Jorge Canizales5436cf12015-06-08 23:06:54 -0700148```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 Canizales54f4d532015-07-17 17:53:06 -0700154Our .proto file also contains protocol buffer message type definitions for all the request and
155response types used in our service methods - for example, here's the `Point` message type:
Jorge Canizales5436cf12015-06-08 23:06:54 -0700156```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).
161message Point {
162 int32 latitude = 1;
163 int32 longitude = 2;
164}
165```
166
Jorge Canizales54f4d532015-07-17 17:53:06 -0700167You can specify a prefix to be used for your generated classes by adding the `objc_class_prefix`
168option at the top of the file. For example:
Jorge Canizales69cd3ef2015-06-08 23:40:01 -0700169```protobuf
170option objc_class_prefix = "RTG";
171```
172
173
Jorge Canizales5436cf12015-06-08 23:06:54 -0700174<a name="protoc"></a>
175## Generating client code
176
Jorge Canizales54f4d532015-07-17 17:53:06 -0700177Next we need to generate the gRPC client interfaces from our .proto service definition. We do this
178using the protocol buffer compiler (`protoc`) with a special gRPC Objective-C plugin.
Jorge Canizales5436cf12015-06-08 23:06:54 -0700179
Stanley Cheung56debcb2015-08-31 12:17:34 -0700180For simplicity, we've provided a [Podspec file](RouteGuide.podspec)
Jorge Canizales54f4d532015-07-17 17:53:06 -0700181that runs `protoc` for you with the appropriate plugin, input, and output, and describes how to
Stanley Cheung0a268212015-08-27 14:38:38 -0700182compile the generated files. You just need to run in this directory (`examples/objective-c/route_guide`):
Jorge Canizales5436cf12015-06-08 23:06:54 -0700183
184```shell
185$ pod install
186```
187
188which, 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 Canizales3e420f22015-06-09 10:18:23 -0700194Running this command generates the following files under `Pods/RouteGuide/`:
Jorge Canizales69cd3ef2015-06-08 23:40:01 -0700195- `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 Canizales5436cf12015-06-08 23:06:54 -0700199
200These contain:
Jorge Canizales54f4d532015-07-17 17:53:06 -0700201- All the protocol buffer code to populate, serialize, and retrieve our request and response message
202types.
203- A class called `RTGRouteGuide` that lets clients call the methods defined in the `RouteGuide`
204service.
Jorge Canizales5436cf12015-06-08 23:06:54 -0700205
Jorge Canizales54f4d532015-07-17 17:53:06 -0700206You can also use the provided Podspec file to generate client code from any other proto service
207definition; just replace the name (matching the file name), version, and other metadata.
Jorge Canizales69cd3ef2015-06-08 23:40:01 -0700208
Jorge Canizales5436cf12015-06-08 23:06:54 -0700209
210<a name="client"></a>
211## Creating the client
212
Jorge Canizales54f4d532015-07-17 17:53:06 -0700213In this section, we'll look at creating an Objective-C client for our `RouteGuide` service. You can
Stanley Cheung56debcb2015-08-31 12:17:34 -0700214see our complete example client code in [ViewControllers.m](ViewControllers.m).
Jorge Canizales54f4d532015-07-17 17:53:06 -0700215(Note: In your apps, for maintainability and readability reasons, you shouldn't put all of your view
216controllers in a single file; it's done here only to simplify the learning process).
Jorge Canizales5436cf12015-06-08 23:06:54 -0700217
Jorge Canizales69cd3ef2015-06-08 23:40:01 -0700218### Constructing a client object
Jorge Canizales5436cf12015-06-08 23:06:54 -0700219
Jorge Canizales54f4d532015-07-17 17:53:06 -0700220To 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
222server address and port we want to connect to:
Jorge Canizales5436cf12015-06-08 23:06:54 -0700223
224```objective-c
225#import <RouteGuide/RouteGuide.pbrpc.h>
226
227static NSString * const kHostAddress = @"http://localhost:50051";
228
229...
230
231RTGRouteGuide *client = [[RTGRouteGuide alloc] initWithHost:kHostAddress];
232```
233
Jorge Canizales54f4d532015-07-17 17:53:06 -0700234Notice that we've specified the HTTP scheme in the host address. This is because the server we will
235be using to test our client doesn't use [TLS](http://en.wikipedia.org/wiki/Transport_Layer_Security).
236This is fine because it will be running locally on our development machine. The most common case,
237though, is connecting with a gRPC server on the internet, running gRPC over TLS. For that case, the
238HTTPS scheme can be specified (or no scheme at all, as HTTPS is the default value). The default
239value of the port is that of the scheme selected: 443 for HTTPS and 80 for HTTP.
Jorge Canizales5436cf12015-06-08 23:06:54 -0700240
241
242### Calling service methods
243
Jorge Canizales54f4d532015-07-17 17:53:06 -0700244Now let's look at how we call our service methods. As you will see, all these methods are
245asynchronous, so you can call them from the main thread of your app without worrying about freezing
246your UI or the OS killing your app.
Jorge Canizales5436cf12015-06-08 23:06:54 -0700247
248#### Simple RPC
249
Jorge Canizales54f4d532015-07-17 17:53:06 -0700250Calling the simple RPC `GetFeature` is nearly as straightforward as calling any other asynchronous
251method on Cocoa.
Jorge Canizales5436cf12015-06-08 23:06:54 -0700252
253```objective-c
254RTGPoint *point = [RTGPoint message];
255point.latitude = 40E7;
256point.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 Canizales54f4d532015-07-17 17:53:06 -0700267As you can see, we create and populate a request protocol buffer object (in our case `RTGPoint`).
268Then, we call the method on the client object, passing it the request, and a block to handle the
269response (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
271argument. If, instead, some RPC error happens, the handler block is called with a `nil` response
272argument, and we can read the details of the problem from the error argument.
Jorge Canizales5436cf12015-06-08 23:06:54 -0700273
274```objective-c
275NSLog(@"Found feature called %@ at %@.", response.name, response.location);
276```
277
278#### Streaming RPCs
279
Jorge Canizales54f4d532015-07-17 17:53:06 -0700280Now 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 Canizales5436cf12015-06-08 23:06:54 -0700282
283```objective-c
Jorge Canizales54f4d532015-07-17 17:53:06 -0700284[client listFeaturesWithRequest:rectangle
285 eventHandler:^(BOOL done, RTGFeature *response, NSError *error) {
Jorge Canizales5436cf12015-06-08 23:06:54 -0700286 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 Canizales54f4d532015-07-17 17:53:06 -0700297Notice 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
299value set to `YES`. If an error occurs, the RPC finishes and the block is called with the arguments
300`(YES, nil, error)`.
Jorge Canizales5436cf12015-06-08 23:06:54 -0700301
Jorge Canizales54f4d532015-07-17 17:53:06 -0700302The request-streaming method `RecordRoute` expects a stream of `RTGPoint`s from the cient. This
303stream is passed to the method as an object of class `GRXWriter`. The simplest way to create one is
304to initialize one from a `NSArray` object:
Jorge Canizales5436cf12015-06-08 23:06:54 -0700305
306
307```objective-c
Jorge Canizales54f4d532015-07-17 17:53:06 -0700308#import <RxLibrary/GRXWriter+Immediate.h>
Jorge Canizales5436cf12015-06-08 23:06:54 -0700309
310...
311
312RTGPoint *point1 = [RTGPoint message];
313point.latitude = 40E7;
314point.longitude = -74E7;
315
316RTGPoint *point2 = [RTGPoint message];
317point.latitude = 40E7;
318point.longitude = -74E7;
319
320GRXWriter *locationsWriter = [GRXWriter writerWithContainer:@[point1, point2]];
321
Jorge Canizales54f4d532015-07-17 17:53:06 -0700322[client recordRouteWithRequestsWriter:locationsWriter
323 handler:^(RTGRouteSummary *response, NSError *error) {
Jorge Canizales5436cf12015-06-08 23:06:54 -0700324 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 Canizales54f4d532015-07-17 17:53:06 -0700336The `GRXWriter` class is generic enough to allow for asynchronous streams, streams of future values,
337or even infinite streams.
Jorge Canizales5436cf12015-06-08 23:06:54 -0700338
Jorge Canizales54f4d532015-07-17 17:53:06 -0700339Finally, let's look at our bidirectional streaming RPC `RouteChat()`. The way to call a
340bidirectional streaming RPC is just a combination of how to call request-streaming RPCs and
341response-streaming RPCs.
Jorge Canizales5436cf12015-06-08 23:06:54 -0700342
343```objective-c
Jorge Canizales54f4d532015-07-17 17:53:06 -0700344[client routeChatWithRequestsWriter:notesWriter
345 eventHandler:^(BOOL done, RTGRouteNote *note, NSError *error) {
Jorge Canizales5436cf12015-06-08 23:06:54 -0700346 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 Canizales54f4d532015-07-17 17:53:06 -0700357The semantics for the handler block and the `GRXWriter` argument here are exactly the same as for
358our request-streaming and response-streaming methods. Although both client and server will always
359get the other's messages in the order they were written, the two streams operate completely
360independently.