aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/objective-c/ProtoRPC/ProtoRPC.m
diff options
context:
space:
mode:
Diffstat (limited to 'src/objective-c/ProtoRPC/ProtoRPC.m')
-rw-r--r--src/objective-c/ProtoRPC/ProtoRPC.m227
1 files changed, 226 insertions, 1 deletions
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];
}