Skip to content

Commit bfe8cd0

Browse files
bhamiltoncxthomasvl
authored andcommitted
Asynchronously calculate User-Agent
1 parent bcb0439 commit bfe8cd0

File tree

6 files changed

+332
-156
lines changed

6 files changed

+332
-156
lines changed

Examples/DriveSample/DriveSampleWindowController.m

+12-10
Original file line numberDiff line numberDiff line change
@@ -277,19 +277,21 @@ - (void)downloadFile:(GTLRDrive_File *)file
277277
// Here's how to download with a GTMSessionFetcher. The fetcher will use the authorizer that's
278278
// attached to the GTLR service's fetcherService.
279279
//
280-
// NSURLRequest *downloadRequest = [service requestForQuery:query];
281-
// GTMSessionFetcher *fetcher = [service.fetcherService fetcherWithRequest:downloadRequest];
280+
// [service requestForQuery:query
281+
// completion:^(NSURLRequest *downloadRequest) {
282+
// GTMSessionFetcher *fetcher = [service.fetcherService fetcherWithRequest:downloadRequest];
282283
//
283-
// [fetcher setCommentWithFormat:@"Downloading %@", file.name];
284-
// fetcher.destinationFileURL = destinationURL;
284+
// [fetcher setCommentWithFormat:@"Downloading %@", file.name];
285+
// fetcher.destinationFileURL = destinationURL;
285286
//
286-
// [fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
287-
// if (error == nil) {
288-
// NSLog(@"Download succeeded.");
287+
// [fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
288+
// if (error == nil) {
289+
// NSLog(@"Download succeeded.");
289290
//
290-
// // With a destinationFileURL property set, the fetcher's callback
291-
// // data parameter here will be nil.
292-
// }
291+
// // With a destinationFileURL property set, the fetcher's callback
292+
// // data parameter here will be nil.
293+
// }
294+
// }];
293295
// }];
294296

295297
[service executeQuery:query

Examples/StorageSample/StorageSampleWindowController.m

+33-33
Original file line numberDiff line numberDiff line change
@@ -193,39 +193,39 @@ - (IBAction)downloadFileClicked:(id)sender {
193193
// Having the service execute this query would download the data to a GTLRDataObject.
194194
// But for downloads that might be large, we'll use a fetcher, since that offers
195195
// better control and monitoring of downloading.
196-
NSURLRequest *request = [storageService requestForQuery:query];
197-
198-
// The Storage service's fetcherService will create a fetcher with an appropriate
199-
// authorizer.
200-
GTMSessionFetcher *fetcher = [storageService.fetcherService fetcherWithRequest:request];
201-
202-
// The fetcher can save data directly to a file.
203-
fetcher.destinationFileURL = destinationURL;
204-
205-
// Fetcher logging can include comments.
206-
[fetcher setCommentWithFormat:@"Downloading \"%@/%@\"",
207-
storageObject.bucket, storageObject.name];
208-
209-
fetcher.downloadProgressBlock = ^(int64_t bytesWritten,
210-
int64_t totalBytesWritten,
211-
int64_t totalBytesExpectedToWrite) {
212-
// The fetcher will call the download progress block periodically.
213-
};
214-
215-
[fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
216-
// Callback
217-
if (error == nil) {
218-
// Successfully saved the file.
219-
//
220-
// Since a downloadPath property was specified, the data argument is
221-
// nil, and the file data has been written to disk.
222-
[self displayAlert:@"Downloaded"
223-
format:@"%@", destinationURL.path];
224-
} else {
225-
[self displayAlert:@"Error Downloading File"
226-
format:@"%@", error];
227-
}
228-
}];
196+
[storageService
197+
requestForQuery:query
198+
completion:^(NSURLRequest *request) {
199+
// The Storage service's fetcherService will create a fetcher with an appropriate
200+
// authorizer.
201+
GTMSessionFetcher *fetcher =
202+
[storageService.fetcherService fetcherWithRequest:request];
203+
204+
// The fetcher can save data directly to a file.
205+
fetcher.destinationFileURL = destinationURL;
206+
207+
// Fetcher logging can include comments.
208+
[fetcher setCommentWithFormat:@"Downloading \"%@/%@\"", storageObject.bucket,
209+
storageObject.name];
210+
211+
fetcher.downloadProgressBlock = ^(int64_t bytesWritten, int64_t totalBytesWritten,
212+
int64_t totalBytesExpectedToWrite) {
213+
// The fetcher will call the download progress block periodically.
214+
};
215+
216+
[fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *error) {
217+
// Callback
218+
if (error == nil) {
219+
// Successfully saved the file.
220+
//
221+
// Since a downloadPath property was specified, the data argument is
222+
// nil, and the file data has been written to disk.
223+
[self displayAlert:@"Downloaded" format:@"%@", destinationURL.path];
224+
} else {
225+
[self displayAlert:@"Error Downloading File" format:@"%@", error];
226+
}
227+
}];
228+
}];
229229
} // result == NSFileHandlingPanelOKButton
230230
}]; // beginSheetModalForWindow:
231231
}

Sources/Core/GTLRService.m

+125-48
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ @implementation GTLRService {
219219
NSString *_overrideUserAgent;
220220
NSDictionary *_serviceProperties; // Properties retained for the convenience of the client app.
221221
NSUInteger _uploadChunkSize; // Only applies to resumable chunked uploads.
222+
dispatch_queue_t _requestCreationQueue;
222223
}
223224

224225
@synthesize additionalHTTPHeaders = _additionalHTTPHeaders,
@@ -250,6 +251,9 @@ - (instancetype)init {
250251
if (self) {
251252
_parseQueue = dispatch_queue_create("com.google.GTLRServiceParse", DISPATCH_QUEUE_SERIAL);
252253
_callbackQueue = dispatch_get_main_queue();
254+
_requestCreationQueue =
255+
dispatch_queue_create("com.google.GTLRServiceRequestCreation", DISPATCH_QUEUE_SERIAL);
256+
253257
_fetcherService = [[GTMSessionFetcherService alloc] init];
254258

255259
// Make the session fetcher use a background delegate queue instead of bouncing
@@ -319,10 +323,26 @@ - (void)setMainBundleIDRestrictionWithAPIKey:(NSString *)apiKey {
319323
self.APIKeyRestrictionBundleID = [[NSBundle mainBundle] bundleIdentifier];
320324
}
321325

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);
326346

327347
// subclasses may add headers to this
328348
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
@@ -371,19 +391,20 @@ - (NSMutableURLRequest *)requestForURL:(NSURL *)url
371391
return request;
372392
}
373393

374-
// objectRequestForURL returns an NSMutableURLRequest for a GTLRObject
394+
// objectRequestForURL asynchronously returns an NSMutableURLRequest for a GTLRObject
375395
//
376396
// the object is the object being sent to the server, or nil;
377397
// the http method may be nil for get, or POST, PUT, DELETE
378398

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 {
387408
if (object) {
388409
// if the object being sent has an etag, add it to the request header to
389410
// avoid retrieving a duplicate or to avoid writing over an updated
@@ -396,10 +417,23 @@ - (NSMutableURLRequest *)objectRequestForURL:(NSURL *)url
396417
}
397418
}
398419

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 {
403437
[request setValue:@"application/json" forHTTPHeaderField:@"Accept"];
404438
[request setValue:contentType forHTTPHeaderField:@"Content-Type"];
405439

@@ -421,17 +455,43 @@ - (NSMutableURLRequest *)objectRequestForURL:(NSURL *)url
421455
NSString *value = [headers objectForKey:key];
422456
[request setValue:value forHTTPHeaderField:key];
423457
}
424-
425-
return request;
426458
}
427459

428460
#pragma mark -
429461

430462
- (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);
433493
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);
435495

436496
NSURL *url = [self URLFromQueryObject:query
437497
usePartialPaths:NO
@@ -446,10 +506,19 @@ - (NSMutableURLRequest *)requestForQuery:(GTLRQuery *)query {
446506
queryParameters:queryParameters];
447507
}
448508

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 {
453522
NSString *apiRestriction = self.APIKeyRestrictionBundleID;
454523
if ([apiRestriction length] > 0) {
455524
[request setValue:apiRestriction forHTTPHeaderField:kXIosBundleIdHeader];
@@ -466,8 +535,6 @@ - (NSMutableURLRequest *)requestForQuery:(GTLRQuery *)query {
466535
NSString *value = [headers objectForKey:key];
467536
[request setValue:value forHTTPHeaderField:key];
468537
}
469-
470-
return request;
471538
}
472539

473540
// common fetch starting method
@@ -574,14 +641,6 @@ - (GTLRServiceTicket *)fetchObjectWithURL:(NSURL *)targetURL
574641
}
575642
}
576643

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];
585644
ticket.postedObject = bodyObject;
586645
ticket.executingQuery = executingQuery;
587646

@@ -591,10 +650,39 @@ - (GTLRServiceTicket *)fetchObjectWithURL:(NSURL *)targetURL
591650
ticket.originalQuery = originalQuery;
592651
}
593652

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 {
594681
// Some proxy servers (and some web servers) have issues with GET URLs being
595682
// too long, trap that and move the query parameters into the body. The
596683
// uploadParams and dataToPost should be nil for a GET, but playing it safe
597684
// and confirming.
685+
GTLRUploadParameters *uploadParams = executingQuery.uploadParameters;
598686
NSString *requestHTTPMethod = request.HTTPMethod;
599687
BOOL isDoingHTTPGet =
600688
(requestHTTPMethod == nil
@@ -630,7 +718,7 @@ - (GTLRServiceTicket *)fetchObjectWithURL:(NSURL *)targetURL
630718
testBlock:testBlock
631719
dataToPost:dataToPost
632720
completionHandler:completionHandler];
633-
return ticket;
721+
return;
634722
}
635723

636724
GTMSessionFetcherService *fetcherService = ticket.fetcherService;
@@ -821,17 +909,6 @@ - (GTLRServiceTicket *)fetchObjectWithURL:(NSURL *)targetURL
821909
hasSentParsingStartNotification:NO
822910
completionHandler:completionHandler];
823911
}]; // 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;
835912
}
836913

837914
- (GTMSessionUploadFetcher *)uploadFetcherWithRequest:(NSURLRequest *)request

0 commit comments

Comments
 (0)