aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/objective-c/ProtoRPC
diff options
context:
space:
mode:
Diffstat (limited to 'src/objective-c/ProtoRPC')
-rw-r--r--src/objective-c/ProtoRPC/ProtoRPC.h118
-rw-r--r--src/objective-c/ProtoRPC/ProtoRPC.m227
-rw-r--r--src/objective-c/ProtoRPC/ProtoService.h35
-rw-r--r--src/objective-c/ProtoRPC/ProtoService.m67
4 files changed, 439 insertions, 8 deletions
diff --git a/src/objective-c/ProtoRPC/ProtoRPC.h b/src/objective-c/ProtoRPC/ProtoRPC.h
index 45d3526092..8ce3421cc1 100644
--- a/src/objective-c/ProtoRPC/ProtoRPC.h
+++ b/src/objective-c/ProtoRPC/ProtoRPC.h
@@ -21,6 +21,122 @@
#import "ProtoMethod.h"
+NS_ASSUME_NONNULL_BEGIN
+
+@class GPBMessage;
+
+/** An object can implement this protocol to receive responses from server from a call. */
+@protocol GRPCProtoResponseHandler<NSObject>
+
+@required
+
+/**
+ * All the responses must be issued to a user-provided dispatch queue. This property specifies the
+ * dispatch queue to be used for issuing the notifications.
+ */
+@property(atomic, readonly) dispatch_queue_t dispatchQueue;
+
+@optional
+
+/**
+ * Issued when initial metadata is received from the server.
+ */
+- (void)didReceiveInitialMetadata:(nullable NSDictionary *)initialMetadata;
+
+/**
+ * Issued when a message is received from the server. The message is the deserialized proto object.
+ */
+- (void)didReceiveProtoMessage:(nullable GPBMessage *)message;
+
+/**
+ * Issued when a call finished. If the call finished successfully, \a error is nil and \a
+ * trainingMetadata consists any trailing metadata received from the server. Otherwise, \a error
+ * is non-nil and contains the corresponding error information, including gRPC error codes and
+ * error descriptions.
+ */
+- (void)didCloseWithTrailingMetadata:(nullable NSDictionary *)trailingMetadata
+ error:(nullable NSError *)error;
+
+@end
+
+/** A unary-request RPC call with Protobuf. */
+@interface GRPCUnaryProtoCall : NSObject
+
+- (instancetype)init NS_UNAVAILABLE;
+
++ (instancetype) new NS_UNAVAILABLE;
+
+/**
+ * Users should not use this initializer directly. Call objects will be created, initialized, and
+ * returned to users by methods of the generated service.
+ */
+- (nullable instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions
+ message:(GPBMessage *)message
+ responseHandler:(id<GRPCProtoResponseHandler>)handler
+ callOptions:(nullable GRPCCallOptions *)callOptions
+ responseClass:(Class)responseClass NS_DESIGNATED_INITIALIZER;
+
+/**
+ * Start the call. This function must only be called once for each instance.
+ */
+- (void)start;
+
+/**
+ * Cancel the request of this call at best effort. It attempts to notify the server that the RPC
+ * should be cancelled, and issue didCloseWithTrailingMetadata:error: callback with error code
+ * CANCELED if no other error code has already been issued.
+ */
+- (void)cancel;
+
+@end
+
+/** A client-streaming RPC call with Protobuf. */
+@interface GRPCStreamingProtoCall : NSObject
+
+- (instancetype)init NS_UNAVAILABLE;
+
++ (instancetype) new NS_UNAVAILABLE;
+
+/**
+ * Users should not use this initializer directly. Call objects will be created, initialized, and
+ * returned to users by methods of the generated service.
+ */
+- (nullable instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions
+ responseHandler:(id<GRPCProtoResponseHandler>)handler
+ callOptions:(nullable GRPCCallOptions *)callOptions
+ responseClass:(Class)responseClass NS_DESIGNATED_INITIALIZER;
+
+/**
+ * Start the call. This function must only be called once for each instance.
+ */
+- (void)start;
+
+/**
+ * Cancel the request of this call at best effort. It attempts to notify the server that the RPC
+ * should be cancelled, and issue didCloseWithTrailingMetadata:error: callback with error code
+ * CANCELED if no other error code has already been issued.
+ */
+- (void)cancel;
+
+/**
+ * Send a message to the server. The message should be a Protobuf message which will be serialized
+ * internally.
+ */
+- (void)writeMessage:(GPBMessage *)message;
+
+/**
+ * Finish the RPC request and half-close the call. The server may still send messages and/or
+ * trailers to the client.
+ */
+- (void)finish;
+
+@end
+
+NS_ASSUME_NONNULL_END
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnullability-completeness"
+
__attribute__((deprecated("Please use GRPCProtoCall."))) @interface ProtoRPC
: GRPCCall
@@ -47,3 +163,5 @@ __attribute__((deprecated("Please use GRPCProtoCall."))) @interface ProtoRPC
#pragma clang diagnostic pop
@end
+
+#pragma clang diagnostic pop
diff --git a/src/objective-c/ProtoRPC/ProtoRPC.m b/src/objective-c/ProtoRPC/ProtoRPC.m
index 5dca971b08..0ab96a5ba2 100644
--- a/src/objective-c/ProtoRPC/ProtoRPC.m
+++ b/src/objective-c/ProtoRPC/ProtoRPC.m
@@ -23,9 +23,13 @@
#else
#import <GPBProtocolBuffers.h>
#endif
+#import <GRPCClient/GRPCCall.h>
#import <RxLibrary/GRXWriteable.h>
#import <RxLibrary/GRXWriter+Transformations.h>
+/**
+ * Generate an NSError object that represents a failure in parsing a proto class.
+ */
static NSError *ErrorForBadProto(id proto, Class expectedClass, NSError *parsingError) {
NSDictionary *info = @{
NSLocalizedDescriptionKey : @"Unable to parse response from the server",
@@ -41,6 +45,227 @@ static NSError *ErrorForBadProto(id proto, Class expectedClass, NSError *parsing
return [NSError errorWithDomain:@"io.grpc" code:13 userInfo:info];
}
+@implementation GRPCUnaryProtoCall {
+ GRPCStreamingProtoCall *_call;
+ GPBMessage *_message;
+}
+
+- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions
+ message:(GPBMessage *)message
+ responseHandler:(id<GRPCProtoResponseHandler>)handler
+ callOptions:(GRPCCallOptions *)callOptions
+ responseClass:(Class)responseClass {
+ NSAssert(message != nil, @"message cannot be empty.");
+ NSAssert(responseClass != nil, @"responseClass cannot be empty.");
+ if (message == nil || responseClass == nil) {
+ return nil;
+ }
+ if ((self = [super init])) {
+ _call = [[GRPCStreamingProtoCall alloc] initWithRequestOptions:requestOptions
+ responseHandler:handler
+ callOptions:callOptions
+ responseClass:responseClass];
+ _message = [message copy];
+ }
+ return self;
+}
+
+- (void)start {
+ [_call start];
+ [_call writeMessage:_message];
+ [_call finish];
+}
+
+- (void)cancel {
+ [_call cancel];
+}
+
+@end
+
+@interface GRPCStreamingProtoCall ()<GRPCResponseHandler>
+
+@end
+
+@implementation GRPCStreamingProtoCall {
+ GRPCRequestOptions *_requestOptions;
+ id<GRPCProtoResponseHandler> _handler;
+ GRPCCallOptions *_callOptions;
+ Class _responseClass;
+
+ GRPCCall2 *_call;
+ dispatch_queue_t _dispatchQueue;
+}
+
+- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions
+ responseHandler:(id<GRPCProtoResponseHandler>)handler
+ callOptions:(GRPCCallOptions *)callOptions
+ responseClass:(Class)responseClass {
+ NSAssert(requestOptions.host.length != 0 && requestOptions.path.length != 0 &&
+ requestOptions.safety <= GRPCCallSafetyCacheableRequest,
+ @"Invalid callOptions.");
+ NSAssert(handler != nil, @"handler cannot be empty.");
+ if (requestOptions.host.length == 0 || requestOptions.path.length == 0 ||
+ requestOptions.safety > GRPCCallSafetyCacheableRequest) {
+ return nil;
+ }
+ if (handler == nil) {
+ return nil;
+ }
+
+ if ((self = [super init])) {
+ _requestOptions = [requestOptions copy];
+ _handler = handler;
+ _callOptions = [callOptions copy];
+ _responseClass = responseClass;
+
+ // Set queue QoS only when iOS version is 8.0 or above and Xcode version is 9.0 or above
+#if __IPHONE_OS_VERSION_MAX_ALLOWED < 110000 || __MAC_OS_X_VERSION_MAX_ALLOWED < 101300
+ if (@available(iOS 8.0, macOS 10.10, *)) {
+ _dispatchQueue = dispatch_queue_create(
+ NULL,
+ dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0));
+ } else {
+#else
+ {
+#endif
+ _dispatchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
+ }
+ dispatch_set_target_queue(_dispatchQueue, handler.dispatchQueue);
+
+ _call = [[GRPCCall2 alloc] initWithRequestOptions:_requestOptions
+ responseHandler:self
+ callOptions:_callOptions];
+ }
+ return self;
+}
+
+- (void)start {
+ GRPCCall2 *copiedCall;
+ @synchronized(self) {
+ copiedCall = _call;
+ }
+ [copiedCall start];
+}
+
+- (void)cancel {
+ GRPCCall2 *copiedCall;
+ @synchronized(self) {
+ copiedCall = _call;
+ _call = nil;
+ if ([_handler respondsToSelector:@selector(didCloseWithTrailingMetadata:error:)]) {
+ dispatch_async(_dispatchQueue, ^{
+ id<GRPCProtoResponseHandler> copiedHandler = nil;
+ @synchronized(self) {
+ copiedHandler = self->_handler;
+ self->_handler = nil;
+ }
+ [copiedHandler didCloseWithTrailingMetadata:nil
+ error:[NSError errorWithDomain:kGRPCErrorDomain
+ code:GRPCErrorCodeCancelled
+ userInfo:@{
+ NSLocalizedDescriptionKey :
+ @"Canceled by app"
+ }]];
+ });
+ } else {
+ _handler = nil;
+ }
+ }
+ [copiedCall cancel];
+}
+
+- (void)writeMessage:(GPBMessage *)message {
+ NSAssert([message isKindOfClass:[GPBMessage class]], @"Parameter message must be a GPBMessage");
+ if (![message isKindOfClass:[GPBMessage class]]) {
+ NSLog(@"Failed to send a message that is non-proto.");
+ return;
+ }
+
+ GRPCCall2 *copiedCall;
+ @synchronized(self) {
+ copiedCall = _call;
+ }
+ [copiedCall writeData:[message data]];
+}
+
+- (void)finish {
+ GRPCCall2 *copiedCall;
+ @synchronized(self) {
+ copiedCall = _call;
+ _call = nil;
+ }
+ [copiedCall finish];
+}
+
+- (void)didReceiveInitialMetadata:(NSDictionary *)initialMetadata {
+ @synchronized(self) {
+ if (initialMetadata != nil &&
+ [_handler respondsToSelector:@selector(didReceiveInitialMetadata:)]) {
+ dispatch_async(_dispatchQueue, ^{
+ id<GRPCProtoResponseHandler> copiedHandler = nil;
+ @synchronized(self) {
+ copiedHandler = self->_handler;
+ }
+ [copiedHandler didReceiveInitialMetadata:initialMetadata];
+ });
+ }
+ }
+}
+
+- (void)didReceiveRawMessage:(NSData *)message {
+ if (message == nil) return;
+
+ NSError *error = nil;
+ GPBMessage *parsed = [_responseClass parseFromData:message error:&error];
+ @synchronized(self) {
+ if (parsed && [_handler respondsToSelector:@selector(didReceiveProtoMessage:)]) {
+ dispatch_async(_dispatchQueue, ^{
+ id<GRPCProtoResponseHandler> copiedHandler = nil;
+ @synchronized(self) {
+ copiedHandler = self->_handler;
+ }
+ [copiedHandler didReceiveProtoMessage:parsed];
+ });
+ } else if (!parsed &&
+ [_handler respondsToSelector:@selector(didCloseWithTrailingMetadata:error:)]) {
+ dispatch_async(_dispatchQueue, ^{
+ id<GRPCProtoResponseHandler> copiedHandler = nil;
+ @synchronized(self) {
+ copiedHandler = self->_handler;
+ self->_handler = nil;
+ }
+ [copiedHandler
+ didCloseWithTrailingMetadata:nil
+ error:ErrorForBadProto(message, self->_responseClass, error)];
+ });
+ [_call cancel];
+ _call = nil;
+ }
+ }
+}
+
+- (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error {
+ @synchronized(self) {
+ if ([_handler respondsToSelector:@selector(didCloseWithTrailingMetadata:error:)]) {
+ dispatch_async(_dispatchQueue, ^{
+ id<GRPCProtoResponseHandler> copiedHandler = nil;
+ @synchronized(self) {
+ copiedHandler = self->_handler;
+ self->_handler = nil;
+ }
+ [copiedHandler didCloseWithTrailingMetadata:trailingMetadata error:error];
+ });
+ }
+ _call = nil;
+ }
+}
+
+- (dispatch_queue_t)dispatchQueue {
+ return _dispatchQueue;
+}
+
+@end
+
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-implementations"
@implementation ProtoRPC {
@@ -72,7 +297,7 @@ static NSError *ErrorForBadProto(id proto, Class expectedClass, NSError *parsing
}
// A writer that serializes the proto messages to send.
GRXWriter *bytesWriter = [requestsWriter map:^id(GPBMessage *proto) {
- if (![proto isKindOfClass:GPBMessage.class]) {
+ if (![proto isKindOfClass:[GPBMessage class]]) {
[NSException raise:NSInvalidArgumentException
format:@"Request must be a proto message: %@", proto];
}
diff --git a/src/objective-c/ProtoRPC/ProtoService.h b/src/objective-c/ProtoRPC/ProtoService.h
index 29c4e9be36..900ec8d0e1 100644
--- a/src/objective-c/ProtoRPC/ProtoService.h
+++ b/src/objective-c/ProtoRPC/ProtoService.h
@@ -21,18 +21,47 @@
@class GRPCProtoCall;
@protocol GRXWriteable;
@class GRXWriter;
+@class GRPCCallOptions;
+@class GRPCProtoCall;
+@class GRPCUnaryProtoCall;
+@class GRPCStreamingProtoCall;
+@protocol GRPCProtoResponseHandler;
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnullability-completeness"
__attribute__((deprecated("Please use GRPCProtoService."))) @interface ProtoService
- : NSObject -
- (instancetype)initWithHost : (NSString *)host packageName
- : (NSString *)packageName serviceName : (NSString *)serviceName NS_DESIGNATED_INITIALIZER;
+ : NSObject
+
+ -
+ (nullable instancetype)initWithHost : (nonnull NSString *)host packageName
+ : (nonnull NSString *)packageName serviceName : (nonnull NSString *)serviceName callOptions
+ : (nullable GRPCCallOptions *)callOptions NS_DESIGNATED_INITIALIZER;
+
+- (instancetype)initWithHost:(NSString *)host
+ packageName:(NSString *)packageName
+ serviceName:(NSString *)serviceName;
- (GRPCProtoCall *)RPCToMethod:(NSString *)method
requestsWriter:(GRXWriter *)requestsWriter
responseClass:(Class)responseClass
responsesWriteable:(id<GRXWriteable>)responsesWriteable;
+
+- (nullable GRPCUnaryProtoCall *)RPCToMethod:(nonnull NSString *)method
+ message:(nonnull id)message
+ responseHandler:(nonnull id<GRPCProtoResponseHandler>)handler
+ callOptions:(nullable GRPCCallOptions *)callOptions
+ responseClass:(nonnull Class)responseClass;
+
+- (nullable GRPCStreamingProtoCall *)RPCToMethod:(nonnull NSString *)method
+ responseHandler:(nonnull id<GRPCProtoResponseHandler>)handler
+ callOptions:(nullable GRPCCallOptions *)callOptions
+ responseClass:(nonnull Class)responseClass;
+
@end
+#pragma clang diagnostic pop
+
/**
* This subclass is empty now. Eventually we'll remove ProtoService class
* to avoid potential naming conflict
diff --git a/src/objective-c/ProtoRPC/ProtoService.m b/src/objective-c/ProtoRPC/ProtoService.m
index 3e9cc5c0b2..80a1f2f226 100644
--- a/src/objective-c/ProtoRPC/ProtoService.m
+++ b/src/objective-c/ProtoRPC/ProtoService.m
@@ -18,6 +18,7 @@
#import "ProtoService.h"
+#import <GRPCClient/GRPCCall.h>
#import <RxLibrary/GRXWriteable.h>
#import <RxLibrary/GRXWriter.h>
@@ -31,6 +32,7 @@
NSString *_host;
NSString *_packageName;
NSString *_serviceName;
+ GRPCCallOptions *_callOptions;
}
- (instancetype)init {
@@ -40,19 +42,41 @@
// Designated initializer
- (instancetype)initWithHost:(NSString *)host
packageName:(NSString *)packageName
- serviceName:(NSString *)serviceName {
- if (!host || !serviceName) {
- [NSException raise:NSInvalidArgumentException
- format:@"Neither host nor serviceName can be nil."];
+ serviceName:(NSString *)serviceName
+ callOptions:(GRPCCallOptions *)callOptions {
+ NSAssert(host.length != 0 && packageName.length != 0 && serviceName.length != 0,
+ @"Invalid parameter.");
+ if (host.length == 0 || packageName.length == 0 || serviceName.length == 0) {
+ return nil;
}
if ((self = [super init])) {
_host = [host copy];
_packageName = [packageName copy];
_serviceName = [serviceName copy];
+ _callOptions = [callOptions copy];
}
return self;
}
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
+// Do not call designated initializer here due to nullability incompatibility. This method is from
+// old API and does not assert on nullability of the parameters.
+
+- (instancetype)initWithHost:(NSString *)host
+ packageName:(NSString *)packageName
+ serviceName:(NSString *)serviceName {
+ if ((self = [super init])) {
+ _host = [host copy];
+ _packageName = [packageName copy];
+ _serviceName = [serviceName copy];
+ _callOptions = nil;
+ }
+ return self;
+}
+
+#pragma clang diagnostic pop
+
- (GRPCProtoCall *)RPCToMethod:(NSString *)method
requestsWriter:(GRXWriter *)requestsWriter
responseClass:(Class)responseClass
@@ -65,6 +89,41 @@
responseClass:responseClass
responsesWriteable:responsesWriteable];
}
+
+- (GRPCUnaryProtoCall *)RPCToMethod:(NSString *)method
+ message:(id)message
+ responseHandler:(id<GRPCProtoResponseHandler>)handler
+ callOptions:(GRPCCallOptions *)callOptions
+ responseClass:(Class)responseClass {
+ GRPCProtoMethod *methodName =
+ [[GRPCProtoMethod alloc] initWithPackage:_packageName service:_serviceName method:method];
+ GRPCRequestOptions *requestOptions =
+ [[GRPCRequestOptions alloc] initWithHost:_host
+ path:methodName.HTTPPath
+ safety:GRPCCallSafetyDefault];
+ return [[GRPCUnaryProtoCall alloc] initWithRequestOptions:requestOptions
+ message:message
+ responseHandler:handler
+ callOptions:callOptions ?: _callOptions
+ responseClass:responseClass];
+}
+
+- (GRPCStreamingProtoCall *)RPCToMethod:(NSString *)method
+ responseHandler:(id<GRPCProtoResponseHandler>)handler
+ callOptions:(GRPCCallOptions *)callOptions
+ responseClass:(Class)responseClass {
+ GRPCProtoMethod *methodName =
+ [[GRPCProtoMethod alloc] initWithPackage:_packageName service:_serviceName method:method];
+ GRPCRequestOptions *requestOptions =
+ [[GRPCRequestOptions alloc] initWithHost:_host
+ path:methodName.HTTPPath
+ safety:GRPCCallSafetyDefault];
+ return [[GRPCStreamingProtoCall alloc] initWithRequestOptions:requestOptions
+ responseHandler:handler
+ callOptions:callOptions ?: _callOptions
+ responseClass:responseClass];
+}
+
@end
@implementation GRPCProtoService