aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/objective-c/GRPCClient
diff options
context:
space:
mode:
Diffstat (limited to 'src/objective-c/GRPCClient')
-rw-r--r--src/objective-c/GRPCClient/GRPCCall.m157
-rw-r--r--src/objective-c/GRPCClient/private/GRPCCompletionQueue.h8
-rw-r--r--src/objective-c/GRPCClient/private/GRPCCompletionQueue.m27
-rw-r--r--src/objective-c/GRPCClient/private/GRPCWrappedCall.h97
-rw-r--r--src/objective-c/GRPCClient/private/GRPCWrappedCall.m326
-rw-r--r--src/objective-c/GRPCClient/private/NSData+GRPC.m3
-rw-r--r--src/objective-c/GRPCClient/private/NSDictionary+GRPC.h4
-rw-r--r--src/objective-c/GRPCClient/private/NSDictionary+GRPC.m22
-rw-r--r--src/objective-c/GRPCClient/private/NSError+GRPC.h12
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