@@ -219,6 +219,7 @@ @implementation GTLRService {
219
219
NSString *_overrideUserAgent;
220
220
NSDictionary *_serviceProperties; // Properties retained for the convenience of the client app.
221
221
NSUInteger _uploadChunkSize; // Only applies to resumable chunked uploads.
222
+ dispatch_queue_t _requestCreationQueue;
222
223
}
223
224
224
225
@synthesize additionalHTTPHeaders = _additionalHTTPHeaders,
@@ -250,6 +251,9 @@ - (instancetype)init {
250
251
if (self) {
251
252
_parseQueue = dispatch_queue_create (" com.google.GTLRServiceParse" , DISPATCH_QUEUE_SERIAL);
252
253
_callbackQueue = dispatch_get_main_queue ();
254
+ _requestCreationQueue =
255
+ dispatch_queue_create (" com.google.GTLRServiceRequestCreation" , DISPATCH_QUEUE_SERIAL);
256
+
253
257
_fetcherService = [[GTMSessionFetcherService alloc ] init ];
254
258
255
259
// Make the session fetcher use a background delegate queue instead of bouncing
@@ -319,10 +323,26 @@ - (void)setMainBundleIDRestrictionWithAPIKey:(NSString *)apiKey {
319
323
self.APIKeyRestrictionBundleID = [[NSBundle mainBundle ] bundleIdentifier ];
320
324
}
321
325
322
- - (NSMutableURLRequest *)requestForURL : (NSURL *)url
323
- ETag : (NSString *)etag
324
- httpMethod : (NSString *)httpMethod
325
- ticket : (GTLRServiceTicket *)ticket {
326
+ - (void )requestForURL : (NSURL *)url
327
+ ETag : (NSString *)etag
328
+ httpMethod : (NSString *)httpMethod
329
+ ticket : (GTLRServiceTicket *)ticket
330
+ completion : (void (^)(NSMutableURLRequest *))completion {
331
+ dispatch_async (_requestCreationQueue, ^{
332
+ NSMutableURLRequest *request = [self createRequestForURL: url
333
+ ETag: etag
334
+ httpMethod: httpMethod
335
+ ticket: ticket];
336
+ completion (request);
337
+ });
338
+ }
339
+
340
+ - (NSMutableURLRequest *)createRequestForURL : (NSURL *)url
341
+ ETag : (NSString *)etag
342
+ httpMethod : (NSString *)httpMethod
343
+ ticket : (GTLRServiceTicket *)ticket {
344
+ // This method may block, so make sure it's not on the caller's queue when executing a query.
345
+ dispatch_assert_queue_debug (_requestCreationQueue);
326
346
327
347
// subclasses may add headers to this
328
348
NSMutableURLRequest *request = [[NSMutableURLRequest alloc ] initWithURL: url
@@ -371,19 +391,20 @@ - (NSMutableURLRequest *)requestForURL:(NSURL *)url
371
391
return request;
372
392
}
373
393
374
- // objectRequestForURL returns an NSMutableURLRequest for a GTLRObject
394
+ // objectRequestForURL asynchronously returns an NSMutableURLRequest for a GTLRObject
375
395
//
376
396
// the object is the object being sent to the server, or nil;
377
397
// the http method may be nil for get, or POST, PUT, DELETE
378
398
379
- - (NSMutableURLRequest *)objectRequestForURL : (NSURL *)url
380
- object : (GTLRObject *)object
381
- contentType : (NSString *)contentType
382
- contentLength : (NSString *)contentLength
383
- ETag : (NSString *)etag
384
- httpMethod : (NSString *)httpMethod
385
- additionalHeaders : (NSDictionary *)additionalHeaders
386
- ticket : (GTLRServiceTicket *)ticket {
399
+ - (void )objectRequestForURL : (NSURL *)url
400
+ object : (GTLRObject *)object
401
+ contentType : (NSString *)contentType
402
+ contentLength : (NSString *)contentLength
403
+ ETag : (NSString *)etag
404
+ httpMethod : (NSString *)httpMethod
405
+ additionalHeaders : (NSDictionary *)additionalHeaders
406
+ ticket : (GTLRServiceTicket *)ticket
407
+ completion : (void (^)(NSMutableURLRequest *))completion {
387
408
if (object) {
388
409
// if the object being sent has an etag, add it to the request header to
389
410
// avoid retrieving a duplicate or to avoid writing over an updated
@@ -396,10 +417,23 @@ - (NSMutableURLRequest *)objectRequestForURL:(NSURL *)url
396
417
}
397
418
}
398
419
399
- NSMutableURLRequest *request = [self requestForURL: url
400
- ETag: etag
401
- httpMethod: httpMethod
402
- ticket: ticket];
420
+ [self requestForURL: url
421
+ ETag: etag
422
+ httpMethod: httpMethod
423
+ ticket: ticket
424
+ completion: ^(NSMutableURLRequest *request) {
425
+ [self handleRequestCompletion: request
426
+ contentType: contentType
427
+ contentLength: contentLength
428
+ additionalHeaders: additionalHeaders];
429
+ completion (request);
430
+ }];
431
+ }
432
+
433
+ - (void )handleRequestCompletion : (NSMutableURLRequest *)request
434
+ contentType : (NSString *)contentType
435
+ contentLength : (NSString *)contentLength
436
+ additionalHeaders : (NSDictionary <NSString *, NSString *> *)additionalHeaders {
403
437
[request setValue: @" application/json" forHTTPHeaderField: @" Accept" ];
404
438
[request setValue: contentType forHTTPHeaderField: @" Content-Type" ];
405
439
@@ -421,17 +455,43 @@ - (NSMutableURLRequest *)objectRequestForURL:(NSURL *)url
421
455
NSString *value = [headers objectForKey: key];
422
456
[request setValue: value forHTTPHeaderField: key];
423
457
}
424
-
425
- return request;
426
458
}
427
459
428
460
#pragma mark -
429
461
430
462
- (NSMutableURLRequest *)requestForQuery : (GTLRQuery *)query {
431
- GTLR_DEBUG_ASSERT (query.bodyObject == nil ,
432
- @" requestForQuery: supports only GET methods, but was passed: %@ " , query);
463
+ // This public API will block the calling thread waiting for a completion to be dispatched onto
464
+ // this queue, so it cannot be called on this queue.
465
+ //
466
+ // Thankfully, this queue is an internal implementation detail, so this just guards against
467
+ // someone in the future accidentally calling this method on the request creation queue.
468
+ dispatch_assert_queue_not_debug (_requestCreationQueue);
469
+
470
+ dispatch_semaphore_t requestCompleteSemaphore = dispatch_semaphore_create (0 );
471
+
472
+ __block NSMutableURLRequest *result;
473
+ [self requestForQuery: query
474
+ completionQueue: _requestCreationQueue
475
+ completion: ^(NSMutableURLRequest *request) {
476
+ result = request;
477
+ dispatch_semaphore_signal (requestCompleteSemaphore);
478
+ }];
479
+
480
+ dispatch_semaphore_wait (requestCompleteSemaphore, DISPATCH_TIME_FOREVER);
481
+ return result;
482
+ }
483
+
484
+ - (void )requestForQuery : (GTLRQuery *)query completion : (void (^)(NSMutableURLRequest *))completion {
485
+ [self requestForQuery: query completionQueue: self .callbackQueue completion: completion];
486
+ }
487
+
488
+ - (void )requestForQuery : (GTLRQuery *)query
489
+ completionQueue : (dispatch_queue_t )completionQueue
490
+ completion : (void (^)(NSMutableURLRequest *))completion {
491
+ GTLR_DEBUG_ASSERT (query.bodyObject == nil , @" %s supports only GET methods, but was passed: %@ " ,
492
+ __func__, query);
433
493
GTLR_DEBUG_ASSERT (query.uploadParameters == nil ,
434
- @" requestForQuery: does not support uploads, but was passed: %@ " , query);
494
+ @" %s does not support uploads, but was passed: %@ " , __func__ , query);
435
495
436
496
NSURL *url = [self URLFromQueryObject: query
437
497
usePartialPaths: NO
@@ -446,10 +506,19 @@ - (NSMutableURLRequest *)requestForQuery:(GTLRQuery *)query {
446
506
queryParameters: queryParameters];
447
507
}
448
508
449
- NSMutableURLRequest *request = [self requestForURL: url
450
- ETag: nil
451
- httpMethod: query.httpMethod
452
- ticket: nil ];
509
+ [self requestForURL: url
510
+ ETag: nil
511
+ httpMethod: query.httpMethod
512
+ ticket: nil
513
+ completion: ^(NSMutableURLRequest *request) {
514
+ [self handleRequestCompletion: request forQuery: query];
515
+ dispatch_async (completionQueue, ^{
516
+ completion (request);
517
+ });
518
+ }];
519
+ }
520
+
521
+ - (void )handleRequestCompletion : (NSMutableURLRequest *)request forQuery : (GTLRQuery *)query {
453
522
NSString *apiRestriction = self.APIKeyRestrictionBundleID ;
454
523
if ([apiRestriction length ] > 0 ) {
455
524
[request setValue: apiRestriction forHTTPHeaderField: kXIosBundleIdHeader ];
@@ -466,8 +535,6 @@ - (NSMutableURLRequest *)requestForQuery:(GTLRQuery *)query {
466
535
NSString *value = [headers objectForKey: key];
467
536
[request setValue: value forHTTPHeaderField: key];
468
537
}
469
-
470
- return request;
471
538
}
472
539
473
540
// common fetch starting method
@@ -574,14 +641,6 @@ - (GTLRServiceTicket *)fetchObjectWithURL:(NSURL *)targetURL
574
641
}
575
642
}
576
643
577
- NSURLRequest *request = [self objectRequestForURL: targetURL
578
- object: bodyObject
579
- contentType: contentType
580
- contentLength: contentLength
581
- ETag: etag
582
- httpMethod: httpMethod
583
- additionalHeaders: additionalHeaders
584
- ticket: ticket];
585
644
ticket.postedObject = bodyObject;
586
645
ticket.executingQuery = executingQuery;
587
646
@@ -591,10 +650,39 @@ - (GTLRServiceTicket *)fetchObjectWithURL:(NSURL *)targetURL
591
650
ticket.originalQuery = originalQuery;
592
651
}
593
652
653
+ [self objectRequestForURL: targetURL
654
+ object: bodyObject
655
+ contentType: contentType
656
+ contentLength: contentLength
657
+ ETag: etag
658
+ httpMethod: httpMethod
659
+ additionalHeaders: additionalHeaders
660
+ ticket: ticket
661
+ completion: ^(NSMutableURLRequest *request) {
662
+ [self handleObjectRequestCompletionWithRequest: request
663
+ objectClass: objectClass
664
+ dataToPost: dataToPost
665
+ mayAuthorize: mayAuthorize
666
+ completionHandler: completionHandler
667
+ executingQuery: executingQuery
668
+ ticket: ticket];
669
+ }];
670
+
671
+ return ticket;
672
+ }
673
+
674
+ - (void )handleObjectRequestCompletionWithRequest : (NSMutableURLRequest *)request
675
+ objectClass : (Class )objectClass
676
+ dataToPost : (NSData *)dataToPost
677
+ mayAuthorize : (BOOL )mayAuthorize
678
+ completionHandler : (GTLRServiceCompletionHandler)completionHandler
679
+ executingQuery : (id <GTLRQueryProtocol>)executingQuery
680
+ ticket : (GTLRServiceTicket *)ticket {
594
681
// Some proxy servers (and some web servers) have issues with GET URLs being
595
682
// too long, trap that and move the query parameters into the body. The
596
683
// uploadParams and dataToPost should be nil for a GET, but playing it safe
597
684
// and confirming.
685
+ GTLRUploadParameters *uploadParams = executingQuery.uploadParameters ;
598
686
NSString *requestHTTPMethod = request.HTTPMethod ;
599
687
BOOL isDoingHTTPGet =
600
688
(requestHTTPMethod == nil
@@ -630,7 +718,7 @@ - (GTLRServiceTicket *)fetchObjectWithURL:(NSURL *)targetURL
630
718
testBlock: testBlock
631
719
dataToPost: dataToPost
632
720
completionHandler: completionHandler];
633
- return ticket ;
721
+ return ;
634
722
}
635
723
636
724
GTMSessionFetcherService *fetcherService = ticket.fetcherService ;
@@ -821,17 +909,6 @@ - (GTLRServiceTicket *)fetchObjectWithURL:(NSURL *)targetURL
821
909
hasSentParsingStartNotification: NO
822
910
completionHandler: completionHandler];
823
911
}]; // fetcher completion handler
824
-
825
- // If something weird happens and the networking callbacks have been called
826
- // already synchronously, we don't want to return the ticket since the caller
827
- // will never know when to stop retaining it, so we'll make sure the
828
- // success/failure callbacks have not yet been called by checking the
829
- // ticket
830
- if (ticket.hasCalledCallback ) {
831
- return nil ;
832
- }
833
-
834
- return ticket;
835
912
}
836
913
837
914
- (GTMSessionUploadFetcher *)uploadFetcherWithRequest : (NSURLRequest *)request
0 commit comments