xref: /aosp_15_r20/external/grpc-grpc/src/objective-c/ProtoRPC/ProtoRPC.m (revision cc02d7e222339f7a4f6ba5f422e6413f4bd931f2)
1*cc02d7e2SAndroid Build Coastguard Worker/*
2*cc02d7e2SAndroid Build Coastguard Worker *
3*cc02d7e2SAndroid Build Coastguard Worker * Copyright 2015 gRPC authors.
4*cc02d7e2SAndroid Build Coastguard Worker *
5*cc02d7e2SAndroid Build Coastguard Worker * Licensed under the Apache License, Version 2.0 (the "License");
6*cc02d7e2SAndroid Build Coastguard Worker * you may not use this file except in compliance with the License.
7*cc02d7e2SAndroid Build Coastguard Worker * You may obtain a copy of the License at
8*cc02d7e2SAndroid Build Coastguard Worker *
9*cc02d7e2SAndroid Build Coastguard Worker *     http://www.apache.org/licenses/LICENSE-2.0
10*cc02d7e2SAndroid Build Coastguard Worker *
11*cc02d7e2SAndroid Build Coastguard Worker * Unless required by applicable law or agreed to in writing, software
12*cc02d7e2SAndroid Build Coastguard Worker * distributed under the License is distributed on an "AS IS" BASIS,
13*cc02d7e2SAndroid Build Coastguard Worker * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14*cc02d7e2SAndroid Build Coastguard Worker * See the License for the specific language governing permissions and
15*cc02d7e2SAndroid Build Coastguard Worker * limitations under the License.
16*cc02d7e2SAndroid Build Coastguard Worker *
17*cc02d7e2SAndroid Build Coastguard Worker */
18*cc02d7e2SAndroid Build Coastguard Worker
19*cc02d7e2SAndroid Build Coastguard Worker#import "ProtoRPC.h"
20*cc02d7e2SAndroid Build Coastguard Worker
21*cc02d7e2SAndroid Build Coastguard Worker#if GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS
22*cc02d7e2SAndroid Build Coastguard Worker#import <Protobuf/GPBProtocolBuffers.h>
23*cc02d7e2SAndroid Build Coastguard Worker#else
24*cc02d7e2SAndroid Build Coastguard Worker#import <GPBProtocolBuffers.h>
25*cc02d7e2SAndroid Build Coastguard Worker#endif
26*cc02d7e2SAndroid Build Coastguard Worker#import <GRPCClient/GRPCCall.h>
27*cc02d7e2SAndroid Build Coastguard Worker#import <RxLibrary/GRXWriteable.h>
28*cc02d7e2SAndroid Build Coastguard Worker#import <RxLibrary/GRXWriter+Transformations.h>
29*cc02d7e2SAndroid Build Coastguard Worker
30*cc02d7e2SAndroid Build Coastguard Worker@implementation GRPCUnaryResponseHandler {
31*cc02d7e2SAndroid Build Coastguard Worker  void (^_responseHandler)(id, NSError *);
32*cc02d7e2SAndroid Build Coastguard Worker  dispatch_queue_t _responseDispatchQueue;
33*cc02d7e2SAndroid Build Coastguard Worker
34*cc02d7e2SAndroid Build Coastguard Worker  GPBMessage *_message;
35*cc02d7e2SAndroid Build Coastguard Worker}
36*cc02d7e2SAndroid Build Coastguard Worker
37*cc02d7e2SAndroid Build Coastguard Worker- (nullable instancetype)initWithResponseHandler:(void (^)(id, NSError *))handler
38*cc02d7e2SAndroid Build Coastguard Worker                           responseDispatchQueue:(dispatch_queue_t)dispatchQueue {
39*cc02d7e2SAndroid Build Coastguard Worker  if ((self = [super init])) {
40*cc02d7e2SAndroid Build Coastguard Worker    _responseHandler = handler;
41*cc02d7e2SAndroid Build Coastguard Worker    if (dispatchQueue == nil) {
42*cc02d7e2SAndroid Build Coastguard Worker      _responseDispatchQueue = dispatch_get_main_queue();
43*cc02d7e2SAndroid Build Coastguard Worker    } else {
44*cc02d7e2SAndroid Build Coastguard Worker      _responseDispatchQueue = dispatchQueue;
45*cc02d7e2SAndroid Build Coastguard Worker    }
46*cc02d7e2SAndroid Build Coastguard Worker  }
47*cc02d7e2SAndroid Build Coastguard Worker  return self;
48*cc02d7e2SAndroid Build Coastguard Worker}
49*cc02d7e2SAndroid Build Coastguard Worker
50*cc02d7e2SAndroid Build Coastguard Worker// Implements GRPCProtoResponseHandler
51*cc02d7e2SAndroid Build Coastguard Worker- (dispatch_queue_t)dispatchQueue {
52*cc02d7e2SAndroid Build Coastguard Worker  return _responseDispatchQueue;
53*cc02d7e2SAndroid Build Coastguard Worker}
54*cc02d7e2SAndroid Build Coastguard Worker
55*cc02d7e2SAndroid Build Coastguard Worker- (void)didReceiveInitialMetadata:(NSDictionary *)initialMetadata {
56*cc02d7e2SAndroid Build Coastguard Worker  _responseHeaders = [initialMetadata copy];
57*cc02d7e2SAndroid Build Coastguard Worker}
58*cc02d7e2SAndroid Build Coastguard Worker
59*cc02d7e2SAndroid Build Coastguard Worker- (void)didReceiveProtoMessage:(GPBMessage *)message {
60*cc02d7e2SAndroid Build Coastguard Worker  _message = message;
61*cc02d7e2SAndroid Build Coastguard Worker}
62*cc02d7e2SAndroid Build Coastguard Worker
63*cc02d7e2SAndroid Build Coastguard Worker- (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error {
64*cc02d7e2SAndroid Build Coastguard Worker  _responseTrailers = [trailingMetadata copy];
65*cc02d7e2SAndroid Build Coastguard Worker  GPBMessage *message = _message;
66*cc02d7e2SAndroid Build Coastguard Worker  _message = nil;
67*cc02d7e2SAndroid Build Coastguard Worker  _responseHandler(message, error);
68*cc02d7e2SAndroid Build Coastguard Worker}
69*cc02d7e2SAndroid Build Coastguard Worker
70*cc02d7e2SAndroid Build Coastguard Worker// Intentional no-op since flow control is N/A in a unary call
71*cc02d7e2SAndroid Build Coastguard Worker- (void)didWriteMessage {
72*cc02d7e2SAndroid Build Coastguard Worker}
73*cc02d7e2SAndroid Build Coastguard Worker
74*cc02d7e2SAndroid Build Coastguard Worker@end
75*cc02d7e2SAndroid Build Coastguard Worker
76*cc02d7e2SAndroid Build Coastguard Worker@implementation GRPCUnaryProtoCall {
77*cc02d7e2SAndroid Build Coastguard Worker  GRPCStreamingProtoCall *_call;
78*cc02d7e2SAndroid Build Coastguard Worker  GPBMessage *_message;
79*cc02d7e2SAndroid Build Coastguard Worker}
80*cc02d7e2SAndroid Build Coastguard Worker
81*cc02d7e2SAndroid Build Coastguard Worker- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions
82*cc02d7e2SAndroid Build Coastguard Worker                               message:(GPBMessage *)message
83*cc02d7e2SAndroid Build Coastguard Worker                       responseHandler:(id<GRPCProtoResponseHandler>)handler
84*cc02d7e2SAndroid Build Coastguard Worker                           callOptions:(GRPCCallOptions *)callOptions
85*cc02d7e2SAndroid Build Coastguard Worker                         responseClass:(Class)responseClass {
86*cc02d7e2SAndroid Build Coastguard Worker  NSAssert(message != nil, @"message cannot be empty.");
87*cc02d7e2SAndroid Build Coastguard Worker  NSAssert(responseClass != nil, @"responseClass cannot be empty.");
88*cc02d7e2SAndroid Build Coastguard Worker  if (message == nil || responseClass == nil) {
89*cc02d7e2SAndroid Build Coastguard Worker    return nil;
90*cc02d7e2SAndroid Build Coastguard Worker  }
91*cc02d7e2SAndroid Build Coastguard Worker  if ((self = [super init])) {
92*cc02d7e2SAndroid Build Coastguard Worker    _call = [[GRPCStreamingProtoCall alloc] initWithRequestOptions:requestOptions
93*cc02d7e2SAndroid Build Coastguard Worker                                                   responseHandler:handler
94*cc02d7e2SAndroid Build Coastguard Worker                                                       callOptions:callOptions
95*cc02d7e2SAndroid Build Coastguard Worker                                                     responseClass:responseClass];
96*cc02d7e2SAndroid Build Coastguard Worker    _message = [message copy];
97*cc02d7e2SAndroid Build Coastguard Worker  }
98*cc02d7e2SAndroid Build Coastguard Worker  return self;
99*cc02d7e2SAndroid Build Coastguard Worker}
100*cc02d7e2SAndroid Build Coastguard Worker
101*cc02d7e2SAndroid Build Coastguard Worker- (void)start {
102*cc02d7e2SAndroid Build Coastguard Worker  [_call start];
103*cc02d7e2SAndroid Build Coastguard Worker  [_call receiveNextMessage];
104*cc02d7e2SAndroid Build Coastguard Worker  [_call writeMessage:_message];
105*cc02d7e2SAndroid Build Coastguard Worker  [_call finish];
106*cc02d7e2SAndroid Build Coastguard Worker}
107*cc02d7e2SAndroid Build Coastguard Worker
108*cc02d7e2SAndroid Build Coastguard Worker- (void)cancel {
109*cc02d7e2SAndroid Build Coastguard Worker  [_call cancel];
110*cc02d7e2SAndroid Build Coastguard Worker}
111*cc02d7e2SAndroid Build Coastguard Worker
112*cc02d7e2SAndroid Build Coastguard Worker@end
113*cc02d7e2SAndroid Build Coastguard Worker
114*cc02d7e2SAndroid Build Coastguard Worker@interface GRPCStreamingProtoCall () <GRPCResponseHandler>
115*cc02d7e2SAndroid Build Coastguard Worker
116*cc02d7e2SAndroid Build Coastguard Worker@end
117*cc02d7e2SAndroid Build Coastguard Worker
118*cc02d7e2SAndroid Build Coastguard Worker@implementation GRPCStreamingProtoCall {
119*cc02d7e2SAndroid Build Coastguard Worker  GRPCRequestOptions *_requestOptions;
120*cc02d7e2SAndroid Build Coastguard Worker  id<GRPCProtoResponseHandler> _handler;
121*cc02d7e2SAndroid Build Coastguard Worker  GRPCCallOptions *_callOptions;
122*cc02d7e2SAndroid Build Coastguard Worker  Class _responseClass;
123*cc02d7e2SAndroid Build Coastguard Worker
124*cc02d7e2SAndroid Build Coastguard Worker  GRPCCall2 *_call;
125*cc02d7e2SAndroid Build Coastguard Worker  dispatch_queue_t _dispatchQueue;
126*cc02d7e2SAndroid Build Coastguard Worker}
127*cc02d7e2SAndroid Build Coastguard Worker
128*cc02d7e2SAndroid Build Coastguard Worker- (instancetype)initWithRequestOptions:(GRPCRequestOptions *)requestOptions
129*cc02d7e2SAndroid Build Coastguard Worker                       responseHandler:(id<GRPCProtoResponseHandler>)handler
130*cc02d7e2SAndroid Build Coastguard Worker                           callOptions:(GRPCCallOptions *)callOptions
131*cc02d7e2SAndroid Build Coastguard Worker                         responseClass:(Class)responseClass {
132*cc02d7e2SAndroid Build Coastguard Worker  NSAssert(requestOptions.host.length != 0 && requestOptions.path.length != 0,
133*cc02d7e2SAndroid Build Coastguard Worker           @"Invalid callOptions.");
134*cc02d7e2SAndroid Build Coastguard Worker  NSAssert(handler != nil, @"handler cannot be empty.");
135*cc02d7e2SAndroid Build Coastguard Worker  if (requestOptions.host.length == 0 || requestOptions.path.length == 0) {
136*cc02d7e2SAndroid Build Coastguard Worker    return nil;
137*cc02d7e2SAndroid Build Coastguard Worker  }
138*cc02d7e2SAndroid Build Coastguard Worker  if (handler == nil) {
139*cc02d7e2SAndroid Build Coastguard Worker    return nil;
140*cc02d7e2SAndroid Build Coastguard Worker  }
141*cc02d7e2SAndroid Build Coastguard Worker
142*cc02d7e2SAndroid Build Coastguard Worker  if ((self = [super init])) {
143*cc02d7e2SAndroid Build Coastguard Worker    _requestOptions = [requestOptions copy];
144*cc02d7e2SAndroid Build Coastguard Worker    _handler = handler;
145*cc02d7e2SAndroid Build Coastguard Worker    _callOptions = [callOptions copy];
146*cc02d7e2SAndroid Build Coastguard Worker    _responseClass = responseClass;
147*cc02d7e2SAndroid Build Coastguard Worker
148*cc02d7e2SAndroid Build Coastguard Worker    // Set queue QoS only when iOS version is 8.0 or above and Xcode version is 9.0 or above
149*cc02d7e2SAndroid Build Coastguard Worker#if __IPHONE_OS_VERSION_MAX_ALLOWED < 110000 || __MAC_OS_X_VERSION_MAX_ALLOWED < 101300
150*cc02d7e2SAndroid Build Coastguard Worker    if (@available(iOS 8.0, macOS 10.10, *)) {
151*cc02d7e2SAndroid Build Coastguard Worker      _dispatchQueue = dispatch_queue_create(
152*cc02d7e2SAndroid Build Coastguard Worker          NULL,
153*cc02d7e2SAndroid Build Coastguard Worker          dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0));
154*cc02d7e2SAndroid Build Coastguard Worker    } else {
155*cc02d7e2SAndroid Build Coastguard Worker#else
156*cc02d7e2SAndroid Build Coastguard Worker    {
157*cc02d7e2SAndroid Build Coastguard Worker#endif
158*cc02d7e2SAndroid Build Coastguard Worker      _dispatchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
159*cc02d7e2SAndroid Build Coastguard Worker    }
160*cc02d7e2SAndroid Build Coastguard Worker    dispatch_set_target_queue(_dispatchQueue, handler.dispatchQueue);
161*cc02d7e2SAndroid Build Coastguard Worker
162*cc02d7e2SAndroid Build Coastguard Worker    _call = [[GRPCCall2 alloc] initWithRequestOptions:_requestOptions
163*cc02d7e2SAndroid Build Coastguard Worker                                      responseHandler:self
164*cc02d7e2SAndroid Build Coastguard Worker                                          callOptions:_callOptions];
165*cc02d7e2SAndroid Build Coastguard Worker  }
166*cc02d7e2SAndroid Build Coastguard Worker  return self;
167*cc02d7e2SAndroid Build Coastguard Worker}
168*cc02d7e2SAndroid Build Coastguard Worker
169*cc02d7e2SAndroid Build Coastguard Worker- (void)start {
170*cc02d7e2SAndroid Build Coastguard Worker  GRPCCall2 *copiedCall;
171*cc02d7e2SAndroid Build Coastguard Worker  @synchronized(self) {
172*cc02d7e2SAndroid Build Coastguard Worker    copiedCall = _call;
173*cc02d7e2SAndroid Build Coastguard Worker  }
174*cc02d7e2SAndroid Build Coastguard Worker  [copiedCall start];
175*cc02d7e2SAndroid Build Coastguard Worker}
176*cc02d7e2SAndroid Build Coastguard Worker
177*cc02d7e2SAndroid Build Coastguard Worker- (void)cancel {
178*cc02d7e2SAndroid Build Coastguard Worker  GRPCCall2 *copiedCall;
179*cc02d7e2SAndroid Build Coastguard Worker  @synchronized(self) {
180*cc02d7e2SAndroid Build Coastguard Worker    copiedCall = _call;
181*cc02d7e2SAndroid Build Coastguard Worker    _call = nil;
182*cc02d7e2SAndroid Build Coastguard Worker    if ([_handler respondsToSelector:@selector(didCloseWithTrailingMetadata:error:)]) {
183*cc02d7e2SAndroid Build Coastguard Worker      dispatch_async(_dispatchQueue, ^{
184*cc02d7e2SAndroid Build Coastguard Worker        id<GRPCProtoResponseHandler> copiedHandler = nil;
185*cc02d7e2SAndroid Build Coastguard Worker        @synchronized(self) {
186*cc02d7e2SAndroid Build Coastguard Worker          copiedHandler = self->_handler;
187*cc02d7e2SAndroid Build Coastguard Worker          self->_handler = nil;
188*cc02d7e2SAndroid Build Coastguard Worker        }
189*cc02d7e2SAndroid Build Coastguard Worker        [copiedHandler didCloseWithTrailingMetadata:nil
190*cc02d7e2SAndroid Build Coastguard Worker                                              error:[NSError errorWithDomain:kGRPCErrorDomain
191*cc02d7e2SAndroid Build Coastguard Worker                                                                        code:GRPCErrorCodeCancelled
192*cc02d7e2SAndroid Build Coastguard Worker                                                                    userInfo:@{
193*cc02d7e2SAndroid Build Coastguard Worker                                                                      NSLocalizedDescriptionKey :
194*cc02d7e2SAndroid Build Coastguard Worker                                                                          @"Canceled by app"
195*cc02d7e2SAndroid Build Coastguard Worker                                                                    }]];
196*cc02d7e2SAndroid Build Coastguard Worker      });
197*cc02d7e2SAndroid Build Coastguard Worker    } else {
198*cc02d7e2SAndroid Build Coastguard Worker      _handler = nil;
199*cc02d7e2SAndroid Build Coastguard Worker    }
200*cc02d7e2SAndroid Build Coastguard Worker  }
201*cc02d7e2SAndroid Build Coastguard Worker  [copiedCall cancel];
202*cc02d7e2SAndroid Build Coastguard Worker}
203*cc02d7e2SAndroid Build Coastguard Worker
204*cc02d7e2SAndroid Build Coastguard Worker- (void)writeMessage:(GPBMessage *)message {
205*cc02d7e2SAndroid Build Coastguard Worker  NSAssert([message isKindOfClass:[GPBMessage class]], @"Parameter message must be a GPBMessage");
206*cc02d7e2SAndroid Build Coastguard Worker  if (![message isKindOfClass:[GPBMessage class]]) {
207*cc02d7e2SAndroid Build Coastguard Worker    NSLog(@"Failed to send a message that is non-proto.");
208*cc02d7e2SAndroid Build Coastguard Worker    return;
209*cc02d7e2SAndroid Build Coastguard Worker  }
210*cc02d7e2SAndroid Build Coastguard Worker
211*cc02d7e2SAndroid Build Coastguard Worker  GRPCCall2 *copiedCall;
212*cc02d7e2SAndroid Build Coastguard Worker  @synchronized(self) {
213*cc02d7e2SAndroid Build Coastguard Worker    copiedCall = _call;
214*cc02d7e2SAndroid Build Coastguard Worker  }
215*cc02d7e2SAndroid Build Coastguard Worker  [copiedCall writeData:[message data]];
216*cc02d7e2SAndroid Build Coastguard Worker}
217*cc02d7e2SAndroid Build Coastguard Worker
218*cc02d7e2SAndroid Build Coastguard Worker- (void)finish {
219*cc02d7e2SAndroid Build Coastguard Worker  GRPCCall2 *copiedCall;
220*cc02d7e2SAndroid Build Coastguard Worker  @synchronized(self) {
221*cc02d7e2SAndroid Build Coastguard Worker    copiedCall = _call;
222*cc02d7e2SAndroid Build Coastguard Worker    _call = nil;
223*cc02d7e2SAndroid Build Coastguard Worker  }
224*cc02d7e2SAndroid Build Coastguard Worker  [copiedCall finish];
225*cc02d7e2SAndroid Build Coastguard Worker}
226*cc02d7e2SAndroid Build Coastguard Worker
227*cc02d7e2SAndroid Build Coastguard Worker- (void)receiveNextMessage {
228*cc02d7e2SAndroid Build Coastguard Worker  [self receiveNextMessages:1];
229*cc02d7e2SAndroid Build Coastguard Worker}
230*cc02d7e2SAndroid Build Coastguard Worker- (void)receiveNextMessages:(NSUInteger)numberOfMessages {
231*cc02d7e2SAndroid Build Coastguard Worker  GRPCCall2 *copiedCall;
232*cc02d7e2SAndroid Build Coastguard Worker  @synchronized(self) {
233*cc02d7e2SAndroid Build Coastguard Worker    copiedCall = _call;
234*cc02d7e2SAndroid Build Coastguard Worker  }
235*cc02d7e2SAndroid Build Coastguard Worker  [copiedCall receiveNextMessages:numberOfMessages];
236*cc02d7e2SAndroid Build Coastguard Worker}
237*cc02d7e2SAndroid Build Coastguard Worker
238*cc02d7e2SAndroid Build Coastguard Worker- (void)didReceiveInitialMetadata:(NSDictionary *)initialMetadata {
239*cc02d7e2SAndroid Build Coastguard Worker  @synchronized(self) {
240*cc02d7e2SAndroid Build Coastguard Worker    if (initialMetadata != nil &&
241*cc02d7e2SAndroid Build Coastguard Worker        [_handler respondsToSelector:@selector(didReceiveInitialMetadata:)]) {
242*cc02d7e2SAndroid Build Coastguard Worker      dispatch_async(_dispatchQueue, ^{
243*cc02d7e2SAndroid Build Coastguard Worker        id<GRPCProtoResponseHandler> copiedHandler = nil;
244*cc02d7e2SAndroid Build Coastguard Worker        @synchronized(self) {
245*cc02d7e2SAndroid Build Coastguard Worker          copiedHandler = self->_handler;
246*cc02d7e2SAndroid Build Coastguard Worker        }
247*cc02d7e2SAndroid Build Coastguard Worker        [copiedHandler didReceiveInitialMetadata:initialMetadata];
248*cc02d7e2SAndroid Build Coastguard Worker      });
249*cc02d7e2SAndroid Build Coastguard Worker    }
250*cc02d7e2SAndroid Build Coastguard Worker  }
251*cc02d7e2SAndroid Build Coastguard Worker}
252*cc02d7e2SAndroid Build Coastguard Worker
253*cc02d7e2SAndroid Build Coastguard Worker- (void)didReceiveData:(id)data {
254*cc02d7e2SAndroid Build Coastguard Worker  if (data == nil) return;
255*cc02d7e2SAndroid Build Coastguard Worker
256*cc02d7e2SAndroid Build Coastguard Worker  NSError *error = nil;
257*cc02d7e2SAndroid Build Coastguard Worker  GPBMessage *parsed = [_responseClass parseFromData:data error:&error];
258*cc02d7e2SAndroid Build Coastguard Worker  @synchronized(self) {
259*cc02d7e2SAndroid Build Coastguard Worker    if (parsed && [_handler respondsToSelector:@selector(didReceiveProtoMessage:)]) {
260*cc02d7e2SAndroid Build Coastguard Worker      dispatch_async(_dispatchQueue, ^{
261*cc02d7e2SAndroid Build Coastguard Worker        id<GRPCProtoResponseHandler> copiedHandler = nil;
262*cc02d7e2SAndroid Build Coastguard Worker        @synchronized(self) {
263*cc02d7e2SAndroid Build Coastguard Worker          copiedHandler = self->_handler;
264*cc02d7e2SAndroid Build Coastguard Worker        }
265*cc02d7e2SAndroid Build Coastguard Worker        [copiedHandler didReceiveProtoMessage:parsed];
266*cc02d7e2SAndroid Build Coastguard Worker      });
267*cc02d7e2SAndroid Build Coastguard Worker    } else if (!parsed && [_handler respondsToSelector:@selector(didCloseWithTrailingMetadata:
268*cc02d7e2SAndroid Build Coastguard Worker                                                                                        error:)]) {
269*cc02d7e2SAndroid Build Coastguard Worker      dispatch_async(_dispatchQueue, ^{
270*cc02d7e2SAndroid Build Coastguard Worker        id<GRPCProtoResponseHandler> copiedHandler = nil;
271*cc02d7e2SAndroid Build Coastguard Worker        @synchronized(self) {
272*cc02d7e2SAndroid Build Coastguard Worker          copiedHandler = self->_handler;
273*cc02d7e2SAndroid Build Coastguard Worker          self->_handler = nil;
274*cc02d7e2SAndroid Build Coastguard Worker        }
275*cc02d7e2SAndroid Build Coastguard Worker        [copiedHandler
276*cc02d7e2SAndroid Build Coastguard Worker            didCloseWithTrailingMetadata:nil
277*cc02d7e2SAndroid Build Coastguard Worker                                   error:ErrorForBadProto(data, self->_responseClass, error)];
278*cc02d7e2SAndroid Build Coastguard Worker      });
279*cc02d7e2SAndroid Build Coastguard Worker      [_call cancel];
280*cc02d7e2SAndroid Build Coastguard Worker      _call = nil;
281*cc02d7e2SAndroid Build Coastguard Worker    }
282*cc02d7e2SAndroid Build Coastguard Worker  }
283*cc02d7e2SAndroid Build Coastguard Worker}
284*cc02d7e2SAndroid Build Coastguard Worker
285*cc02d7e2SAndroid Build Coastguard Worker- (void)didCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error {
286*cc02d7e2SAndroid Build Coastguard Worker  @synchronized(self) {
287*cc02d7e2SAndroid Build Coastguard Worker    if ([_handler respondsToSelector:@selector(didCloseWithTrailingMetadata:error:)]) {
288*cc02d7e2SAndroid Build Coastguard Worker      dispatch_async(_dispatchQueue, ^{
289*cc02d7e2SAndroid Build Coastguard Worker        id<GRPCProtoResponseHandler> copiedHandler = nil;
290*cc02d7e2SAndroid Build Coastguard Worker        @synchronized(self) {
291*cc02d7e2SAndroid Build Coastguard Worker          copiedHandler = self->_handler;
292*cc02d7e2SAndroid Build Coastguard Worker          self->_handler = nil;
293*cc02d7e2SAndroid Build Coastguard Worker        }
294*cc02d7e2SAndroid Build Coastguard Worker        [copiedHandler didCloseWithTrailingMetadata:trailingMetadata error:error];
295*cc02d7e2SAndroid Build Coastguard Worker      });
296*cc02d7e2SAndroid Build Coastguard Worker    }
297*cc02d7e2SAndroid Build Coastguard Worker    _call = nil;
298*cc02d7e2SAndroid Build Coastguard Worker  }
299*cc02d7e2SAndroid Build Coastguard Worker}
300*cc02d7e2SAndroid Build Coastguard Worker
301*cc02d7e2SAndroid Build Coastguard Worker- (void)didWriteData {
302*cc02d7e2SAndroid Build Coastguard Worker  @synchronized(self) {
303*cc02d7e2SAndroid Build Coastguard Worker    if ([_handler respondsToSelector:@selector(didWriteMessage)]) {
304*cc02d7e2SAndroid Build Coastguard Worker      dispatch_async(_dispatchQueue, ^{
305*cc02d7e2SAndroid Build Coastguard Worker        id<GRPCProtoResponseHandler> copiedHandler = nil;
306*cc02d7e2SAndroid Build Coastguard Worker        @synchronized(self) {
307*cc02d7e2SAndroid Build Coastguard Worker          copiedHandler = self->_handler;
308*cc02d7e2SAndroid Build Coastguard Worker        }
309*cc02d7e2SAndroid Build Coastguard Worker        [copiedHandler didWriteMessage];
310*cc02d7e2SAndroid Build Coastguard Worker      });
311*cc02d7e2SAndroid Build Coastguard Worker    }
312*cc02d7e2SAndroid Build Coastguard Worker  }
313*cc02d7e2SAndroid Build Coastguard Worker}
314*cc02d7e2SAndroid Build Coastguard Worker
315*cc02d7e2SAndroid Build Coastguard Worker- (dispatch_queue_t)dispatchQueue {
316*cc02d7e2SAndroid Build Coastguard Worker  return _dispatchQueue;
317*cc02d7e2SAndroid Build Coastguard Worker}
318*cc02d7e2SAndroid Build Coastguard Worker
319*cc02d7e2SAndroid Build Coastguard Worker@end
320*cc02d7e2SAndroid Build Coastguard Worker
321*cc02d7e2SAndroid Build Coastguard Worker/**
322*cc02d7e2SAndroid Build Coastguard Worker * Generate an NSError object that represents a failure in parsing a proto class.
323*cc02d7e2SAndroid Build Coastguard Worker */
324*cc02d7e2SAndroid Build Coastguard WorkerNSError *ErrorForBadProto(id proto, Class expectedClass, NSError *parsingError) {
325*cc02d7e2SAndroid Build Coastguard Worker  NSDictionary *info = @{
326*cc02d7e2SAndroid Build Coastguard Worker    NSLocalizedDescriptionKey : @"Unable to parse response from the server",
327*cc02d7e2SAndroid Build Coastguard Worker    NSLocalizedRecoverySuggestionErrorKey :
328*cc02d7e2SAndroid Build Coastguard Worker        @"If this RPC is idempotent, retry "
329*cc02d7e2SAndroid Build Coastguard Worker        @"with exponential backoff. Otherwise, query the server status before "
330*cc02d7e2SAndroid Build Coastguard Worker        @"retrying.",
331*cc02d7e2SAndroid Build Coastguard Worker    NSUnderlyingErrorKey : parsingError,
332*cc02d7e2SAndroid Build Coastguard Worker    @"Expected class" : expectedClass,
333*cc02d7e2SAndroid Build Coastguard Worker    @"Received value" : proto,
334*cc02d7e2SAndroid Build Coastguard Worker  };
335*cc02d7e2SAndroid Build Coastguard Worker  // TODO(jcanizales): Use kGRPCErrorDomain and GRPCErrorCodeInternal when they're public.
336*cc02d7e2SAndroid Build Coastguard Worker  return [NSError errorWithDomain:@"io.grpc" code:13 userInfo:info];
337*cc02d7e2SAndroid Build Coastguard Worker}
338