Google APIs allow client software to access and manipulate data hosted by Google services.
The Google APIs Objective-C Client Library for REST APIs is a Objective-C framework that enables developers for iOS, macOS, tvOS, and watchOS to easily write native applications using Google's JSON REST APIs. The framework handles
- JSON parsing and generation
- Networking
- File uploading and downloading
- Batch requests and responses
- Service-specific protocols and query generation
- Testing without network activity
- HTTP logging
The Examples directory contains example applications showing typical interactions with Google services using the framework. The applications act as simple browsers for the data classes for each service. The WindowController source files of the samples were written with typical Cocoa idioms to serve as quick introductions to use of the APIs.
The example applications run on macOS, but the library does not provide any user interface support apart from authentication, so use of the library APIs is the same for Mac and iOS applications.
In order to use the example applications that require authentication, you can follow the same steps as for the GTMAppAuth Examples The client secret can be blank/nil.
Google APIs which do not require authentication may require an API key, also obtained from the Developer Console.
If you are building from CocoaPods, just use the pod provided, GoogleAPIClientForREST
.
The Core
subspec includes the common parts of the library. There is also a
subspec for each service API provided, such as Calendar and Drive. Your project
can just depend on one or more service subspecs, and the core library files will
be built as well.
See the podspec file for the subspec names.
For example, if you needed the Drive apis, you'd just need to add:
pod 'GoogleAPIClientForREST/Drive'
To your Podfile
and run pod install
.
If you are generating code for your own APIs, then add
pod 'GoogleAPIClientForREST/Core'
to get the supporting runtime and then
manually add the generated sources to your Xcode project.
Refer to the Xcode docs for how to add SwiftPM based dependences to the Xcode UI
or via your Package.swift
file.
The GoogleAPIClientForRESTCore
product includes the common parts of the
library, and then there are specific products for each service API provide. Your
project can use one or multiple services.
See the Package.swift for the different product names.
For example, if you needed the Drive apis, you just need to depend on the
GoogleAPIClientForREST_Drive
product.
If you are generating code for your own APIs, then add
GoogleAPIClientForRESTCore
to get the supporting runtime and then manually add
the generated sources to your Xcode project.
Since CocoaPods and SwiftPM use different models for how things are built, the
module names for @import
directives will be specific to each packaging system.
However, if consuming this library via Objective-C, all the packages export
their headers as GoogleAPIClientForREST/HEADER.h, so you can always #import
them as a framework import and that will work with either packaging system,
i.e. - #import <GoogleAPIClientForREST/GTLRService.h
and
#import <GoogleAPIClientForREST/GTLRYouTube.h
.
The Google APIs Library for Objective-C for REST uses the prefix GTLR
.
The library uses one other Google libraries with a GTM
prefix. It provides
http handling
(gtm-session-fetcher).
Authentication is the confirmation of a user's identity using her username, password, and possibly other data, such as captcha answers or 2-step codes provided via mobile phone. Authentication is required for access to non-public data.
Authorization is the use of access tokens to allow specific requests.
Applications pass the OAuth 2 authorization object to a service class's
setAuthorizer:
method to authorize queries.
Google APIs rely on OAuth 2 for user sign-in. The GTMSessionFetcher
provides
an Objective-C protocol to provide this, any library that supports that protocol
will work.
Note: Neither packaging system used here includes a dependency on any
authentication support, if you plan on using an api that also needs
authentication, you'll either want to use
GTMAppAuth by depending on GTMAppAuth
also, or you can use something else like the
Google Sign-In SDK; see
their site for full information this.
Note: The Oauth2
subspec in the pod is NOT needed for authentication,
that is for talking to that service directly. You just need something like
GTMAppAuth
or the Google Sign-In SDK
.
For each API used by your application, enable the API and check its default quota in the Services section of the Developer Console.
Applications which make frequent API requests or have many users may need more than the default limit. The specific API documentation or the service's control in the API Console will explain how to request a larger quota.
Note: Be sure to estimate your application's total queries per day for all users and request an appropriate quota before releasing your application to a large audience. An inadequate quota may lead to errors on API requests after your application ships.
The Google APIs Client Library for Objective-C includes generated service,
query and data classes. These are Objective-C files constructed by processing
the output of the
Google APIs Discovery Service.
The generated classes are derived from GTLRService
, GTLRQuery
, and
GTLRObject
.
Servers respond to client API requests with an object. The object is typically
either an individual item, or a collection with an items
property that
accesses an NSArray
of items.
For example, a search for files on Google Drive would return a
GTLRDrive_FileList
collection, where the items of the collection are the
GTLRDrive_File
items found by the search.
The individual items are derived from GTLRObject. The collection is derived
from GTLRCollectionObject, which is a GTLRObject
that also provides for
indexed access to items via subscripts. GTLRCollectionObject
also supports
for
loops over the collection items by implementing the NSFastEnumeration
protocol, as shown in the next code snippet.
Each request to the server is a query. Server interactions in the library are handled by a service object. A single transaction with a service is tracked with a service ticket.
For example, here is how to use the API library to fetch a list of items in a public YouTube playlist.
- (void)fetchPublicPlaylistWithID:(NSString *)playlistID {
// Create a service for executing queries. For best performance, reuse
// the same service instance throughout the app.
//
// Some of the service's properties may be set on a per-query basis
// via the query's executionParameters property.
GTLRYouTubeService *service = [[GTLRYouTubeService alloc] init];
// Services which do not require user authentication may need an API key
// from the Google Developers Console
service.APIKey = @"put your API key here";
// APIs which retrieve a collection of items may need to fetch
// multiple pages. The service can optionally make multiple requests
// to fetch all pages. The page size can be set in most APIs with the
// query parameter maxResults.
service.shouldFetchNextPages = YES;
// The library can retry common networking errors. The retry criteria
// may be customized by setting the service's retryBlock property.
service.retryEnabled = YES;
// Each API method has a unique class. The required properties
// of the API method are the parameters of the constructor.
// Optional properties of the API method are properties of the
// class.
// The YouTube API requires a "part" parameter for each query.
// The playlist ID an an optional property of the method.
GTLRYouTubeQuery_PlaylistItemsList *query =
[GTLRYouTubeQuery_PlaylistItemsList queryWithPart:@"snippet"];
query.playlistId = playlistID;
// A ticket is returned to let the app monitor or cancel query execution.
GTLRServiceTicket *ticket =
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRYouTube_PlaylistItemListResponse *playlistItemList,
NSError *callbackError) {
// This callback block is run when the fetch completes.
if (callbackError != nil) {
NSLog(@"Fetch failed: %@", callbackError);
} else {
// The error is nil, so the fetch succeeded.
//
// GTLRYouTube_PlaylistItemListResponse derives from
// GTLRCollectionObject, so it supports iteration of
// items and subscript access to items.
for (GTLRYouTube_PlaylistItem *item in playlistItemList) {
// Print the name of each playlist item.
NSLog(@"%@", item.snippet.title);
}
}
}];
}
Unseen by the application, the server is returning a JSON tree in response to
queries. Each GTLRObject
, such as GTLRYouTube_PlaylistItemListResponse
in
the example above, is just an Objective-C wrapper for a tree of JSON data. The
GTLRObject
allows the JSON data to be treated like a first-class Objective-C
object, using normal Objective-C property notation. This use of properties is
visible in the code snippet above, where each product item result name is
accessible as item.snippet.title
.
GTLRObject
's support for Objective-C properties allows compile-time syntax
checking and enables Xcode's autocompletion for each object. The header files
for each object class clearly define the fields of each object. For example, the
YouTube playlist item object interface looks in part like this:
@interface GTLRYouTube_PlaylistItem : GTLRObject
@property(strong) GTLRYouTube_PlaylistItemContentDetails *contentDetails;
@property(copy) NSString *ETag;
@property(copy) NSString *identifier;
@property(copy) NSString *kind;
@property(strong) GTLRYouTube_PlaylistItemSnippet *snippet;
@property(strong) GTLRYouTube_PlaylistItemStatus *status;
@end
Each object property returns either a standard Objective-C type (NSString
,
NSNumber
, NSArray
), other GTLRObjects
, or GTLRDateTime
.
When your application gets a property from a GTLRObject
, the library converts
the property name to the JSON key string to get or set the result in the
underlying JSON tree. Subtrees of the JSON are returned wrapped in a new
GTLRObject
s. To reduce memory overhead, the GTLRObjects
are not created for
inner trees of the JSON until they are needed by the application.
Normally, applications will use GTLRObject
properties and so will not need to
access the plain JSON tree, but it is available for each GTLRObject
as a
dictionary with the property JSON
.
Queries may also be executed with a delegate and selector for the callback:
GTLRServiceTicket *ticket = [service executeQuery:query
delegate:self
finishedSelector:@selector(serviceTicket:finishedWithObject:error:)];
The delegate is retained until the query has completed, or until the ticket has been canceled.
Service objects maintain cookies and track other persistent data across queries. Typically, an application will create and retain a single instance of a service object to use for executing all queries.
The service object makes a copy of each query for execution, so changes to a
query object made after calling executeQuery:
will not affect the request.
Query execution by the service is inherently asynchronous. There is no need to use a dispatch or operation queue to run queries on other threads. Generally, it's best to execute queries on the main application thread. Any number of queries may be executed either concurrently or sequentially, subject to rate limits shown in the Developer Console. Additional information about threading support is listed below in the section on performance optimizations.
A new ticket is created each time a query is executed by the service. When a
ticket is created, many of the ticket properties, such as retry settings and
surrogates (both described below), are initialized from the service's properties
and from the query’s executionProperties
property.
The application may choose to retain the ticket after a query starts executing, allowing the user to cancel the service request.
Once either a query's callback has been invoked or the ticket is canceled, the
ticket is no longer useful and may be released. To cancel a query in progress,
call [ticket cancelTicket]
.
The query being executed by a ticket is available as ticket.originalQuery
.
For queries that fetch multiple result pages, the query for the page currently
being fetched is available as ticket.executingQuery
. The GTMSessionFetcher
object used to execute the query is accessible as ticket.objectFetcher
.
A query may return an object with only a subset of the results in the items array. The object is considered one of several pages. When a result object includes a nextPageToken token, then the query can be executed again with the token provided as the pageToken property of the new query, fetching the next set of results.
// Check if more pages are available for this collection object.
if (object.nextPageToken) {
// Manually make a query to fetch the next page of results by reusing a copy
// of the previous query that was made before the query was executed.
GTLRTasksQuery *nextQuery = copyOfPreviousQuery;
nextQuery.pageToken = object.nextPageToken;
GTLRServiceTicket *nextTicket = [service executeQuery:nextQuery ...
}
For APIs that provide a nextPageToken property, the library can automatically
fetch all pages, and return an object whose items array includes the items of
all pages (up to 25 pages). This can be turned on by setting the
shouldFetchNextPages
property of a service or of the query’s execution
parameters:
// Turn on automatic page fetches
service.shouldFetchNextPages = YES;
Note, however, that results spread over many pages may take a long time to be
retrieved, as each page fetch will lead to a new http request. The server can
be told to use a larger page size (that is, more items in each page returned) by
fetching a query for the feed with a maxResults
value:
// Specify a large page size to reduce the need to fetch additional result pages
GTLRTasksQuery *query = [GTLRTasksQuery_TasklistsList query];
query.maxResults = 1000;
ticket = [service executeQuery:query ...
Ideally, maxResults
will be large enough that, for typical user data, all
results will be returned in a single page.
For queries that search public data, with potentially a very large number of
items resulting, shouldFetchNextPages
should not be enabled for the service
or the ticket. Additional pages of large data sets can be fetched manually.
Query objects typically implement these basic operations:
- list - fetch a list of items
- insert - add an item to a list
- update - replace an entire item
- patch - replace fields of an item
- delete - remove an item
There may be custom query operations as well, such as for uploading and downloading files. To find the Objective-C query class for a documented API operation, search for the method name shown in the documentation for the API. It is listed before each method name in the query class interface, as shown here for the method named “youtube.playlistItems.list”:
/**
* Returns a collection of playlist items that match the API request
* parameters.
*
* Method: youtube.playlistItems.list
*/
@interface GTLRYouTubeQuery_PlaylistItemsList : GTLRYouTubeQuery
/**
* Fetches a GTLRYouTube_PlaylistItemListResponse
*
* @param part The part parameter specifies a comma-separated list of one or
* more playlistItem resource properties that the API response will include.
*
* @returns GTLRYouTube_PlaylistItemListResponse
*/
+ (instancetype)queryWithPart:(NSString *)part;
The comments for each method also specify the type of object passed to the
callback if the query succeeds. In this example, the callback object class is
GTLRYouTube_PlaylistItemListResponse
.
Typically, GTLRObject
s are created by the library from JSON returned from a
server, but occasionally it is useful to create one from scratch, such as when
inserting a new item or patching an existing item. The +object
method creates
an empty instance of a GTLRObject
.
This snippet shows how to create a new folder for the user's Google Drive account:
- (void)createAFolder {
GTLRDriveService *service = self.driveService;
GTLRDrive_File *folder = [GTLRDrive_File object];
folder.name = @"New Folder Name"
folder.mimeType = @"application/vnd.google-apps.folder";
GTLRDriveQuery_FilesCreate *query =
[GTLRDriveQuery_FilesCreate queryWithObject:folderObj
uploadParameters:nil];
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRDrive_File *folderItem,
NSError *callbackError) {
// Callback
if (callbackError == nil) {
// Succeeded.
}
}];
}
Queries that can upload files will take a GTLRUploadParameters
object. The
upload parameters object requires a MIME type describing the file data, and
either an NSData
with the file's contents, or an NSURL
for reading
from the local file.
Here is an example of uploading a file to the user’s Google Drive account.
- (void)uploadFileURL:(NSURL *)fileToUploadURL {
GTLRDriveService *service = self.driveService;
GTLRUploadParameters *uploadParameters =
[GTLRUploadParameters uploadParametersWithFileURL:fileToUploadURL
MIMEType:@"text/plain"];
GTLRDrive_File *newFile = [GTLRDrive_File object];
newFile.name = path.lastPathComponent;
GTLRDriveQuery_FilesCreate *query =
[GTLRDriveQuery_FilesCreate queryWithObject:newFile
uploadParameters:uploadParameters];
GTLRServiceTicket *uploadTicket =
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRDrive_File *uploadedFile,
NSError *callbackError) {
if (callbackError == nil) {
// Succeeded
}
}];
}
The library by default uses Google's resumable (chunked) upload protocol for
transferring the file to the server. The chunked upload requires one or more
additional server requests. When uploading a large file (perhaps over a
megabyte), the chunk transfer can be done with a background NSURLSession
by settings the property uploadParameters.useBackgroundSession
.
Conversely, small uploads (perhaps under 500K) can be done even more quickly with a single server request by setting the property
uploadParameters.shouldUploadWithSingleRequest = YES;
Use the libary’s http logging feature to see and understand what server requests are used for uploading.
The application can supply a block to be called for displaying progress during uploads.
query.executionParameters.uploadProgressBlock =
^(GTLRServiceTicket *ticket,
unsigned long long numberOfBytesRead,
unsigned long long dataLength) {
[uploadProgressIndicator setDoubleValue:(double)numberOfBytesRead];
[uploadProgressIndicator setMaxValue:(double)dataLength];
};
Uploads in progress can be paused and resumed.
if (ticket.uploadPaused) {
[ticket resumeUpload];
} else {
[ticket pauseUpload];
}
Pause and resume are not supported for uploads sent with the
shouldUploadWithSingleRequest
property set.
Uploads can be cancelled with the ticket's cancelTicket
method.
The library supports two ways to download files. Files that are expected
to be small may be downloaded with a library query. Larger files should
instead be downloaded with a GTMSessionFetcher
, a class that supports
convenient http uploading and downloading.
APIs that support direct download of media files will pass the downloaded
file to the query callback as a GTLRDataObject
instance, as shown here:
GTLRQuery *query = [GTLRDriveQuery_FilesGet queryForMediaWithFileId:fileID];
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRDataObject *dataObject,
NSError *callbackError) {
if (callbackError == nil) {
// The file downloaded successfully; its data is available as dataObject.data
}
}];
A GTMSessionFetcher can download any NSURLRequest. The service methods
requestForQuery:completion:
/ requestForQuery:
will convert a library query
into an NSURLRequest.
NOTE: Because formatting the User-Agent
header (required to create the
GTMSessionFetcher
) can block the calling thread, always use
requestForQuery:completion:
when calling from the UI thread / main queue. If
the code is creating the GTMSessionFetcher
from a background queue that is OK
to block, then using requestForQuery:
can be more appropriate.
Download of any individual user’s data from Google services requires
that the request be authorized. A fetcher created from the GTLRService
object’s fetcher service will authorize the download request.
Here is an example of an authorized file download using a fetcher:
GTLRQuery *query = [GTLRDriveQuery_FilesGet queryForMediaWithFileId:fileID];
[service requestForQuery:query
completion:^(NSURLRequest *downloadRequest) {
GTMSessionFetcher *fetcher =
[service.fetcherService fetcherWithRequest:downloadRequest];
[fetcher beginFetchWithCompletionHandler:^(NSData *data, NSError *fetchError) {
if (fetchError == nil) {
// Download succeeded.
}
}];
}];
The class GTMSessionFetcher has many more features for monitoring and controlling http downloads and uploads, and can download directly to a file instead of to an NSData. See the fetcher header file for details.
Applications can avoid fetching unneeded data by setting a query's fields
property. Google APIs support a syntax similar to XPath for selecting fields to
be included in the response.
For example, to request only the IDs and author email addresses for each item in the collection, use this request:
query.fields = @"items(id,author/email,kind),kind,nextPageToken";
Note: The above is just an example, the exact value you need will depend on
the api/method you are using and what fields you want included in the reply.
GTLRObject
provides a
fieldsDescription
method which returns a string describing all of the set fields in an object. The string may
be useful as a starting point when making a query to request specific fields. You'll
want to use it on the root object from a query to get the full hierarchy.
The library may use the kind
fields of responses to choose the proper object
classes, so requests for partial responses should always include the kind
fields for both items and collections, as shown above. Otherwise, the library
may be forced to incorrectly instantiate the GTLRObject
base class for the
response.
Queries for collections should also accept the nextPageToken
field, as that
field is returned when an additional result page should be fetched. The service
API documentation should specify if a nextPageToken
field is returned with
collection responses.
Refer to the documentation for details on the fields parameter.
An update query will replace an entire item of a collection. Typically, it is more useful to replace only one or a few fields of an item. A patch query accomplishes that: it replaces only the fields specified by the patch object.
Here is how to use a partial update to change only the name of a calendar:
GTLRCalendar_Calendar *patchObject = [GTLRCalendar_Calendar object];
patchObject.summary = newCalendarName;
GTLRCalendarQuery_CalendarsPatch *query =
[GTLRCalendarQuery_CalendarsPatch queryWithObject:patchObject
calendarId:calendarID];
[service executeQuery:query ...
An explicit null value indicates that a field should be deleted. For example, removing just the location from a calendar looks like this:
GTLRCalendar_Calendar *patchObject = [GTLRCalendar_Calendar object];
patchObject.location = [GTLRObject nullValue];
GTLRCalendarQuery_CalendarsPatch *query =
[GTLRCalendarQuery_CalendarsPatch queryWithObject:patchObject
calendarId:calendarID];
[service executeQuery:query ...
An array in a patch object field always replaces the entire array for the object on the server, as described in the documentation for partial updates.
GTLRObject
includes a helpful method patchObjectFromOriginal:
for making a
patch object that contains just the changes from a previous version of that
object.
Several unrelated queries may be executed together in a batch. Batch execution is faster than is executing queries individually.
There are two ways to obtain the results of batch queries: the unified completion handler callback for the batch query execution, with results for all queries, and individual completion blocks for each query.
Typically, it is easier to use individual query completion blocks for unrelated methods (such as requesting a variety of different attributes of a single item), and the unified completion handler for batches of related methods (such as requesting the same attributes of an array of one class of item).
Both types of callbacks may be used. The individual query completion blocks are always called before the completion handler for the batch query execution. Individual query completion blocks are optional, and may be omitted.
A completion block can be specified for each query, as shown here:
GTLRCalendarQuery_EventsList *eventsQuery =
[GTLRCalendarQuery_EventsList queryWithCalendarId:calendarID];
eventsQuery.completionBlock = ^(GTLRServiceTicket *callbackTicket,
GTLRCalendar_Events *events,
NSError *callbackError) {
If (callbackError == nil) {
// This query succeeded.
}
};
GTLRBatchQuery *batch = [GTLRBatchQuery batchQuery];
[batch addQuery:eventsQuery];
Errors passed to the query's completion block will have an underlying
GTLRErrorObject
when execution succeeded but the server returned an error for
this specific query:
GTLRErrorObject *errorObj = [GTLRErrorObject underlyingObjectForError:error];
if (errorObj) {
// The server returned this error for this specific query.
} else {
// This error occurred because the batch execution failed.
}
The unified result of executing a batch query is a GTLRBatchResult
object
with two dictionaries, one for the results of successful queries, and one for
the error objects returned by unsuccessful queries.
Note that in addition to the dictionary of error results, there is also an
NSError
passed to the completion handler which indicates if the execution did
not succeed.
GTLRBatchQuery *batchQuery = [GTLRBatchQuery batchQuery];
[batchQuery addQuery:query1];
[batchQuery addQuery:query2];
[service executeQuery:batchQuery
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRBatchResult *batchResult,
NSError *callbackError) {
if (callbackError == nil) {
// Execute succeeded: step through the query successes
// and failures in the result.
NSDictionary *successes = batchResult.successes;
for (NSString *requestID in successes) {
GTLRObject *result = [successes objectForKey:requestID];
}
NSDictionary *failures = batchResults.failures;
for (NSString *requestID in failures) {
GTLRErrorObject *errorObj = [failures objectForKey:requestID];
}
} else {
// Here, callbackError is non-nil so the execute failed:
// no success or failure results were obtained from the server.
}
}];
Each query object is created with a unique requestID
, though your application
may set a custom requestID
string for the query prior to execution. The
requestID
string must be non-empty, and all queries in a batch must have
unique requestID
s.
Typically, applications will execute queries on the main thread, and the query completion handler will be called back on the main thread.
To have callbacks performed on a different queue than the main queue, specify a dispatch queue for the service’s callbackQueue
property.
A service object can convert a GTLRQuery to a plain NSMutableURLRequest with the
service method requestForQuery:completion:
. That may be useful for downloading
media files, or for performing an API request without using the service’s
executeQuery:
method.
Requests that retrieve user data require authorization. The GTMSessionFetcher
authorizer property handles authorization for requests fetched with the
fetcher. Alternatively, a GTMAppAuth authorizer object can authorize a
plain NSMutableURLRequest.
Apps that want to monitor performance or network activity may observe the
notifications kGTLRServiceTicketStartedNotification
and
kGTLRServiceTicketStoppedNotification
.
GTLRObject
conforms to NSSecureCoding
so may be serialized with
NSKeyedArchiver
and deserialized with NSKeyedUnarchiver
. The
object’s userProperties
dictionary is not encoded into the archive.
A GTLRObject
has an underlying NSDictionary accessible through its
JSON
property. The dictionary may be saved as a property list using
NSPropertyListSerialization
or as JSON using NSJSONSerialization
.
Here is an example of serializing with a property list:
// Saving to disk.
NSError *serializeError;
NSData *data = [NSPropertyListSerialization dataWithPropertyList:events.JSON
format:NSPropertyListBinaryFormat_v1_0
options:0
error:&serializeError];
if (data) {
NSError *writeError;
BOOL didWrite = [data writeToURL:fileURL
options:0
error:&writeError];
}
An object's JSON can also be expressed as a string with the JSONString
method.
The object may later be recreated with the class methods +objectWithJSON:
or +objectWithJSON:objectClassResolver:
. The
JSON dictionary should be constructed with mutable containers:
NSData *data = [NSData dataWithContentsOfURL:fileURL
options:0
error:&error];
if (data) {
NSMutableDictionary *dict =
[NSPropertyListSerialization propertyListWithData:data
options:NSPropertyListMutableContainers
format:NULL
error:&error];
if (dict) {
GTLRCalendar_Events *eventsList =
[GTLRCalendar_Events objectWithJSON:dict];
// or
GTLRCalendar_Events *eventsList2 =
[GTLRCalendar_Events objectWithJSON:dict
objectClassResolver:calendarService.objectClassResolver];
}
}
The version taking an objectClassResolver
will do a better job of finding the correct object classes to use based on the kind
properties in the JSON.
Often it is useful to add data locally to a GTLRObject
. For example, an
entry used to represent a file being uploaded would be more convenient if
it also carried a path to the file on the local disk.
Your application can add data to any instance of a GTLRObject
in two ways.
These techniques only add data to objects locally for the Objective-C
code; the data will not be retained on the server, nor serialized by
NSKeyedArchiver.
An application can set and retrieve a dictionary with an object’s
userProperties
property, such as this:
GTLRDrive_File *file = [GTLRDrive_File object];
file.userProperties = @{ @”LocalFileURL” : fileLocalURL };
Alternatively, applications may subclass GTLRObject
s to add properties and
methods. To have your subclasses be instantiated in place of the standard
object class during the parsing of JSON as part of query execution, set the
objectClassResolver
property of the service:
GTLRDriveService *service = [[GTLRDriveService alloc] init];
NSDictionary *surrogates = @{
[GTLRDrive_File class] : [MyFile class],
[GTLRDrive_FileList class] : [MyFileList class]
};
NSDictionary *serviceKindMap = [[service class] kindStringToClassMap];
GTLRObjectClassResolver *updatedResolver =
[GTLRObjectClassResolver resolverWithKindMap:serviceKindMap
surrogates:surrogates];
service.objectClassResolver = updatedResolver;
The surrogates may also be set for a single query using the query’s
executionParameters
property:
GTLRDriveQuery_FilesList *query = [GTLRDriveQuery_FilesList query];
query.executionProperties.objectClassResolver = updatedResolver;
It is often useful to pass data to a callback method, particularly when using selector callbacks rather than blocks.
Each ticket has a property ticketProperties
which is initialized to the
service’s serviceProperties
. An app may set the serviceProperties
or
ticketProperties
to provide access to application-specific data:
query.executionParameters.ticketProperties = @{ @"file source", localFileURL };
ticket = [service executeQuery:query
delegate:self
didFinishSelector:@selector(serviceTicket:finishedWithObject:error:)];
The callback can then access the data:
- (void)serviceTicket:(GTLRServiceTicket *)callbackTicket
finishedWithObject:(Test_GTLRDrive_File *)object
error:(NSError *)callbackError {
if (callbackError == nil) {
NSURL *localFileURL = callbackTicket.ticketProperties[@"file source"];
}
}
GTRL service classes and the GTMSessionFetcher
class provide a mechanism for
automatic retry of a few common network and server errors, with appropriate
increasing delays between each attempt. You can turn on the automatic retry
support for a GTLR service by setting the retryEnabled
property.
// Turn on automatic retry of some common error results
service.retryEnabled = YES;
The default errors retried are http status 408 (request timeout), 502 (gateway
failure), 503 (service unavailable), and 504 (gateway timeout),
NSURLErrorNetworkConnectionLost
, and NSURLErrorTimedOut
. You may specify a
maximum retry interval other than the default of 1 minute, and can provide an
optional retryBlock
for the service or for a single query to customize the
criteria for each retry attempt.
For unit tests, queries can be executed without any network activity.
When testing, set the testBlock
property of a service object or a query’s
execution parameters. The testBlock
should call its response parameter
with an object or error, like this:
query.executionParameters.testBlock = ^(GTLRServiceTicket *ticket,
GTLRQueryTestResponse testResponse) {
// The query is available from the ticket.
GTLRQuery *testQuery = ticket.originalQuery;
// The testBlock can create a GTLRObject or GTLRBatchResult, or an NSError.
//
// Here, we will make a GTLRObject as the test response.
GTLRDrive_File *fileObj = [GTLRDrive_File object];
fileObj.name = @"My Fake File";
NSError *testError = nil;
testResponse(fileObj, testError);
};
As a convenient alternative to creating a test block, the test code can just
create a service object with
+[GTLRService mockServiceWithFakedObject:fakedError:]
and pass the desired
result object or error. That creates a service class instance with an
appropriate test block.
When a service or query has a testBlock
, the block will be called instead of
the library performing the normal network activity. Since the executeQuery:
invocation is asynchronous, the unit test should use either
[GTRLService waitForTicket:timeout:]
or
[XCTestCase waitForExpectationsWithTimeout:]
to wait for the completion
handler to be called.
Debugging query execution is often easier when you can browse the JSON and headers being sent back and forth over the network. To make this convenient, the framework can save copies of the server traffic, including http headers, to files in a local directory. Your application should call
[GTMSessionFetcher setLoggingEnabled:YES]
to turn on logging. The project building the fetcher class must also include
GTMSessionFetcherLogging.h
and .m
.
Normally, logs are written to the directory GTMSessionDebugLogs in the logs directory. The logs directory is in the current user's Desktop folder for Mac applications. In the iPhone simulator, the default logs location is the user's home directory. On the iPhone device, the default location is the application's documents folder on the device.
When http logging begins on iOS, the logs folder path is written to the console, like
GTMSessionFetcher logging to "/Users/username/Library/Developer/CoreSimulator/Devices/3B2F01FE-9D64-4C20/data/Containers/Data/Application/FB8E3483-23022D135FB4/GTMHTTPDebugLogs"
The path (including the quotes) can be pasted into a terminal window to open the logs folder:
open "/Users/username/Library/Developer/.../GTMHTTPDebugLogs"
The path to the logs folder can be specified with the +setLoggingDirectory:
method.
To view the most recently saved logs, use a web browser to open the symlink
named MyAppName_log_newest.html
(for whatever your application's name is) in
the logging directory.
For each executed query, when logging is enabled, the http log is also available
as the property ticket.objectFetcher.log
iOS Note: Logging support is stripped out in non-DEBUG builds by default.
This can be overridden by explicitly setting STRIP_GTM_FETCH_LOGGING=0
for
the project.
Tip: Providing a convenient way for your users to enable logging is often helpful in diagnosing problems when using the API.
If you have any questions or comments about the library or this documentation, please join the discussion group.