Merged changes from master
diff --git a/src/objective-c/GRPCClient/GRPCCall.m b/src/objective-c/GRPCClient/GRPCCall.m
index 989ce39..2186ea4 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 @@
// 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 @@
_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 @@
- (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 @@
}
- (void)dealloc {
- grpc_call *gRPCCall = _gRPCCall;
+ __block GRPCWrappedCall *wrappedCall = _wrappedCall;
dispatch_async(_callQueue, ^{
- grpc_call_destroy(gRPCCall);
+ wrappedCall = nil;
});
}
@@ -177,8 +155,8 @@
// 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 {
+ [_wrappedCall startBatchWithOperations:@[[[GRPCOpRecvMessage alloc] initWithHandler:handler]]];
}
// Called initially from the network queue once response headers are received,
@@ -195,12 +173,12 @@
__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) {
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 +203,10 @@
#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];
- }
- }
+ [_wrappedCall startBatchWithOperations:@[[[GRPCOpSendMetadata alloc]
+ initWithMetadata:metadata ?: @{} handler:nil]]];
}
#pragma mark GRXWriteable implementation
@@ -263,10 +216,7 @@
- (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();
- }
+ void(^resumingHandler)(void) = ^{
// Resume the request writer (even in the case of error).
// TODO(jcanizales): No need to do it in the case of errors anymore?
GRPCCall *strongSelf = weakSelf;
@@ -274,13 +224,9 @@
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 +249,8 @@
// 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 +274,26 @@
// 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 {
+ [_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 c85d57c..198fa2b 100644
--- a/src/objective-c/GRPCClient/private/GRPCCompletionQueue.h
+++ b/src/objective-c/GRPCClient/private/GRPCCompletionQueue.h
@@ -32,11 +32,11 @@
*/
#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
diff --git a/src/objective-c/GRPCClient/private/GRPCCompletionQueue.m b/src/objective-c/GRPCClient/private/GRPCCompletionQueue.m
index b98e8ea..cae21b7 100644
--- a/src/objective-c/GRPCClient/private/GRPCCompletionQueue.m
+++ b/src/objective-c/GRPCClient/private/GRPCCompletionQueue.m
@@ -46,7 +46,7 @@
- (instancetype)init {
if ((self = [super init])) {
_unmanagedQueue = grpc_completion_queue_create();
-
+
// This is for the following block to capture the pointer by value (instead
// of retaining self and doing self->_unmanagedQueue). This is essential
// because the block doesn't end until after grpc_completion_queue_shutdown
@@ -66,30 +66,18 @@
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);
- }
- grpc_event_finish(event);
- continue;
+ case GRPC_OP_COMPLETE:
+ handler = (__bridge_transfer GRPCQueueCompletionHandler)event->tag;
+ handler(event->data.op_complete);
+ break;
case GRPC_QUEUE_SHUTDOWN:
grpc_completion_queue_destroy(unmanagedQueue);
- grpc_event_finish(event);
return;
- case GRPC_SERVER_RPC_NEW:
- NSAssert(NO, @"C gRPC library produced a server-only event.");
- continue;
+ default:
+ [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 0000000..d7b1aa8
--- /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(^)(void))opProcessor;
+
+@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 0000000..ad6aca0
--- /dev/null
+++ b/src/objective-c/GRPCClient/private/GRPCWrappedCall.m
@@ -0,0 +1,341 @@
+/*
+ *
+ * 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 *_send_metadata;
+ size_t _count;
+}
+
+- (instancetype)init {
+ return [self initWithMetadata:nil handler:nil];
+}
+
+- (instancetype)initWithMetadata:(NSDictionary *)metadata handler:(void (^)(void))handler {
+ if (self = [super init]) {
+ if (metadata) {
+ [metadata grpc_getMetadataArray:&_send_metadata];
+ _count = metadata.count;
+ } else {
+ _send_metadata = NULL;
+ _count = 0;
+ }
+ _handler = handler;
+ }
+ return self;
+}
+
+- (void)getOp:(grpc_op *)op {
+ op->op = GRPC_OP_SEND_INITIAL_METADATA;
+ op->data.send_initial_metadata.count = _count;
+}
+
+- (void (^)(void))opProcessor {
+ return ^{
+ gpr_free(_send_metadata);
+ if (_handler) {
+ _handler();
+ }
+ };
+}
+
+@end
+
+@implementation GRPCOpSendMessage{
+ void(^_handler)(void);
+ grpc_byte_buffer *_byte_buffer;
+}
+
+- (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 null"];
+ }
+ if (self = [super init]) {
+ _byte_buffer = [message grpc_byteBuffer];
+ _handler = handler;
+ }
+ return self;
+}
+
+- (void)getOp:(grpc_op *)op {
+ op->op = GRPC_OP_SEND_MESSAGE;
+ op->data.send_message = _byte_buffer;
+}
+
+- (void (^)(void))opProcessor {
+ return ^{
+ gpr_free(_byte_buffer);
+ if (_handler) {
+ _handler();
+ }
+ };
+}
+
+@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 (^)(void))opProcessor {
+ return ^{
+ if (_handler) {
+ _handler();
+ }
+ };
+}
+
+@end
+
+@implementation GRPCOpRecvMetadata{
+ void(^_handler)(NSDictionary *);
+ grpc_metadata_array *_recv_initial_metadata;
+}
+
+- (instancetype) init {
+ return [self initWithHandler:nil];
+}
+
+- (instancetype) initWithHandler:(void (^)(NSDictionary *))handler {
+ if (self = [super init]) {
+ _handler = handler;
+ _recv_initial_metadata = gpr_malloc(sizeof(grpc_metadata_array));
+ grpc_metadata_array_init(_recv_initial_metadata);
+ }
+ return self;
+}
+
+- (void)getOp:(grpc_op *)op {
+ op->op = GRPC_OP_RECV_INITIAL_METADATA;
+ op->data.recv_initial_metadata = _recv_initial_metadata;
+}
+
+- (void (^)(void))opProcessor {
+ return ^{
+ NSDictionary *metadata = [NSDictionary
+ grpc_dictionaryFromMetadata:_recv_initial_metadata->metadata
+ count:_recv_initial_metadata->count];
+ grpc_metadata_array_destroy(_recv_initial_metadata);
+ if (_handler) {
+ _handler(metadata);
+ }
+ };
+}
+
+@end
+
+@implementation GRPCOpRecvMessage{
+ void(^_handler)(grpc_byte_buffer *);
+ grpc_byte_buffer **_recv_message;
+}
+
+- (instancetype)init {
+ return [self initWithHandler:nil];
+}
+
+- (instancetype)initWithHandler:(void (^)(grpc_byte_buffer *))handler {
+ if (self = [super init]) {
+ _handler = handler;
+ _recv_message = gpr_malloc(sizeof(grpc_byte_buffer*));
+ }
+ return self;
+}
+
+- (void)getOp:(grpc_op *)op {
+ op->op = GRPC_OP_RECV_MESSAGE;
+ op->data.recv_message = _recv_message;
+}
+
+- (void (^)(void))opProcessor {
+ return ^{
+ if (_handler) {
+ _handler(*_recv_message);
+ gpr_free(_recv_message);
+ }
+ };
+}
+
+@end
+
+@implementation GRPCOpRecvStatus{
+ void(^_handler)(NSError *);
+ grpc_status_code *_code;
+ char **_details;
+ size_t *_details_capacity;
+ grpc_metadata_array *_recv_trailing_metadata;
+}
+
+- (instancetype) init {
+ return [self initWithHandler:nil];
+}
+
+- (instancetype) initWithHandler:(void (^)(NSError *))handler {
+ if (self = [super init]) {
+ _handler = handler;
+ _code = gpr_malloc(sizeof(grpc_status_code));
+ _details = gpr_malloc(sizeof(char*));
+ _details_capacity = gpr_malloc(sizeof(size_t));
+ *_details_capacity = 0;
+ _recv_trailing_metadata = gpr_malloc(sizeof(grpc_metadata_array));
+ }
+ return self;
+}
+
+- (void)getOp:(grpc_op *)op {
+ op->op = GRPC_OP_RECV_STATUS_ON_CLIENT;
+ op->data.recv_status_on_client.status = _code;
+ op->data.recv_status_on_client.status_details = _details;
+ op->data.recv_status_on_client.status_details_capacity = _details_capacity;
+ op->data.recv_status_on_client.trailing_metadata = _recv_trailing_metadata;
+}
+
+- (void (^)(void))opProcessor {
+ return ^{
+ grpc_status status;
+ status.status = *_code;
+ status.details = *_details;
+ status.metadata = _recv_trailing_metadata;
+ gpr_free(_code);
+ gpr_free(_details);
+ gpr_free(_details_capacity);
+ if (_handler) {
+ _handler([NSError grpc_errorFromStatus:&status]);
+ }
+ };
+}
+
+@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];
+ _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 {
+ NSMutableArray *opProcessors = [NSMutableArray array];
+ 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]];
+ [opProcessors addObject:[op opProcessor]];
+ }
+ 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 {
+ [NSException raise:@"Operation Exception" format:@"The batch failed with an unknown error"];
+ }
+ }
+ for (void(^processor)(void) in opProcessors) {
+ processor();
+ }
+ }));
+
+ if (error != GRPC_CALL_OK) {
+ [NSException raise:NSInvalidArgumentException format:@"The batch did not start successfully"];
+ }
+}
+
+- (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 5ab16d9..6ea4ce9 100644
--- a/src/objective-c/GRPCClient/private/NSData+GRPC.m
+++ b/src/objective-c/GRPCClient/private/NSData+GRPC.m
@@ -59,6 +59,9 @@
@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 8025285..fec2adb 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;
+- (void)grpc_getMetadataArray:(grpc_metadata **)metadata;
@end
diff --git a/src/objective-c/GRPCClient/private/NSDictionary+GRPC.m b/src/objective-c/GRPCClient/private/NSDictionary+GRPC.m
index e59685e..83c09cd 100644
--- a/src/objective-c/GRPCClient/private/NSDictionary+GRPC.m
+++ b/src/objective-c/GRPCClient/private/NSDictionary+GRPC.m
@@ -33,7 +33,8 @@
#import "NSDictionary+GRPC.h"
-#include <grpc.h>
+#include <grpc/grpc.h>
+#include <grpc/support/alloc.h>
@implementation NSDictionary (GRPC)
+ (instancetype)grpc_dictionaryFromMetadata:(grpc_metadata *)entries count:(size_t)count {
@@ -53,4 +54,24 @@
}
return metadata;
}
+
+- (void)grpc_getMetadataArray:(grpc_metadata **)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."];
+ }
+ current->value = [value UTF8String];
+ i += 1;
+ }
+}
@end
diff --git a/src/objective-c/GRPCClient/private/NSError+GRPC.h b/src/objective-c/GRPCClient/private/NSError+GRPC.h
index 6183008..aa032a4 100644
--- a/src/objective-c/GRPCClient/private/NSError+GRPC.h
+++ b/src/objective-c/GRPCClient/private/NSError+GRPC.h
@@ -58,14 +58,12 @@
// 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;
-};
+ grpc_metadata_array *metadata;
+} grpc_status;
@interface NSError (GRPC)
// Returns nil if the status is OK. Otherwise, a NSError whose code is one of