diff --git a/src/compiler/objective_c_generator.cc b/src/compiler/objective_c_generator.cc index 39f68cb9565..eb67e9bb88f 100644 --- a/src/compiler/objective_c_generator.cc +++ b/src/compiler/objective_c_generator.cc @@ -113,6 +113,29 @@ void PrintAdvancedSignature(Printer* printer, const MethodDescriptor* method, PrintMethodSignature(printer, method, vars); } +void PrintV2Signature(Printer* printer, const MethodDescriptor* method, + map< ::grpc::string, ::grpc::string> vars) { + if (method->client_streaming()) { + vars["return_type"] = "GRPCStreamingProtoCall *"; + } else { + vars["return_type"] = "GRPCUnaryProtoCall *"; + } + vars["method_name"] = + grpc_generator::LowercaseFirstLetter(vars["method_name"]); + + PrintAllComments(method, printer); + + printer->Print(vars, "- ($return_type$)$method_name$With"); + if (method->client_streaming()) { + printer->Print("ResponseHandler:(id)handler"); + } else { + printer->Print(vars, + "Message:($request_class$ *)message " + "responseHandler:(id)handler"); + } + printer->Print(" callOptions:(GRPCCallOptions *_Nullable)callOptions"); +} + inline map< ::grpc::string, ::grpc::string> GetMethodVars( const MethodDescriptor* method) { map< ::grpc::string, ::grpc::string> res; @@ -135,6 +158,16 @@ void PrintMethodDeclarations(Printer* printer, const MethodDescriptor* method) { printer->Print(";\n\n\n"); } +void PrintV2MethodDeclarations(Printer* printer, + const MethodDescriptor* method) { + map< ::grpc::string, ::grpc::string> vars = GetMethodVars(method); + + PrintProtoRpcDeclarationAsPragma(printer, method, vars); + + PrintV2Signature(printer, method, vars); + printer->Print(";\n\n"); +} + void PrintSimpleImplementation(Printer* printer, const MethodDescriptor* method, map< ::grpc::string, ::grpc::string> vars) { printer->Print("{\n"); @@ -177,6 +210,25 @@ void PrintAdvancedImplementation(Printer* printer, printer->Print("}\n"); } +void PrintV2Implementation(Printer* printer, const MethodDescriptor* method, + map< ::grpc::string, ::grpc::string> vars) { + printer->Print(" {\n"); + if (method->client_streaming()) { + printer->Print(vars, " return [self RPCToMethod:@\"$method_name$\"\n"); + printer->Print(" responseHandler:handler\n"); + printer->Print(" callOptions:callOptions\n"); + printer->Print( + vars, " responseClass:[$response_class$ class]];\n}\n\n"); + } else { + printer->Print(vars, " return [self RPCToMethod:@\"$method_name$\"\n"); + printer->Print(" message:message\n"); + printer->Print(" responseHandler:handler\n"); + printer->Print(" callOptions:callOptions\n"); + printer->Print( + vars, " responseClass:[$response_class$ class]];\n}\n\n"); + } +} + void PrintMethodImplementations(Printer* printer, const MethodDescriptor* method) { map< ::grpc::string, ::grpc::string> vars = GetMethodVars(method); @@ -184,12 +236,16 @@ void PrintMethodImplementations(Printer* printer, PrintProtoRpcDeclarationAsPragma(printer, method, vars); // TODO(jcanizales): Print documentation from the method. + printer->Print("// Deprecated methods.\n"); PrintSimpleSignature(printer, method, vars); PrintSimpleImplementation(printer, method, vars); printer->Print("// Returns a not-yet-started RPC object.\n"); PrintAdvancedSignature(printer, method, vars); PrintAdvancedImplementation(printer, method, vars); + + PrintV2Signature(printer, method, vars); + PrintV2Implementation(printer, method, vars); } } // namespace @@ -231,6 +287,25 @@ void PrintMethodImplementations(Printer* printer, return output; } +::grpc::string GetV2Protocol(const ServiceDescriptor* service) { + ::grpc::string output; + + // Scope the output stream so it closes and finalizes output to the string. + grpc::protobuf::io::StringOutputStream output_stream(&output); + Printer printer(&output_stream, '$'); + + map< ::grpc::string, ::grpc::string> vars = { + {"service_class", ServiceClassName(service) + "2"}}; + + printer.Print(vars, "@protocol $service_class$ \n\n"); + for (int i = 0; i < service->method_count(); i++) { + PrintV2MethodDeclarations(&printer, service->method(i)); + } + printer.Print("@end\n\n"); + + return output; +} + ::grpc::string GetInterface(const ServiceDescriptor* service) { ::grpc::string output; @@ -248,10 +323,16 @@ void PrintMethodImplementations(Printer* printer, " */\n"); printer.Print(vars, "@interface $service_class$ :" - " GRPCProtoService<$service_class$>\n"); + " GRPCProtoService<$service_class$, $service_class$2>\n"); printer.Print( - "- (instancetype)initWithHost:(NSString *)host" + "- (instancetype)initWithHost:(NSString *)host " + "callOptions:(GRPCCallOptions " + "*_Nullable)callOptions" " NS_DESIGNATED_INITIALIZER;\n"); + printer.Print("- (instancetype)initWithHost:(NSString *)host;\n"); + printer.Print( + "+ (instancetype)serviceWithHost:(NSString *)host " + "callOptions:(GRPCCallOptions *_Nullable)callOptions;\n"); printer.Print("+ (instancetype)serviceWithHost:(NSString *)host;\n"); printer.Print("@end\n"); @@ -273,11 +354,19 @@ void PrintMethodImplementations(Printer* printer, printer.Print(vars, "@implementation $service_class$\n\n" "// Designated initializer\n" - "- (instancetype)initWithHost:(NSString *)host {\n" + "- (instancetype)initWithHost:(NSString *)host " + "callOptions:(GRPCCallOptions *_Nullable)callOptions{\n" " self = [super initWithHost:host\n" " packageName:@\"$package$\"\n" - " serviceName:@\"$service_name$\"];\n" + " serviceName:@\"$service_name$\"\n" + " callOptions:callOptions];\n" " return self;\n" + "}\n\n" + "- (instancetype)initWithHost:(NSString *)host {\n" + " return [self initWithHost:host\n" + " packageName:@\"$package$\"\n" + " serviceName:@\"$service_name$\"\n" + " callOptions:nil];\n" "}\n\n"); printer.Print( @@ -292,7 +381,11 @@ void PrintMethodImplementations(Printer* printer, printer.Print( "#pragma mark - Class Methods\n\n" "+ (instancetype)serviceWithHost:(NSString *)host {\n" - " return [[self alloc] initWithHost:host];\n" + " return [self serviceWithHost:host callOptions:nil];\n" + "}\n\n" + "+ (instancetype)serviceWithHost:(NSString *)host " + "callOptions:(GRPCCallOptions *_Nullable)callOptions {\n" + " return [[self alloc] initWithHost:host callOptions:callOptions];\n" "}\n\n"); printer.Print("#pragma mark - Method Implementations\n\n"); diff --git a/src/compiler/objective_c_generator.h b/src/compiler/objective_c_generator.h index eb1c7ff005a..c171e5bf772 100644 --- a/src/compiler/objective_c_generator.h +++ b/src/compiler/objective_c_generator.h @@ -32,9 +32,14 @@ using ::grpc::string; string GetAllMessageClasses(const FileDescriptor* file); // Returns the content to be included defining the @protocol segment at the -// insertion point of the generated implementation file. +// insertion point of the generated implementation file. This interface is +// legacy and for backwards compatibility. string GetProtocol(const ServiceDescriptor* service); +// Returns the content to be included defining the @protocol segment at the +// insertion point of the generated implementation file. +string GetV2Protocol(const ServiceDescriptor* service); + // Returns the content to be included defining the @interface segment at the // insertion point of the generated implementation file. string GetInterface(const ServiceDescriptor* service); diff --git a/src/compiler/objective_c_plugin.cc b/src/compiler/objective_c_plugin.cc index f0fe3688cca..d0ef9ed0d62 100644 --- a/src/compiler/objective_c_plugin.cc +++ b/src/compiler/objective_c_plugin.cc @@ -93,7 +93,13 @@ class ObjectiveCGrpcGenerator : public grpc::protobuf::compiler::CodeGenerator { SystemImport("RxLibrary/GRXWriteable.h") + SystemImport("RxLibrary/GRXWriter.h"); - ::grpc::string forward_declarations = "@class GRPCProtoCall;\n\n"; + ::grpc::string forward_declarations = + "@class GRPCProtoCall;\n" + "@class GRPCUnaryProtoCall;\n" + "@class GRPCStreamingProtoCall;\n" + "@class GRPCCallOptions;\n" + "@protocol GRPCResponseHandler;\n" + "\n"; ::grpc::string class_declarations = grpc_objective_c_generator::GetAllMessageClasses(file); @@ -103,6 +109,12 @@ class ObjectiveCGrpcGenerator : public grpc::protobuf::compiler::CodeGenerator { class_imports += ImportProtoHeaders(file->dependency(i), " "); } + ::grpc::string ng_protocols; + for (int i = 0; i < file->service_count(); i++) { + const grpc::protobuf::ServiceDescriptor* service = file->service(i); + ng_protocols += grpc_objective_c_generator::GetV2Protocol(service); + } + ::grpc::string protocols; for (int i = 0; i < file->service_count(); i++) { const grpc::protobuf::ServiceDescriptor* service = file->service(i); @@ -120,9 +132,10 @@ class ObjectiveCGrpcGenerator : public grpc::protobuf::compiler::CodeGenerator { PreprocIfNot(kProtocolOnly, system_imports) + "\n" + class_declarations + "\n" + PreprocIfNot(kForwardDeclare, class_imports) + "\n" + - forward_declarations + "\n" + kNonNullBegin + "\n" + protocols + - "\n" + PreprocIfNot(kProtocolOnly, interfaces) + "\n" + - kNonNullEnd + "\n"); + forward_declarations + "\n" + kNonNullBegin + "\n" + + ng_protocols + protocols + "\n" + + PreprocIfNot(kProtocolOnly, interfaces) + "\n" + kNonNullEnd + + "\n"); } { diff --git a/src/objective-c/ProtoRPC/ProtoRPC.h b/src/objective-c/ProtoRPC/ProtoRPC.h index 45d35260921..1a27cac2a32 100644 --- a/src/objective-c/ProtoRPC/ProtoRPC.h +++ b/src/objective-c/ProtoRPC/ProtoRPC.h @@ -21,6 +21,55 @@ #import "ProtoMethod.h" +@class GPBMessage; + +/** A unary-request RPC call with Protobuf. */ +@interface GRPCUnaryProtoCall : NSObject + +/** + * Users should not use this initializer directly. Call objects will be created, initialized, and + * returned to users by methods of the generated service. + */ +- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions + message:(GPBMessage *)message + responseHandler:(id)handler + callOptions:(GRPCCallOptions *)callOptions + responseClass:(Class)responseClass; + +/** Cancel the call at best effort. */ +- (void)cancel; + +@end + +/** A client-streaming RPC call with Protobuf. */ +@interface GRPCStreamingProtoCall : NSObject + +/** + * Users should not use this initializer directly. Call objects will be created, initialized, and + * returned to users by methods of the generated service. + */ +- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions + responseHandler:(id)handler + callOptions:(GRPCCallOptions *)callOptions + responseClass:(Class)responseClass; + +/** Cancel the call at best effort. */ +- (void)cancel; + +/** + * Send a message to the server. The message should be a Protobuf message which will be serialized + * internally. + */ +- (void)writeWithMessage:(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 + __attribute__((deprecated("Please use GRPCProtoCall."))) @interface ProtoRPC : GRPCCall diff --git a/src/objective-c/ProtoRPC/ProtoRPC.m b/src/objective-c/ProtoRPC/ProtoRPC.m index 5dca971b08a..f89c1606507 100644 --- a/src/objective-c/ProtoRPC/ProtoRPC.m +++ b/src/objective-c/ProtoRPC/ProtoRPC.m @@ -23,9 +23,166 @@ #else #import #endif +#import #import #import +@implementation GRPCUnaryProtoCall { + GRPCStreamingProtoCall *_call; +} + +- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions + message:(GPBMessage *)message + responseHandler:(id)handler + callOptions:(GRPCCallOptions *)callOptions + responseClass:(Class)responseClass { + if ((self = [super init])) { + _call = [[GRPCStreamingProtoCall alloc] initWithRequestOptions:requestOptions + responseHandler:handler + callOptions:callOptions + responseClass:responseClass]; + [_call writeWithMessage:message]; + [_call finish]; + } + return self; +} + +- (void)cancel { + [_call cancel]; + _call = nil; +} + +@end + +@interface GRPCStreamingProtoCall () + +@end + +@implementation GRPCStreamingProtoCall { + GRPCRequestOptions *_requestOptions; + id _handler; + GRPCCallOptions *_callOptions; + Class _responseClass; + + GRPCCall2 *_call; + dispatch_queue_t _dispatchQueue; +} + +- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions + responseHandler:(id)handler + callOptions:(GRPCCallOptions *)callOptions + responseClass:(Class)responseClass { + if ((self = [super init])) { + _requestOptions = [requestOptions copy]; + _handler = handler; + _callOptions = [callOptions copy]; + _responseClass = responseClass; + _dispatchQueue = dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL); + + [self start]; + } + return self; +} + +- (void)start { + _call = [[GRPCCall2 alloc] initWithRequestOptions:_requestOptions + handler:self + callOptions:_callOptions]; + [_call start]; +} + +- (void)cancel { + dispatch_async(_dispatchQueue, ^{ + if (_call) { + [_call cancel]; + _call = nil; + } + if (_handler) { + id handler = _handler; + dispatch_async(handler.dispatchQueue, ^{ + [handler closedWithTrailingMetadata:nil + error:[NSError errorWithDomain:kGRPCErrorDomain + code:GRPCErrorCodeCancelled + userInfo:@{ + NSLocalizedDescriptionKey : + @"Canceled by app" + }]]; + }); + _handler = nil; + } + }); +} + +- (void)writeWithMessage:(GPBMessage *)message { + if (![message isKindOfClass:[GPBMessage class]]) { + [NSException raise:NSInvalidArgumentException format:@"Data must be a valid protobuf type."]; + } + + dispatch_async(_dispatchQueue, ^{ + if (_call) { + [_call writeWithData:[message data]]; + } + }); +} + +- (void)finish { + dispatch_async(_dispatchQueue, ^{ + if (_call) { + [_call finish]; + _call = nil; + } + }); +} + +- (void)receivedInitialMetadata:(NSDictionary *)initialMetadata { + if (_handler) { + id handler = _handler; + dispatch_async(handler.dispatchQueue, ^{ + [handler receivedInitialMetadata:initialMetadata]; + }); + } +} + +- (void)receivedMessage:(NSData *)message { + if (_handler) { + id handler = _handler; + NSError *error = nil; + id parsed = [_responseClass parseFromData:message error:&error]; + if (parsed) { + dispatch_async(handler.dispatchQueue, ^{ + [handler receivedMessage:parsed]; + }); + } else { + dispatch_async(handler.dispatchQueue, ^{ + [handler closedWithTrailingMetadata:nil error:error]; + }); + handler = nil; + [_call cancel]; + _call = nil; + } + } +} + +- (void)closedWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error { + if (_handler) { + id handler = _handler; + dispatch_async(handler.dispatchQueue, ^{ + [handler closedWithTrailingMetadata:trailingMetadata error:error]; + }); + _handler = nil; + } + if (_call) { + [_call cancel]; + _call = nil; + } +} + +- (dispatch_queue_t)dispatchQueue { + return _dispatchQueue; +} + +@end + static NSError *ErrorForBadProto(id proto, Class expectedClass, NSError *parsingError) { NSDictionary *info = @{ NSLocalizedDescriptionKey : @"Unable to parse response from the server", diff --git a/src/objective-c/ProtoRPC/ProtoService.h b/src/objective-c/ProtoRPC/ProtoService.h index 29c4e9be360..2105de78a38 100644 --- a/src/objective-c/ProtoRPC/ProtoService.h +++ b/src/objective-c/ProtoRPC/ProtoService.h @@ -21,16 +21,40 @@ @class GRPCProtoCall; @protocol GRXWriteable; @class GRXWriter; +@class GRPCCallOptions; +@class GRPCProtoCall; +@class GRPCUnaryProtoCall; +@class GRPCStreamingProtoCall; +@protocol GRPCProtoResponseCallbacks; __attribute__((deprecated("Please use GRPCProtoService."))) @interface ProtoService - : NSObject - + : NSObject + + - (instancetype)initWithHost : (NSString *)host packageName - : (NSString *)packageName serviceName : (NSString *)serviceName NS_DESIGNATED_INITIALIZER; + : (NSString *)packageName serviceName : (NSString *)serviceName callOptions + : (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)responsesWriteable; + +- (GRPCUnaryProtoCall *)RPCToMethod:(NSString *)method + message:(id)message + responseHandler:(id)handler + callOptions:(GRPCCallOptions *)callOptions + responseClass:(Class)responseClass; + +- (GRPCStreamingProtoCall *)RPCToMethod:(NSString *)method + responseHandler:(id)handler + callOptions:(GRPCCallOptions *)callOptions + responseClass:(Class)responseClass; + @end /** diff --git a/src/objective-c/ProtoRPC/ProtoService.m b/src/objective-c/ProtoRPC/ProtoService.m index 3e9cc5c0b2d..6df502fb0c0 100644 --- a/src/objective-c/ProtoRPC/ProtoService.m +++ b/src/objective-c/ProtoRPC/ProtoService.m @@ -18,6 +18,7 @@ #import "ProtoService.h" +#import #import #import @@ -31,6 +32,7 @@ NSString *_host; NSString *_packageName; NSString *_serviceName; + GRPCCallOptions *_callOptions; } - (instancetype)init { @@ -40,7 +42,8 @@ // Designated initializer - (instancetype)initWithHost:(NSString *)host packageName:(NSString *)packageName - serviceName:(NSString *)serviceName { + serviceName:(NSString *)serviceName + callOptions:(GRPCCallOptions *)callOptions { if (!host || !serviceName) { [NSException raise:NSInvalidArgumentException format:@"Neither host nor serviceName can be nil."]; @@ -49,10 +52,17 @@ _host = [host copy]; _packageName = [packageName copy]; _serviceName = [serviceName copy]; + _callOptions = [callOptions copy]; } return self; } +- (instancetype)initWithHost:(NSString *)host + packageName:(NSString *)packageName + serviceName:(NSString *)serviceName { + return [self initWithHost:host packageName:packageName serviceName:serviceName callOptions:nil]; +} + - (GRPCProtoCall *)RPCToMethod:(NSString *)method requestsWriter:(GRXWriter *)requestsWriter responseClass:(Class)responseClass @@ -65,6 +75,41 @@ responseClass:responseClass responsesWriteable:responsesWriteable]; } + +- (GRPCUnaryProtoCall *)RPCToMethod:(NSString *)method + message:(id)message + responseHandler:(id)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)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