diff options
Diffstat (limited to 'src/objective-c/GRPCClient')
-rw-r--r-- | src/objective-c/GRPCClient/GRPCCall.m | 157 | ||||
-rw-r--r-- | src/objective-c/GRPCClient/private/GRPCCompletionQueue.h | 8 | ||||
-rw-r--r-- | src/objective-c/GRPCClient/private/GRPCCompletionQueue.m | 27 | ||||
-rw-r--r-- | src/objective-c/GRPCClient/private/GRPCWrappedCall.h | 97 | ||||
-rw-r--r-- | src/objective-c/GRPCClient/private/GRPCWrappedCall.m | 326 | ||||
-rw-r--r-- | src/objective-c/GRPCClient/private/NSData+GRPC.m | 3 | ||||
-rw-r--r-- | src/objective-c/GRPCClient/private/NSDictionary+GRPC.h | 4 | ||||
-rw-r--r-- | src/objective-c/GRPCClient/private/NSDictionary+GRPC.m | 22 | ||||
-rw-r--r-- | src/objective-c/GRPCClient/private/NSError+GRPC.h | 12 |
9 files changed, 514 insertions, 142 deletions
diff --git a/src/objective-c/GRPCClient/GRPCCall.m b/src/objective-c/GRPCClient/GRPCCall.m index 989ce390e2..0f458b40cd 100644 --- a/src/objective-c/GRPCClient/GRPCCall.m +++ b/src/objective-c/GRPCClient/GRPCCall.m @@ -41,23 +41,11 @@ #import "private/GRPCCompletionQueue.h" #import "private/GRPCDelegateWrapper.h" #import "private/GRPCMethodName+HTTP2Encoding.h" +#import "private/GRPCWrappedCall.h" #import "private/NSData+GRPC.h" #import "private/NSDictionary+GRPC.h" #import "private/NSError+GRPC.h" -// A grpc_call_error represents a precondition failure when invoking the -// grpc_call_* functions. If one ever happens, it's a bug in this library. -// -// TODO(jcanizales): Can an application shut down gracefully when a thread other -// than the main one throws an exception? -static void AssertNoErrorInCall(grpc_call_error error) { - if (error != GRPC_CALL_OK) { - @throw [NSException exceptionWithName:NSInternalInconsistencyException - reason:@"Precondition of grpc_call_* not met." - userInfo:nil]; - } -} - @interface GRPCCall () <GRXWriteable> // Makes it readwrite. @property(atomic, strong) NSDictionary *responseMetadata; @@ -65,34 +53,24 @@ static void AssertNoErrorInCall(grpc_call_error error) { // The following methods of a C gRPC call object aren't reentrant, and thus // calls to them must be serialized: -// - add_metadata -// - invoke -// - start_write -// - writes_done -// - start_read +// - start_batch // - destroy -// The first four are called as part of responding to client commands, but -// start_read we want to call as soon as we're notified that the RPC was -// successfully established (which happens concurrently in the network queue). -// Serialization is achieved by using a private serial queue to operate the -// call object. -// Because add_metadata and invoke are called and return successfully before -// any of the other methods is called, they don't need to use the queue. // -// Furthermore, start_write and writes_done can only be called after the -// WRITE_ACCEPTED event for any previous write is received. This is achieved by +// start_batch with a SEND_MESSAGE argument can only be called after the +// OP_COMPLETE event for any previous write is received. This is achieved by // pausing the requests writer immediately every time it writes a value, and -// resuming it again when WRITE_ACCEPTED is received. +// resuming it again when OP_COMPLETE is received. // -// Similarly, start_read can only be called after the READ event for any -// previous read is received. This is easier to enforce, as we're writing the -// received messages into the writeable: start_read is enqueued once upon receiving -// the CLIENT_METADATA_READ event, and then once after receiving each READ -// event. +// Similarly, start_batch with a RECV_MESSAGE argument can only be called after +// the OP_COMPLETE event for any previous read is received.This is easier to +// enforce, as we're writing the received messages into the writeable: +// start_batch is enqueued once upon receiving the OP_COMPLETE event for the +// RECV_METADATA batch, and then once after receiving each OP_COMPLETE event for +// each RECV_MESSAGE batch. @implementation GRPCCall { dispatch_queue_t _callQueue; - grpc_call *_gRPCCall; + GRPCWrappedCall *_wrappedCall; dispatch_once_t _callAlreadyInvoked; GRPCChannel *_channel; @@ -129,10 +107,10 @@ static void AssertNoErrorInCall(grpc_call_error error) { _completionQueue = [GRPCCompletionQueue completionQueue]; _channel = [GRPCChannel channelToHost:host]; - _gRPCCall = grpc_channel_create_call_old(_channel.unmanagedChannel, - method.HTTP2Path.UTF8String, - host.UTF8String, - gpr_inf_future); + + _wrappedCall = [[GRPCWrappedCall alloc] initWithChannel:_channel + method:method.HTTP2Path + host:host]; // Serial queue to invoke the non-reentrant methods of the grpc_call object. _callQueue = dispatch_queue_create("org.grpc.call", NULL); @@ -156,7 +134,7 @@ static void AssertNoErrorInCall(grpc_call_error error) { - (void)cancelCall { // Can be called from any thread, any number of times. - AssertNoErrorInCall(grpc_call_cancel(_gRPCCall)); + [_wrappedCall cancel]; } - (void)cancel { @@ -167,9 +145,9 @@ static void AssertNoErrorInCall(grpc_call_error error) { } - (void)dealloc { - grpc_call *gRPCCall = _gRPCCall; + __block GRPCWrappedCall *wrappedCall = _wrappedCall; dispatch_async(_callQueue, ^{ - grpc_call_destroy(gRPCCall); + wrappedCall = nil; }); } @@ -177,8 +155,9 @@ static void AssertNoErrorInCall(grpc_call_error error) { // Only called from the call queue. // The handler will be called from the network queue. -- (void)startReadWithHandler:(GRPCEventHandler)handler { - AssertNoErrorInCall(grpc_call_start_read_old(_gRPCCall, (__bridge_retained void *)handler)); +- (void)startReadWithHandler:(void(^)(grpc_byte_buffer *))handler { + // TODO(jcanizales): Add error handlers for async failures + [_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvMessage alloc] initWithHandler:handler]]]; } // Called initially from the network queue once response headers are received, @@ -195,12 +174,13 @@ static void AssertNoErrorInCall(grpc_call_error error) { __weak GRPCDelegateWrapper *weakWriteable = _responseWriteable; dispatch_async(_callQueue, ^{ - [weakSelf startReadWithHandler:^(grpc_event *event) { - if (!event->data.read) { - // No more responses from the server. + [weakSelf startReadWithHandler:^(grpc_byte_buffer *message) { + if (message == NULL) { + // No more messages from the server return; } - NSData *data = [NSData grpc_dataWithByteBuffer:event->data.read]; + NSData *data = [NSData grpc_dataWithByteBuffer:message]; + grpc_byte_buffer_destroy(message); if (!data) { // The app doesn't have enough memory to hold the server response. We // don't want to throw, because the app shouldn't crash for a behavior @@ -225,35 +205,11 @@ static void AssertNoErrorInCall(grpc_call_error error) { #pragma mark Send headers -- (void)addHeaderWithName:(NSString *)name binaryValue:(NSData *)value { - grpc_metadata metadata; - // Safe to discard const qualifiers; we're not going to modify the contents. - metadata.key = (char *)name.UTF8String; - metadata.value = (char *)value.bytes; - metadata.value_length = value.length; - grpc_call_add_metadata_old(_gRPCCall, &metadata, 0); -} - -- (void)addHeaderWithName:(NSString *)name ASCIIValue:(NSString *)value { - grpc_metadata metadata; - // Safe to discard const qualifiers; we're not going to modify the contents. - metadata.key = (char *)name.UTF8String; - metadata.value = (char *)value.UTF8String; - // The trailing \0 isn't encoded in HTTP2. - metadata.value_length = value.length; - grpc_call_add_metadata_old(_gRPCCall, &metadata, 0); -} - // TODO(jcanizales): Rename to commitHeaders. - (void)sendHeaders:(NSDictionary *)metadata { - for (NSString *name in metadata) { - id value = metadata[name]; - if ([value isKindOfClass:[NSData class]]) { - [self addHeaderWithName:name binaryValue:value]; - } else if ([value isKindOfClass:[NSString class]]) { - [self addHeaderWithName:name ASCIIValue:value]; - } - } + // TODO(jcanizales): Add error handlers for async failures + [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendMetadata alloc] + initWithMetadata:metadata ?: @{} handler:nil]]]; } #pragma mark GRXWriteable implementation @@ -263,24 +219,16 @@ static void AssertNoErrorInCall(grpc_call_error error) { - (void)writeMessage:(NSData *)message withErrorHandler:(void (^)())errorHandler { __weak GRPCCall *weakSelf = self; - GRPCEventHandler resumingHandler = ^(grpc_event *event) { - if (event->data.write_accepted != GRPC_OP_OK) { - errorHandler(); - } - // Resume the request writer (even in the case of error). - // TODO(jcanizales): No need to do it in the case of errors anymore? + void(^resumingHandler)(void) = ^{ + // Resume the request writer. GRPCCall *strongSelf = weakSelf; if (strongSelf) { strongSelf->_requestWriter.state = GRXWriterStateStarted; } }; - - grpc_byte_buffer *buffer = message.grpc_byteBuffer; - AssertNoErrorInCall(grpc_call_start_write_old(_gRPCCall, - buffer, - (__bridge_retained void *)resumingHandler, - 0)); - grpc_byte_buffer_destroy(buffer); + [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendMessage alloc] + initWithMessage:message + handler:resumingHandler]] errorHandler:errorHandler]; } - (void)didReceiveValue:(id)value { @@ -303,12 +251,8 @@ static void AssertNoErrorInCall(grpc_call_error error) { // Only called from the call queue. The error handler will be called from the // network queue if the requests stream couldn't be closed successfully. - (void)finishRequestWithErrorHandler:(void (^)())errorHandler { - GRPCEventHandler handler = ^(grpc_event *event) { - if (event->data.finish_accepted != GRPC_OP_OK) { - errorHandler(); - } - }; - AssertNoErrorInCall(grpc_call_writes_done_old(_gRPCCall, (__bridge_retained void *)handler)); + [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendClose alloc] init]] + errorHandler:errorHandler]; } - (void)didFinishWithError:(NSError *)errorOrNil { @@ -332,32 +276,27 @@ static void AssertNoErrorInCall(grpc_call_error error) { // after this. // The first one (metadataHandler), when the response headers are received. // The second one (completionHandler), whenever the RPC finishes for any reason. -- (void)invokeCallWithMetadataHandler:(GRPCEventHandler)metadataHandler - completionHandler:(GRPCEventHandler)completionHandler { - AssertNoErrorInCall(grpc_call_invoke_old(_gRPCCall, - _completionQueue.unmanagedQueue, - (__bridge_retained void *)metadataHandler, - (__bridge_retained void *)completionHandler, - 0)); +- (void)invokeCallWithMetadataHandler:(void(^)(NSDictionary *))metadataHandler + completionHandler:(void(^)(NSError *))completionHandler { + // TODO(jcanizales): Add error handlers for async failures + [_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvMetadata alloc] + initWithHandler:metadataHandler]]]; + [_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvStatus alloc] + initWithHandler:completionHandler]]]; } - (void)invokeCall { __weak GRPCCall *weakSelf = self; - [self invokeCallWithMetadataHandler:^(grpc_event *event) { + [self invokeCallWithMetadataHandler:^(NSDictionary *metadata) { // Response metadata received. - // TODO(jcanizales): Name the type of event->data.client_metadata_read - // in the C library so one can actually pass the object to a method. - grpc_metadata *entries = event->data.client_metadata_read.elements; - size_t count = event->data.client_metadata_read.count; GRPCCall *strongSelf = weakSelf; if (strongSelf) { - strongSelf.responseMetadata = [NSDictionary grpc_dictionaryFromMetadata:entries - count:count]; + strongSelf.responseMetadata = metadata; [strongSelf startNextRead]; } - } completionHandler:^(grpc_event *event) { + } completionHandler:^(NSError *error) { // TODO(jcanizales): Merge HTTP2 trailers into response metadata. - [weakSelf finishWithError:[NSError grpc_errorFromStatus:&event->data.finished]]; + [weakSelf finishWithError:error]; }]; // Now that the RPC has been initiated, request writes can start. [_requestWriter startWithWriteable:self]; diff --git a/src/objective-c/GRPCClient/private/GRPCCompletionQueue.h b/src/objective-c/GRPCClient/private/GRPCCompletionQueue.h index c85d57c0ea..25ca9bd119 100644 --- a/src/objective-c/GRPCClient/private/GRPCCompletionQueue.h +++ b/src/objective-c/GRPCClient/private/GRPCCompletionQueue.h @@ -32,11 +32,9 @@ */ #import <Foundation/Foundation.h> +#include <grpc/grpc.h> -struct grpc_completion_queue; -struct grpc_event; - -typedef void(^GRPCEventHandler)(struct grpc_event *event); +typedef void(^GRPCQueueCompletionHandler)(grpc_op_error error); // This class lets one more easily use grpc_completion_queue. To use it, pass // the value of the unmanagedQueue property of an instance of this class to @@ -48,7 +46,7 @@ typedef void(^GRPCEventHandler)(struct grpc_event *event); // Release the GRPCCompletionQueue object only after you are not going to pass // any more blocks to the grpc_call that's using it. @interface GRPCCompletionQueue : NSObject -@property(nonatomic, readonly) struct grpc_completion_queue *unmanagedQueue; +@property(nonatomic, readonly) grpc_completion_queue *unmanagedQueue; + (instancetype)completionQueue; @end diff --git a/src/objective-c/GRPCClient/private/GRPCCompletionQueue.m b/src/objective-c/GRPCClient/private/GRPCCompletionQueue.m index b98e8ea439..a0a10164b1 100644 --- a/src/objective-c/GRPCClient/private/GRPCCompletionQueue.m +++ b/src/objective-c/GRPCClient/private/GRPCCompletionQueue.m @@ -66,30 +66,21 @@ while (YES) { // The following call blocks until an event is available. grpc_event *event = grpc_completion_queue_next(unmanagedQueue, gpr_inf_future); + GRPCQueueCompletionHandler handler; switch (event->type) { - case GRPC_WRITE_ACCEPTED: - case GRPC_FINISH_ACCEPTED: - case GRPC_CLIENT_METADATA_READ: - case GRPC_READ: - case GRPC_FINISHED: - if (event->tag) { - GRPCEventHandler handler = (__bridge_transfer GRPCEventHandler) event->tag; - handler(event); - } + case GRPC_OP_COMPLETE: + handler = (__bridge_transfer GRPCQueueCompletionHandler)event->tag; + handler(event->data.op_complete); grpc_event_finish(event); - continue; + break; case GRPC_QUEUE_SHUTDOWN: - grpc_completion_queue_destroy(unmanagedQueue); grpc_event_finish(event); + grpc_completion_queue_destroy(unmanagedQueue); return; - case GRPC_SERVER_RPC_NEW: - NSAssert(NO, @"C gRPC library produced a server-only event."); - continue; + default: + grpc_event_finish(event); + [NSException raise:@"Unrecognized completion type" format:@""]; } - // This means the C gRPC library produced an event that wasn't known - // when this library was written. To preserve evolvability, ignore the - // unknown event on release builds. - NSAssert(NO, @"C gRPC library produced an unknown event."); }; }); } diff --git a/src/objective-c/GRPCClient/private/GRPCWrappedCall.h b/src/objective-c/GRPCClient/private/GRPCWrappedCall.h new file mode 100644 index 0000000000..91cd703faf --- /dev/null +++ b/src/objective-c/GRPCClient/private/GRPCWrappedCall.h @@ -0,0 +1,97 @@ +/* + * + * 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> +#include <grpc/grpc.h> +#import "GRPCChannel.h" + +typedef void(^GRPCCompletionHandler)(NSDictionary *); + +@protocol GRPCOp <NSObject> + +- (void)getOp:(grpc_op *)op; + +- (void)finish; + +@end + +@interface GRPCOpSendMetadata : NSObject <GRPCOp> + +- (instancetype)initWithMetadata:(NSDictionary *)metadata + handler:(void(^)(void))handler NS_DESIGNATED_INITIALIZER; + +@end + +@interface GRPCOpSendMessage : NSObject <GRPCOp> + +- (instancetype)initWithMessage:(NSData *)message + handler:(void(^)(void))handler NS_DESIGNATED_INITIALIZER; + +@end + +@interface GRPCOpSendClose : NSObject <GRPCOp> + +- (instancetype)initWithHandler:(void(^)(void))handler NS_DESIGNATED_INITIALIZER; + +@end + +@interface GRPCOpRecvMetadata : NSObject <GRPCOp> + +- (instancetype)initWithHandler:(void(^)(NSDictionary *))handler NS_DESIGNATED_INITIALIZER; + +@end + +@interface GRPCOpRecvMessage : NSObject <GRPCOp> + +- (instancetype)initWithHandler:(void(^)(grpc_byte_buffer *))handler NS_DESIGNATED_INITIALIZER; + +@end + +@interface GRPCOpRecvStatus : NSObject <GRPCOp> + +- (instancetype)initWithHandler:(void(^)(NSError *))handler NS_DESIGNATED_INITIALIZER; + +@end + +@interface GRPCWrappedCall : NSObject + +- (instancetype)initWithChannel:(GRPCChannel *)channel + method:(NSString *)method + host:(NSString *)host NS_DESIGNATED_INITIALIZER; + +- (void)startBatchWithOperations:(NSArray *)ops errorHandler:(void(^)())errorHandler; + +- (void)startBatchWithOperations:(NSArray *)ops; + +- (void)cancel; +@end diff --git a/src/objective-c/GRPCClient/private/GRPCWrappedCall.m b/src/objective-c/GRPCClient/private/GRPCWrappedCall.m new file mode 100644 index 0000000000..41ec1a18b6 --- /dev/null +++ b/src/objective-c/GRPCClient/private/GRPCWrappedCall.m @@ -0,0 +1,326 @@ +/* + * + * 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 "GRPCWrappedCall.h" +#import <Foundation/Foundation.h> +#include <grpc/grpc.h> +#include <grpc/byte_buffer.h> +#include <grpc/support/alloc.h> +#import "GRPCCompletionQueue.h" +#import "NSDictionary+GRPC.h" +#import "NSData+GRPC.h" +#import "NSError+GRPC.h" + +@implementation GRPCOpSendMetadata{ + void(^_handler)(void); + grpc_metadata *_sendMetadata; + size_t _count; +} + +- (instancetype)init { + return [self initWithMetadata:nil handler:nil]; +} + +- (instancetype)initWithMetadata:(NSDictionary *)metadata handler:(void (^)(void))handler { + if (self = [super init]) { + _sendMetadata = [metadata grpc_metadataArray]; + _count = metadata.count; + _handler = handler; + } + return self; +} + +- (void)getOp:(grpc_op *)op { + op->op = GRPC_OP_SEND_INITIAL_METADATA; + op->data.send_initial_metadata.count = _count; + op->data.send_initial_metadata.metadata = _sendMetadata; +} + +- (void)finish { + if (_handler) { + _handler(); + } +} + +- (void)dealloc { + gpr_free(_sendMetadata); +} + +@end + +@implementation GRPCOpSendMessage{ + void(^_handler)(void); + grpc_byte_buffer *_byteBuffer; +} + +- (instancetype)init { + return [self initWithMessage:nil handler:nil]; +} + +- (instancetype)initWithMessage:(NSData *)message handler:(void (^)(void))handler { + if (!message) { + [NSException raise:NSInvalidArgumentException format:@"message cannot be nil"]; + } + if (self = [super init]) { + _byteBuffer = [message grpc_byteBuffer]; + _handler = handler; + } + return self; +} + +- (void)getOp:(grpc_op *)op { + op->op = GRPC_OP_SEND_MESSAGE; + op->data.send_message = _byteBuffer; +} + +- (void)finish { + if (_handler) { + _handler(); + } +} + +- (void)dealloc { + gpr_free(_byteBuffer); +} + +@end + +@implementation GRPCOpSendClose{ + void(^_handler)(void); +} + +- (instancetype)init { + return [self initWithHandler:nil]; +} + +- (instancetype)initWithHandler:(void (^)(void))handler { + if (self = [super init]) { + _handler = handler; + } + return self; +} + +- (void)getOp:(grpc_op *)op { + op->op = GRPC_OP_SEND_CLOSE_FROM_CLIENT; +} + +- (void)finish { + if (_handler) { + _handler(); + } +} + +@end + +@implementation GRPCOpRecvMetadata{ + void(^_handler)(NSDictionary *); + grpc_metadata_array _recvInitialMetadata; +} + +- (instancetype) init { + return [self initWithHandler:nil]; +} + +- (instancetype) initWithHandler:(void (^)(NSDictionary *))handler { + if (self = [super init]) { + _handler = handler; + grpc_metadata_array_init(&_recvInitialMetadata); + } + return self; +} + +- (void)getOp:(grpc_op *)op { + op->op = GRPC_OP_RECV_INITIAL_METADATA; + op->data.recv_initial_metadata = &_recvInitialMetadata; +} + +- (void)finish { + NSDictionary *metadata = [NSDictionary + grpc_dictionaryFromMetadata:_recvInitialMetadata.metadata + count:_recvInitialMetadata.count]; + if (_handler) { + _handler(metadata); + } +} + +- (void)dealloc { + grpc_metadata_array_destroy(&_recvInitialMetadata); +} + +@end + +@implementation GRPCOpRecvMessage{ + void(^_handler)(grpc_byte_buffer *); + grpc_byte_buffer *_recvMessage; +} + +- (instancetype)init { + return [self initWithHandler:nil]; +} + +- (instancetype)initWithHandler:(void (^)(grpc_byte_buffer *))handler { + if (self = [super init]) { + _handler = handler; + } + return self; +} + +- (void)getOp:(grpc_op *)op { + op->op = GRPC_OP_RECV_MESSAGE; + op->data.recv_message = &_recvMessage; +} + +- (void)finish { + if (_handler) { + _handler(_recvMessage); + } +} + +@end + +@implementation GRPCOpRecvStatus{ + void(^_handler)(NSError *); + size_t _detailsCapacity; + grpc_status _status; +} + +- (instancetype) init { + return [self initWithHandler:nil]; +} + +- (instancetype) initWithHandler:(void (^)(NSError *))handler { + if (self = [super init]) { + _handler = handler; + grpc_metadata_array_init(&_status.metadata); + } + return self; +} + +- (void)getOp:(grpc_op *)op { + op->op = GRPC_OP_RECV_STATUS_ON_CLIENT; + op->data.recv_status_on_client.status = &_status.status; + op->data.recv_status_on_client.status_details = &_status.details; + op->data.recv_status_on_client.status_details_capacity = &_detailsCapacity; + op->data.recv_status_on_client.trailing_metadata = &_status.metadata; +} + +- (void)finish { + if (_handler) { + NSError *error = [NSError grpc_errorFromStatus:&_status]; + _handler(error); + } +} + +- (void)dealloc { + grpc_metadata_array_destroy(&_status.metadata); + gpr_free(_status.details); +} + +@end + +@implementation GRPCWrappedCall{ + grpc_call *_call; + GRPCCompletionQueue *_queue; +} + +- (instancetype)init { + return [self initWithChannel:nil method:nil host:nil]; +} + +- (instancetype)initWithChannel:(GRPCChannel *)channel + method:(NSString *)method + host:(NSString *)host { + if (!channel || !method || !host) { + [NSException raise:NSInvalidArgumentException + format:@"channel, method, and host cannot be nil."]; + } + + if (self = [super init]) { + static dispatch_once_t initialization; + dispatch_once(&initialization, ^{ + grpc_init(); + }); + + _queue = [GRPCCompletionQueue completionQueue]; + if (!_queue) { + return nil; + } + _call = grpc_channel_create_call(channel.unmanagedChannel, _queue.unmanagedQueue, + method.UTF8String, host.UTF8String, gpr_inf_future); + if (_call == NULL) { + return nil; + } + } + return self; +} + +- (void)startBatchWithOperations:(NSArray *)operations { + [self startBatchWithOperations:operations errorHandler:nil]; +} + +- (void)startBatchWithOperations:(NSArray *)operations errorHandler:(void (^)())errorHandler { + size_t nops = operations.count; + grpc_op *ops_array = gpr_malloc(nops * sizeof(grpc_op)); + size_t i = 0; + for (id op in operations) { + [op getOp:&ops_array[i++]]; + } + grpc_call_error error = grpc_call_start_batch(_call, ops_array, nops, + (__bridge_retained void *)(^(grpc_op_error error){ + if (error != GRPC_OP_OK) { + if (errorHandler) { + errorHandler(); + } else { + return; + } + } + for (id<GRPCOp> operation in operations) { + [operation finish]; + } + })); + + if (error != GRPC_CALL_OK) { + [NSException raise:NSInternalInconsistencyException + format:@"A precondition for calling grpc_call_start_batch wasn't met"]; + } +} + +- (void)cancel { + grpc_call_cancel(_call); +} + +- (void)dealloc { + grpc_call_destroy(_call); +} + +@end
\ No newline at end of file diff --git a/src/objective-c/GRPCClient/private/NSData+GRPC.m b/src/objective-c/GRPCClient/private/NSData+GRPC.m index 5ab16d9d34..6ea4ce979e 100644 --- a/src/objective-c/GRPCClient/private/NSData+GRPC.m +++ b/src/objective-c/GRPCClient/private/NSData+GRPC.m @@ -59,6 +59,9 @@ static grpc_byte_buffer *CopyCharArrayToNewByteBuffer(const char *array, size_t @implementation NSData (GRPC) + (instancetype)grpc_dataWithByteBuffer:(grpc_byte_buffer *)buffer { + if (buffer == NULL) { + return nil; + } NSUInteger length = grpc_byte_buffer_length(buffer); char *array = malloc(length * sizeof(*array)); if (!array) { diff --git a/src/objective-c/GRPCClient/private/NSDictionary+GRPC.h b/src/objective-c/GRPCClient/private/NSDictionary+GRPC.h index 8025285259..622fddcf8e 100644 --- a/src/objective-c/GRPCClient/private/NSDictionary+GRPC.h +++ b/src/objective-c/GRPCClient/private/NSDictionary+GRPC.h @@ -32,9 +32,9 @@ */ #import <Foundation/Foundation.h> - -struct grpc_metadata; +#include <grpc/grpc.h> @interface NSDictionary (GRPC) + (instancetype)grpc_dictionaryFromMetadata:(struct grpc_metadata *)entries count:(size_t)count; +- (grpc_metadata *)grpc_metadataArray; @end diff --git a/src/objective-c/GRPCClient/private/NSDictionary+GRPC.m b/src/objective-c/GRPCClient/private/NSDictionary+GRPC.m index e59685e4a8..c350f32f2a 100644 --- a/src/objective-c/GRPCClient/private/NSDictionary+GRPC.m +++ b/src/objective-c/GRPCClient/private/NSDictionary+GRPC.m @@ -33,7 +33,7 @@ #import "NSDictionary+GRPC.h" -#include <grpc.h> +#include <grpc/support/alloc.h> @implementation NSDictionary (GRPC) + (instancetype)grpc_dictionaryFromMetadata:(grpc_metadata *)entries count:(size_t)count { @@ -53,4 +53,24 @@ } return metadata; } + +- (grpc_metadata *)grpc_metadataArray { + grpc_metadata *metadata = gpr_malloc([self count] * sizeof(grpc_metadata)); + int i = 0; + for (id key in self) { + id value = self[key]; + grpc_metadata *current = &metadata[i]; + current->key = [key UTF8String]; + if ([value isKindOfClass:[NSData class]]) { + current->value = [value bytes]; + } else if ([value isKindOfClass:[NSString class]]) { + current->value = [value UTF8String]; + } else { + [NSException raise:NSInvalidArgumentException + format:@"Metadata values must be NSString or NSData."]; + } + i += 1; + } + return metadata; +} @end diff --git a/src/objective-c/GRPCClient/private/NSError+GRPC.h b/src/objective-c/GRPCClient/private/NSError+GRPC.h index 6183008983..6577d34e80 100644 --- a/src/objective-c/GRPCClient/private/NSError+GRPC.h +++ b/src/objective-c/GRPCClient/private/NSError+GRPC.h @@ -58,14 +58,12 @@ typedef NS_ENUM(NSInteger, GRPCErrorCode) { // TODO(jcanizales): This is conflating trailing metadata with Status details. Fix it once there's // a decision on how to codify Status. -#include <grpc/status.h> -struct grpc_metadata; -struct grpc_status { +#include <grpc/grpc.h> +typedef struct grpc_status { grpc_status_code status; - const char *details; - size_t metadata_count; - struct grpc_metadata *metadata_elements; -}; + char *details; + grpc_metadata_array metadata; +} grpc_status; @interface NSError (GRPC) // Returns nil if the status is OK. Otherwise, a NSError whose code is one of |