diff options
author | Craig Tiller <ctiller@google.com> | 2015-05-20 10:33:37 -0700 |
---|---|---|
committer | Craig Tiller <ctiller@google.com> | 2015-05-20 10:33:37 -0700 |
commit | bf6bd2303c2693373c4df161e11b6ce93f5d6ae3 (patch) | |
tree | 3196fcfb73f5df28815df87ba90d398809a46e3b /src/objective-c | |
parent | 1e0452b63b335c5f00d35d3774a4b4a60a21b80d (diff) | |
parent | a42c1fe8aeddff6e1fe13b7ee2d990999f2867c6 (diff) |
Merge github.com:grpc/grpc into mmm-mmm-mmm-mmm
Diffstat (limited to 'src/objective-c')
-rw-r--r-- | src/objective-c/GRPCClient/GRPCCall.m | 6 | ||||
-rw-r--r-- | src/objective-c/GRPCClient/private/GRPCDelegateWrapper.h | 17 | ||||
-rw-r--r-- | src/objective-c/GRPCClient/private/GRPCDelegateWrapper.m | 8 | ||||
-rw-r--r-- | src/objective-c/ProtoRPC/ProtoRPC.m | 4 | ||||
-rw-r--r-- | src/objective-c/README.md | 48 | ||||
-rw-r--r-- | src/objective-c/RxLibrary/GRXBufferedPipe.h | 59 | ||||
-rw-r--r-- | src/objective-c/RxLibrary/GRXBufferedPipe.m | 146 | ||||
-rw-r--r-- | src/objective-c/RxLibrary/GRXImmediateWriter.m | 4 | ||||
-rw-r--r-- | src/objective-c/RxLibrary/GRXWriteable.h | 8 | ||||
-rw-r--r-- | src/objective-c/RxLibrary/GRXWriteable.m | 4 | ||||
-rw-r--r-- | src/objective-c/RxLibrary/GRXWriter.h | 6 | ||||
-rw-r--r-- | src/objective-c/RxLibrary/GRXWriter.m | 8 | ||||
-rw-r--r-- | src/objective-c/RxLibrary/transformations/GRXMappingWriter.m | 4 | ||||
-rw-r--r-- | src/objective-c/examples/Sample/SampleTests/RemoteProtoTests.m | 98 |
14 files changed, 371 insertions, 49 deletions
diff --git a/src/objective-c/GRPCClient/GRPCCall.m b/src/objective-c/GRPCClient/GRPCCall.m index 0f458b40cd..a4a0ddb324 100644 --- a/src/objective-c/GRPCClient/GRPCCall.m +++ b/src/objective-c/GRPCClient/GRPCCall.m @@ -231,7 +231,7 @@ handler:resumingHandler]] errorHandler:errorHandler]; } -- (void)didReceiveValue:(id)value { +- (void)writeValue:(id)value { // TODO(jcanizales): Throw/assert if value isn't NSData. // Pause the input and only resume it when the C layer notifies us that writes @@ -255,7 +255,7 @@ errorHandler:errorHandler]; } -- (void)didFinishWithError:(NSError *)errorOrNil { +- (void)writesFinishedWithError:(NSError *)errorOrNil { if (errorOrNil) { [self cancel]; } else { @@ -306,7 +306,7 @@ - (void)startWithWriteable:(id<GRXWriteable>)writeable { // The following produces a retain cycle self:_responseWriteable:self, which is only - // broken when didFinishWithError: is sent to the wrapped writeable. + // broken when writesFinishedWithError: is sent to the wrapped writeable. // Care is taken not to retain self strongly in any of the blocks used in // the implementation of GRPCCall, so that the life of the instance is // determined by this retain cycle. diff --git a/src/objective-c/GRPCClient/private/GRPCDelegateWrapper.h b/src/objective-c/GRPCClient/private/GRPCDelegateWrapper.h index 24c2b96729..1ef245fe37 100644 --- a/src/objective-c/GRPCClient/private/GRPCDelegateWrapper.h +++ b/src/objective-c/GRPCClient/private/GRPCDelegateWrapper.h @@ -38,11 +38,11 @@ // This is a thread-safe wrapper over a GRXWriteable instance. It lets one // enqueue calls to a GRXWriteable instance for the main thread, guaranteeing -// that didFinishWithError: is the last message sent to it (no matter what +// that writesFinishedWithError: is the last message sent to it (no matter what // messages are sent to the wrapper, in what order, nor from which thread). It // also guarantees that, if cancelWithError: is called from the main thread // (e.g. by the app cancelling the writes), no further messages are sent to the -// writeable except didFinishWithError:. +// writeable except writesFinishedWithError:. // // TODO(jcanizales): Let the user specify another queue for the writeable // callbacks. @@ -51,23 +51,22 @@ // The GRXWriteable passed is the wrapped writeable. // Both the GRXWriter instance and the GRXWriteable instance are retained until -// didFinishWithError: is sent to the writeable, and released after that. +// writesFinishedWithError: is sent to the writeable, and released after that. // This is used to create a retain cycle that keeps both objects alive until the // writing is explicitly finished. - (instancetype)initWithWriteable:(id<GRXWriteable>)writeable writer:(id<GRXWriter>)writer NS_DESIGNATED_INITIALIZER; -// Enqueues didReceiveValue: to be sent to the writeable in the main thread. -// The passed handler is invoked from the main thread after didReceiveValue: -// returns. +// Enqueues writeValue: to be sent to the writeable in the main thread. +// The passed handler is invoked from the main thread after writeValue: returns. - (void)enqueueMessage:(NSData *)message completionHandler:(void (^)())handler; -// Enqueues didFinishWithError:nil to be sent to the writeable in the main +// Enqueues writesFinishedWithError:nil to be sent to the writeable in the main // thread. After that message is sent to the writeable, all other methods of // this object are effectively noops. - (void)enqueueSuccessfulCompletion; -// If the writeable has not yet received a didFinishWithError: message, this +// If the writeable has not yet received a writesFinishedWithError: message, this // will enqueue one to be sent to it in the main thread, and cancel all other // pending messages to the writeable enqueued by this object (both past and // future). @@ -75,7 +74,7 @@ - (void)cancelWithError:(NSError *)error; // Cancels all pending messages to the writeable enqueued by this object (both -// past and future). Because the writeable won't receive didFinishWithError:, +// past and future). Because the writeable won't receive writesFinishedWithError:, // this also releases the writeable and the writer. - (void)cancelSilently; @end diff --git a/src/objective-c/GRPCClient/private/GRPCDelegateWrapper.m b/src/objective-c/GRPCClient/private/GRPCDelegateWrapper.m index ac444ef406..7d5ecb56d9 100644 --- a/src/objective-c/GRPCClient/private/GRPCDelegateWrapper.m +++ b/src/objective-c/GRPCClient/private/GRPCDelegateWrapper.m @@ -43,7 +43,7 @@ @implementation GRPCDelegateWrapper { dispatch_queue_t _writeableQueue; - // This ensures that didFinishWithError: is only sent once to the writeable. + // This ensures that writesFinishedWithError: is only sent once to the writeable. dispatch_once_t _alreadyFinished; } @@ -69,7 +69,7 @@ // the race. id<GRXWriteable> writeable = self.writeable; if (writeable) { - [writeable didReceiveValue:message]; + [writeable writeValue:message]; handler(); } }); @@ -80,7 +80,7 @@ dispatch_once(&_alreadyFinished, ^{ // Cancellation is now impossible. None of the other three blocks can run // concurrently with this one. - [self.writeable didFinishWithError:nil]; + [self.writeable writesFinishedWithError:nil]; // Break the retain cycle with writer, and skip any possible message to the // wrapped writeable enqueued after this one. self.writeable = nil; @@ -100,7 +100,7 @@ self.writeable = nil; dispatch_async(_writeableQueue, ^{ - [writeable didFinishWithError:error]; + [writeable writesFinishedWithError:error]; // Break the retain cycle with writer. self.writer = nil; }); diff --git a/src/objective-c/ProtoRPC/ProtoRPC.m b/src/objective-c/ProtoRPC/ProtoRPC.m index c5051c0123..96608f2898 100644 --- a/src/objective-c/ProtoRPC/ProtoRPC.m +++ b/src/objective-c/ProtoRPC/ProtoRPC.m @@ -71,9 +71,9 @@ if ((self = [super initWithHost:host method:method requestsWriter:bytesWriter])) { // A writeable that parses the proto messages received. _responseWriteable = [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) { - [responsesWriteable didReceiveValue:[responseClass parseFromData:value]]; + [responsesWriteable writeValue:[responseClass parseFromData:value]]; } completionHandler:^(NSError *errorOrNil) { - [responsesWriteable didFinishWithError:errorOrNil]; + [responsesWriteable writesFinishedWithError:errorOrNil]; }]; } return self; diff --git a/src/objective-c/README.md b/src/objective-c/README.md index 05e9f2b4dc..cdba3777dd 100644 --- a/src/objective-c/README.md +++ b/src/objective-c/README.md @@ -1,3 +1,47 @@ -gRPC implementation for Objective-C on iOS +# gRPC for Objective-C -This is a work in progress. +## How to generate a client library from a Protocol Buffers definition + +First install v3 of the Protocol Buffers compiler (_protoc_), by cloning [its Git repository](https://github.com/google/protobuf) and following these [installation instructions](https://github.com/google/protobuf#c-installation---unix) (the ones titled C++; don't miss the note for Mac users). + +Then clone this repository and execute the following commands from the root directory where it was cloned. + +Compile the gRPC plugins for _protoc_: +```sh +make plugins +``` + +Create a symbolic link to the compiled plugin binary somewhere in your `$PATH`: +```sh +ln -s `pwd`/bins/opt/grpc_objective_c_plugin /usr/local/bin/protoc-gen-objcgrpc +``` +(Notice that the name of the created link must begin with "protoc-gen-" for _protoc_ to recognize it as a plugin). + +If you don't want to create the symbolic link, you can alternatively copy the binary (with the appropriate name). Or you might prefer instead to specify the plugin's path as a flag when invoking _protoc_, in which case no system modification nor renaming is necessary. + +Finally, run _protoc_ with the following flags to generate the client library for your `.proto` files: + +```sh +protoc --objc_out=. --objcrpc_out=. *.proto +``` + +This will generate a pair of `.pbobjc.h`/`.pbobjc.m` files for each `.proto` file, with the messages and enums defined in them. And a pair of `.pbrpc.h`/`.pbrpc.m` files for each `.proto` file with services defined. The latter contains the code to make remote calls to the specified API. + +## How to integrate a generated gRPC library in your project + +### If you use Cocoapods + +This is the recommended approach. + +You need to create a Podspec file for the generated library. This is simply a matter of copying an example like [this one](https://github.com/grpc/grpc/blob/master/src/objective-c/examples/Sample/RemoteTestClient/RemoteTest.podspec) to the directory where the source files were generated. Update the name and other metadata of the Podspec as suitable. + +Once your library has a Podspec, refer to it from your Podfile using `:path` as described [here](https://guides.cocoapods.org/using/the-podfile.html#using-the-files-from-a-folder-local-to-the-machine). + +### If you don't use Cocoapods + +You need to compile the generated `.pbpbjc.*` files (the enums and messages) without ARC support, and the generated `.pbrpc.*` files (the services) with ARC support. The generated code depends on v0.3+ of the Objective-C gRPC runtime library and v3.0+ of the Objective-C Protobuf runtime library. + +These libraries need to be integrated into your project as described in their respective Podspec files: + +* [Podspec](https://github.com/grpc/grpc/blob/master/gRPC.podspec) for the Objective-C gRPC runtime library. This can be tedious to configure manually. +* [Podspec](https://github.com/jcanizales/protobuf/blob/add-podspec/Protobuf.podspec) for the Objective-C Protobuf runtime library. diff --git a/src/objective-c/RxLibrary/GRXBufferedPipe.h b/src/objective-c/RxLibrary/GRXBufferedPipe.h new file mode 100644 index 0000000000..5e876a73bf --- /dev/null +++ b/src/objective-c/RxLibrary/GRXBufferedPipe.h @@ -0,0 +1,59 @@ +/* + * + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#import <Foundation/Foundation.h> + +#import "GRXWriteable.h" +#import "GRXWriter.h" + +// A buffered pipe is a Writeable that also acts as a Writer (to whichever other writeable is passed +// to -startWithWriteable:). +// Once it is started, whatever values are written into it (via -writeValue:) will be propagated +// immediately, unless flow control prevents it. +// If it is throttled and keeps receiving values, as well as if it receives values before being +// started, it will buffer them and propagate them in order as soon as its state becomes +// GRXWriterStateStarted. +// If it receives an error (via -writesFinishedWithError:), it will drop any buffered values and +// propagate the error immediately. +// +// Beware that a pipe of this type can't prevent receiving more values when it is paused (for +// example if used to write data to a congested network connection). Because in such situations the +// pipe will keep buffering all data written to it, your application could run out of memory and +// crash. If you want to react to flow control signals to prevent that, instead of using this class +// you can implement an object that conforms to GRXWriter. +@interface GRXBufferedPipe : NSObject<GRXWriteable, GRXWriter> + +// Convenience constructor. ++ (instancetype)pipe; + +@end diff --git a/src/objective-c/RxLibrary/GRXBufferedPipe.m b/src/objective-c/RxLibrary/GRXBufferedPipe.m new file mode 100644 index 0000000000..4820c84af0 --- /dev/null +++ b/src/objective-c/RxLibrary/GRXBufferedPipe.m @@ -0,0 +1,146 @@ +/* + * + * Copyright 2015, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#import "GRXBufferedPipe.h" + +@implementation GRXBufferedPipe { + id<GRXWriteable> _writeable; + NSMutableArray *_queue; + BOOL _inputIsFinished; + NSError *_errorOrNil; +} + +@synthesize state = _state; + ++ (instancetype)pipe { + return [[self alloc] init]; +} + +- (instancetype)init { + if (self = [super init]) { + _queue = [NSMutableArray array]; + _state = GRXWriterStateNotStarted; + } + return self; +} + +- (id)popValue { + id value = _queue[0]; + [_queue removeObjectAtIndex:0]; + return value; +} + +- (void)writeBufferUntilPausedOrStopped { + while (_state == GRXWriterStateStarted && _queue.count > 0) { + [_writeable writeValue:[self popValue]]; + } + if (_inputIsFinished && _queue.count == 0) { + // Our writer finished normally while we were paused or not-started-yet. + [self finishWithError:_errorOrNil]; + } +} + +#pragma mark GRXWriteable implementation + +// Returns whether events can be simply propagated to the other end of the pipe. +- (BOOL)shouldFastForward { + return _state == GRXWriterStateStarted && _queue.count == 0; +} + +- (void)writeValue:(id)value { + if (self.shouldFastForward) { + // Skip the queue. + [_writeable writeValue:value]; + } else { + // Even if we're paused and with enqueued values, we can't excert back-pressure to our writer. + // So just buffer the new value. + // We need a copy, so that it doesn't mutate before it's written at the other end of the pipe. + if ([value respondsToSelector:@selector(copy)]) { + value = [value copy]; + } + [_queue addObject:value]; + } +} + +- (void)writesFinishedWithError:(NSError *)errorOrNil { + _inputIsFinished = YES; + _errorOrNil = errorOrNil; + if (errorOrNil || self.shouldFastForward) { + // No need to write pending values. + [self finishWithError:_errorOrNil]; + } +} + +#pragma mark GRXWriter implementation + +- (void)setState:(GRXWriterState)newState { + // Manual transitions are only allowed from the started or paused states. + if (_state == GRXWriterStateNotStarted || _state == GRXWriterStateFinished) { + return; + } + + switch (newState) { + case GRXWriterStateFinished: + _state = newState; + _queue = nil; + // Per GRXWriter's contract, setting the state to Finished manually means one doesn't wish the + // writeable to be messaged anymore. + _writeable = nil; + return; + case GRXWriterStatePaused: + _state = newState; + return; + case GRXWriterStateStarted: + if (_state == GRXWriterStatePaused) { + _state = newState; + [self writeBufferUntilPausedOrStopped]; + } + return; + case GRXWriterStateNotStarted: + return; + } +} + +- (void)startWithWriteable:(id<GRXWriteable>)writeable { + _state = GRXWriterStateStarted; + _writeable = writeable; + [self writeBufferUntilPausedOrStopped]; +} + +- (void)finishWithError:(NSError *)errorOrNil { + id<GRXWriteable> writeable = _writeable; + self.state = GRXWriterStateFinished; + [writeable writesFinishedWithError:errorOrNil]; +} + +@end diff --git a/src/objective-c/RxLibrary/GRXImmediateWriter.m b/src/objective-c/RxLibrary/GRXImmediateWriter.m index 7468af557f..0b4979872e 100644 --- a/src/objective-c/RxLibrary/GRXImmediateWriter.m +++ b/src/objective-c/RxLibrary/GRXImmediateWriter.m @@ -109,7 +109,7 @@ - (void)writeUntilPausedOrStopped { id value; while (value = [_enumerator nextObject]) { - [_writeable didReceiveValue:value]; + [_writeable writeValue:value]; // If the writeable has a reference to us, it might change our state to paused or finished. if (_state == GRXWriterStatePaused || _state == GRXWriterStateFinished) { return; @@ -130,7 +130,7 @@ _errorOrNil = nil; id<GRXWriteable> writeable = _writeable; _writeable = nil; - [writeable didFinishWithError:errorOrNil]; + [writeable writesFinishedWithError:errorOrNil]; } - (void)setState:(GRXWriterState)newState { diff --git a/src/objective-c/RxLibrary/GRXWriteable.h b/src/objective-c/RxLibrary/GRXWriteable.h index 6f6ea142e0..216de30735 100644 --- a/src/objective-c/RxLibrary/GRXWriteable.h +++ b/src/objective-c/RxLibrary/GRXWriteable.h @@ -38,14 +38,12 @@ @protocol GRXWriteable <NSObject> // Push the next value of the sequence to the receiving object. -// TODO(jcanizales): Name it enumerator:(id<GRXEnumerator>) didProduceValue:(id)? -- (void)didReceiveValue:(id)value; +- (void)writeValue:(id)value; // Signal that the sequence is completed, or that an error ocurred. After this -// message is sent to the instance, neither it nor didReceiveValue: may be +// message is sent to the instance, neither it nor writeValue: may be // called again. -// TODO(jcanizales): enumerator:(id<GRXEnumerator>) didFinishWithError:(NSError*)? -- (void)didFinishWithError:(NSError *)errorOrNil; +- (void)writesFinishedWithError:(NSError *)errorOrNil; @end typedef void (^GRXValueHandler)(id value); diff --git a/src/objective-c/RxLibrary/GRXWriteable.m b/src/objective-c/RxLibrary/GRXWriteable.m index 7000a078d1..63f7c3e7f3 100644 --- a/src/objective-c/RxLibrary/GRXWriteable.m +++ b/src/objective-c/RxLibrary/GRXWriteable.m @@ -76,13 +76,13 @@ return self; } -- (void)didReceiveValue:(id)value { +- (void)writeValue:(id)value { if (_valueHandler) { _valueHandler(value); } } -- (void)didFinishWithError:(NSError *)errorOrNil { +- (void)writesFinishedWithError:(NSError *)errorOrNil { if (_completionHandler) { _completionHandler(errorOrNil); } diff --git a/src/objective-c/RxLibrary/GRXWriter.h b/src/objective-c/RxLibrary/GRXWriter.h index 68c294f007..dcf44e3143 100644 --- a/src/objective-c/RxLibrary/GRXWriter.h +++ b/src/objective-c/RxLibrary/GRXWriter.h @@ -50,7 +50,7 @@ typedef NS_ENUM(NSInteger, GRXWriterState) { // The writer is temporarily paused, and won't send any more values to the // writeable unless its state is set back to Started. The writer might still // transition to the Finished state at any moment, and is allowed to send - // didFinishWithError: to its writeable. + // writesFinishedWithError: to its writeable. // // Not all implementations of writer have to support pausing, and thus // trying to set an writer's state to this value might have no effect. @@ -59,7 +59,7 @@ typedef NS_ENUM(NSInteger, GRXWriterState) { // The writer has released its writeable and won't interact with it anymore. // // One seldomly wants to set an writer's state to this value, as its - // writeable isn't notified with a didFinishWithError: message. Instead, sending + // writeable isn't notified with a writesFinishedWithError: message. Instead, sending // finishWithError: to the writer will make it notify the writeable and then // transition to this state. GRXWriterStateFinished @@ -105,7 +105,7 @@ typedef NS_ENUM(NSInteger, GRXWriterState) { // This method might only be called on writers in the NotStarted state. - (void)startWithWriteable:(id<GRXWriteable>)writeable; -// Send didFinishWithError:errorOrNil immediately to the writeable, and don't send +// Send writesFinishedWithError:errorOrNil immediately to the writeable, and don't send // any more messages to it. // // This method might only be called on writers in the Started or Paused diff --git a/src/objective-c/RxLibrary/GRXWriter.m b/src/objective-c/RxLibrary/GRXWriter.m index b48a44f3a7..cc14383560 100644 --- a/src/objective-c/RxLibrary/GRXWriter.m +++ b/src/objective-c/RxLibrary/GRXWriter.m @@ -62,7 +62,7 @@ - (void)finishOutputWithError:(NSError *)errorOrNil { id<GRXWriteable> writeable = _writeable; _writeable = nil; - [writeable didFinishWithError:errorOrNil]; + [writeable writesFinishedWithError:errorOrNil]; } // This is used to stop the input writer. It nillifies our reference to it @@ -75,11 +75,11 @@ #pragma mark GRXWriteable implementation -- (void)didReceiveValue:(id)value { - [_writeable didReceiveValue:value]; +- (void)writeValue:(id)value { + [_writeable writeValue:value]; } -- (void)didFinishWithError:(NSError *)errorOrNil { +- (void)writesFinishedWithError:(NSError *)errorOrNil { _writer = nil; [self finishOutputWithError:errorOrNil]; } diff --git a/src/objective-c/RxLibrary/transformations/GRXMappingWriter.m b/src/objective-c/RxLibrary/transformations/GRXMappingWriter.m index 8a41c819a6..2cdfea1b67 100644 --- a/src/objective-c/RxLibrary/transformations/GRXMappingWriter.m +++ b/src/objective-c/RxLibrary/transformations/GRXMappingWriter.m @@ -57,7 +57,7 @@ static id (^kIdentity)(id value) = ^id(id value) { } // Override -- (void)didReceiveValue:(id)value { - [super didReceiveValue:_map(value)]; +- (void)writeValue:(id)value { + [super writeValue:_map(value)]; } @end diff --git a/src/objective-c/examples/Sample/SampleTests/RemoteProtoTests.m b/src/objective-c/examples/Sample/SampleTests/RemoteProtoTests.m index f489229834..7e063fddb4 100644 --- a/src/objective-c/examples/Sample/SampleTests/RemoteProtoTests.m +++ b/src/objective-c/examples/Sample/SampleTests/RemoteProtoTests.m @@ -36,13 +36,46 @@ #import <UIKit/UIKit.h> #import <XCTest/XCTest.h> -#import <gRPC/ProtoRPC.h> #import <gRPC/GRXWriter+Immediate.h> +#import <gRPC/GRXBufferedPipe.h> +#import <gRPC/ProtoRPC.h> #import <RemoteTest/Empty.pbobjc.h> #import <RemoteTest/Messages.pbobjc.h> #import <RemoteTest/Test.pbobjc.h> #import <RemoteTest/Test.pbrpc.h> +// Convenience constructors for the generated proto messages: + +@interface RMTStreamingOutputCallRequest (Constructors) ++ (instancetype)messageWithPayloadSize:(NSNumber *)payloadSize + requestedResponseSize:(NSNumber *)responseSize; +@end + +@implementation RMTStreamingOutputCallRequest (Constructors) ++ (instancetype)messageWithPayloadSize:(NSNumber *)payloadSize + requestedResponseSize:(NSNumber *)responseSize { + RMTStreamingOutputCallRequest *request = [self message]; + RMTResponseParameters *parameters = [RMTResponseParameters message]; + parameters.size = responseSize.integerValue; + [request.responseParametersArray addObject:parameters]; + request.payload.body = [NSMutableData dataWithLength:payloadSize.unsignedIntegerValue]; + return request; +} +@end + +@interface RMTStreamingOutputCallResponse (Constructors) ++ (instancetype)messageWithPayloadSize:(NSNumber *)payloadSize; +@end + +@implementation RMTStreamingOutputCallResponse (Constructors) ++ (instancetype)messageWithPayloadSize:(NSNumber *)payloadSize { + RMTStreamingOutputCallResponse * response = [self message]; + response.payload.type = RMTPayloadType_Compressable; + response.payload.body = [NSMutableData dataWithLength:payloadSize.unsignedIntegerValue]; + return response; +} +@end + @interface RemoteProtoTests : XCTestCase @end @@ -70,7 +103,7 @@ [expectation fulfill]; }]; - [self waitForExpectationsWithTimeout:2. handler:nil]; + [self waitForExpectationsWithTimeout:2 handler:nil]; } - (void)testLargeUnaryRPC { @@ -92,7 +125,7 @@ [expectation fulfill]; }]; - [self waitForExpectationsWithTimeout:4. handler:nil]; + [self waitForExpectationsWithTimeout:4 handler:nil]; } - (void)testClientStreamingRPC { @@ -124,7 +157,7 @@ [expectation fulfill]; }]; - [self waitForExpectationsWithTimeout:4. handler:nil]; + [self waitForExpectationsWithTimeout:4 handler:nil]; } - (void)testServerStreamingRPC { @@ -149,10 +182,7 @@ if (response) { XCTAssertLessThan(index, 4, @"More than 4 responses received."); - RMTStreamingOutputCallResponse * expected = [RMTStreamingOutputCallResponse message]; - expected.payload.type = RMTPayloadType_Compressable; - int expectedSize = [expectedSizes[index] unsignedIntegerValue]; - expected.payload.body = [NSMutableData dataWithLength:expectedSize]; + id expected = [RMTStreamingOutputCallResponse messageWithPayloadSize:expectedSizes[index]]; XCTAssertEqualObjects(response, expected); index += 1; } @@ -166,6 +196,49 @@ [self waitForExpectationsWithTimeout:4 handler:nil]; } +- (void)testPingPongRPC { + __weak XCTestExpectation *expectation = [self expectationWithDescription:@"PingPong"]; + + NSArray *requests = @[@27182, @8, @1828, @45904]; + NSArray *responses = @[@31415, @9, @2653, @58979]; + + GRXBufferedPipe *requestsBuffer = [[GRXBufferedPipe alloc] init]; + + __block int index = 0; + + id request = [RMTStreamingOutputCallRequest messageWithPayloadSize:requests[index] + requestedResponseSize:responses[index]]; + [requestsBuffer writeValue:request]; + + [_service fullDuplexCallWithRequestsWriter:requestsBuffer + handler:^(BOOL done, + RMTStreamingOutputCallResponse *response, + NSError *error) { + XCTAssertNil(error, @"Finished with unexpected error: %@", error); + XCTAssertTrue(done || response, @"Event handler called without an event."); + + if (response) { + XCTAssertLessThan(index, 4, @"More than 4 responses received."); + id expected = [RMTStreamingOutputCallResponse messageWithPayloadSize:responses[index]]; + XCTAssertEqualObjects(response, expected); + index += 1; + if (index < 4) { + id request = [RMTStreamingOutputCallRequest messageWithPayloadSize:requests[index] + requestedResponseSize:responses[index]]; + [requestsBuffer writeValue:request]; + } else { + [requestsBuffer writesFinishedWithError:nil]; + } + } + + if (done) { + XCTAssertEqual(index, 4, @"Received %i responses instead of 4.", index); + [expectation fulfill]; + } + }]; + [self waitForExpectationsWithTimeout:2 handler:nil]; +} + - (void)testEmptyStreamRPC { __weak XCTestExpectation *expectation = [self expectationWithDescription:@"EmptyStream"]; [_service fullDuplexCallWithRequestsWriter:[GRXWriter emptyWriter] @@ -176,13 +249,16 @@ XCTAssert(done, @"Unexpected response: %@", response); [expectation fulfill]; }]; - [self waitForExpectationsWithTimeout:4 handler:nil]; + [self waitForExpectationsWithTimeout:2 handler:nil]; } - (void)testCancelAfterBeginRPC { __weak XCTestExpectation *expectation = [self expectationWithDescription:@"CancelAfterBegin"]; - // TODO(mlumish): change to writing that blocks instead of writing - ProtoRPC *call = [_service RPCToStreamingInputCallWithRequestsWriter:[GRXWriter emptyWriter] + + // A buffered pipe to which we never write any value acts as a writer that just hangs. + GRXBufferedPipe *requestsBuffer = [[GRXBufferedPipe alloc] init]; + + ProtoRPC *call = [_service RPCToStreamingInputCallWithRequestsWriter:requestsBuffer handler:^(RMTStreamingInputCallResponse *response, NSError *error) { XCTAssertEqual(error.code, GRPC_STATUS_CANCELLED); |