Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 10 additions & 16 deletions .travis.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,35 +11,29 @@ readonly BUILD_MODE="$1"
readonly BUILD_CFG="$2"

# Default to "build", based on BUILD_MODE below.
XCTOOL_ACTION="build"
XCODEBUILD_ACTION="build"

# Report then run the build
RunXCTool() {
echo xctool "$@"
xctool "$@"
RunXcodeBuild() {
echo xcodebuild "$@"
xcodebuild "$@"
}

CMD_BUILDER=(
# Always use -reporter plain to avoid escape codes in output (makes travis
# logs easier to read).
-reporter plain
)

case "${BUILD_MODE}" in
iOSCore)
CMD_BUILDER+=(
-project Source/GTLRCore.xcodeproj
-scheme "iOS Framework and Tests"
-sdk iphonesimulator
-destination "platform=iOS Simulator,name=iPhone 6,OS=latest"
)
XCTOOL_ACTION="test"
XCODEBUILD_ACTION="test"
;;
OSXCore)
CMD_BUILDER+=(
-project Source/GTLRCore.xcodeproj
-scheme "OS X Framework and Tests"
)
XCTOOL_ACTION="test"
XCODEBUILD_ACTION="test"
;;
Example_*)
EXAMPLE_NAME="${BUILD_MODE/Example_/}"
Expand All @@ -56,11 +50,11 @@ esac

case "${BUILD_CFG}" in
Debug|Release)
RunXCTool "${CMD_BUILDER[@]}" -configuration "${BUILD_CFG}" "${XCTOOL_ACTION}"
RunXcodeBuild "${CMD_BUILDER[@]}" -configuration "${BUILD_CFG}" "${XCODEBUILD_ACTION}"
;;
Both)
RunXCTool "${CMD_BUILDER[@]}" -configuration Debug "${XCTOOL_ACTION}"
RunXCTool "${CMD_BUILDER[@]}" -configuration Release "${XCTOOL_ACTION}"
RunXcodeBuild "${CMD_BUILDER[@]}" -configuration Debug "${XCODEBUILD_ACTION}"
RunXcodeBuild "${CMD_BUILDER[@]}" -configuration Release "${XCODEBUILD_ACTION}"
;;
*)
echo "Unknown BUILD_CFG: ${BUILD_CFG}"
Expand Down
6 changes: 1 addition & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
language: objective-c
osx_image: xcode7.2
osx_image: xcode7.3
env:
- MODE=OSXCore CFG=Debug
- MODE=OSXCore CFG=Release
Expand All @@ -11,9 +11,5 @@ env:
- MODE=Example_StorageSample CFG=Both
script:
- ./.travis.sh "${MODE}" "${CFG}"
before_install:
# Ensure xctool is up to date.
- brew update
- brew outdated xctool || brew upgrade xctool
notifications:
email: false
2 changes: 1 addition & 1 deletion Deps/gtm-session-fetcher
10 changes: 7 additions & 3 deletions GoogleAPIClientForREST.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,20 @@ Pod::Spec.new do |s|
s.description = <<-DESC
Written by Google, this library is a flexible and efficient Objective-C
framework for accessing JSON REST APIs. This is the recommended library
for accessing JSON-based Google APIs for iOS and Mac OS X applications.
for accessing JSON-based Google APIs for iOS, OS X, and tvOS applications.

This version can be used with iOS ≥ 7.0 or OS X ≥ 10.9.
This version can be used with iOS ≥ 7.0, OS X ≥ 10.9, tvOS ≥ 9.0.
DESC
s.ios.deployment_target = '7.0'
s.osx.deployment_target = '10.9'
s.tvos.deployment_target = '9.0'

s.user_target_xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) GTLR_USE_FRAMEWORK_IMPORTS=1' }
s.pod_target_xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) GTLR_HAS_SESSION_UPLOAD_FETCHER_IMPORT=1' }
s.dependency 'GTMSessionFetcher', '~> 1.1'

# Require atleast 1.1.3 of the SessionFetcher so it has the backgroundTask
# Testing support.
s.dependency 'GTMSessionFetcher', '~> 1.1', '>= 1.1.3'

s.subspec 'Core' do |sp|
sp.source_files = 'Source/GTLRDefines.h',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
buildConfiguration = "Release">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
buildConfiguration = "Release">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
Expand Down
2 changes: 1 addition & 1 deletion Source/Objects/GTLRService.h
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,7 @@ typedef void (^GTLRServiceTestBlock)(GTLRServiceTicket *testTicket,
* @param ticket The ticket being executed.
* @param timeoutInSeconds Maximum duration to wait.
*
* @return YES if the ticket completed; NO if the wait timed out.
* @return YES if the ticket completed or was cancelled; NO if the wait timed out.
*/
- (BOOL)waitForTicket:(GTLRServiceTicket *)ticket
timeout:(NSTimeInterval)timeoutInSeconds;
Expand Down
113 changes: 71 additions & 42 deletions Source/Objects/GTLRService.m
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,21 @@ - (instancetype)initWithService:(GTLRService *)service
@property(nonatomic, assign) UIBackgroundTaskIdentifier backgroundTaskIdentifier;
#endif // GTM_BACKGROUND_TASK_FETCHING

// Dispatch group enabling waitForTicket: to delay until async callbacks and notifications
// related to the ticket have completed.
@property(nonatomic, readonly) dispatch_group_t callbackGroup;

// startBackgroundTask and endBackgroundTask do nothing if !GTM_BACKGROUND_TASK_FETCHING
- (void)startBackgroundTask;
- (void)endBackgroundTask;

- (void)notifyStarting:(BOOL)isStarting;
- (void)releaseTicketCallbacks;

// Posts a notification on the main queue using the ticket's dispatch group.
- (void)postNotificationOnMainThreadWithName:(NSString *)name
object:(id)object
userInfo:(NSDictionary *)userInfo;
@end

#if !defined(GTLR_HAS_SESSION_UPLOAD_FETCHER_IMPORT)
Expand Down Expand Up @@ -502,19 +510,23 @@ - (GTLRServiceTicket *)fetchObjectWithURL:(NSURL *)targetURL

if (uploadParams.shouldUploadWithSingleRequest) {
NSData *uploadData = uploadParams.data;
NSString *uploadMIMEType = uploadParams.MIMEType;
if (!uploadData) {
GTLR_DEBUG_ASSERT(0, @"Uploading with a single request requires bytes to upload as NSData");
} else {
if (uploadParams.shouldSendUploadOnly) {
contentType = uploadParams.MIMEType;
dataToPost = uploadParams.data;
contentType = uploadMIMEType;
dataToPost = uploadData;
contentLength = @(dataToPost.length).stringValue;
} else {
GTMMIMEDocument *mimeDoc = [GTMMIMEDocument MIMEDocument];
[mimeDoc addPartWithHeaders:@{ @"Content-Type" : contentType }
body:dataToPost];
[mimeDoc addPartWithHeaders:@{ @"Content-Type" : uploadParams.MIMEType }
body:uploadParams.data];
if (dataToPost) {
// Include the object as metadata with the upload.
[mimeDoc addPartWithHeaders:@{ @"Content-Type" : contentType }
body:dataToPost];
}
[mimeDoc addPartWithHeaders:@{ @"Content-Type" : uploadMIMEType }
body:uploadData];

dispatch_data_t mimeDispatchData;
unsigned long long mimeLength;
Expand Down Expand Up @@ -607,7 +619,7 @@ - (GTLRServiceTicket *)fetchObjectWithURL:(NSURL *)targetURL
if (!retryBlock) {
response(suggestedWillRetry);
} else {
dispatch_async(ticket.callbackQueue, ^{
dispatch_group_async(ticket.callbackGroup, ticket.callbackQueue, ^{
if (ticket.cancelled) {
response(NO);
return;
Expand Down Expand Up @@ -1015,7 +1027,7 @@ - (void)invokeProgressCallbackForTicket:(GTLRServiceTicket *)ticket

GTLRServiceUploadProgressBlock block = ticket.uploadProgressBlock;
if (block) {
dispatch_async(ticket.callbackQueue, ^{
dispatch_group_async(ticket.callbackGroup, ticket.callbackQueue, ^{
if (ticket.cancelled) return;

block(ticket, numReadSoFar, total);
Expand All @@ -1040,9 +1052,9 @@ - (void)prepareToParseObjectForFetcher:(GTMSessionFetcher *)fetcher
completionHandler:(GTLRServiceCompletionHandler)completionHandler {
GTLR_ASSERT_CURRENT_QUEUE_DEBUG(self.parseQueue);

[[self class] postNotificationOnMainThreadWithName:kGTLRServiceTicketParsingStartedNotification
object:ticket
userInfo:nil];
[ticket postNotificationOnMainThreadWithName:kGTLRServiceTicketParsingStartedNotification
object:ticket
userInfo:nil];

// For unit tests to cancel during parsing, we need a synchronous notification posted.
// Because this notification is intended only for unit tests, there is no public symbol
Expand Down Expand Up @@ -1194,9 +1206,9 @@ - (void)handleParsedObjectForFetcher:(GTMSessionFetcher *)fetcher

if (hasSentParsingStartNotification) {
// we want to always balance the start and stop notifications
[[self class] postNotificationOnMainThreadWithName:kGTLRServiceTicketParsingStoppedNotification
object:ticket
userInfo:nil];
[ticket postNotificationOnMainThreadWithName:kGTLRServiceTicketParsingStoppedNotification
object:ticket
userInfo:nil];
}

BOOL shouldCallCallbacks = YES;
Expand Down Expand Up @@ -1280,7 +1292,7 @@ - (void)handleParsedObjectForFetcher:(GTMSessionFetcher *)fetcher
if (!shouldCallCallbacks) {
// More fetches are happening.
} else {
dispatch_async(ticket.callbackQueue, ^{
dispatch_group_async(ticket.callbackGroup, ticket.callbackQueue, ^{
// First, call query-specific callback blocks. We do this before the
// fetch callback to let applications do any final clean-up (or update
// their UI) in the fetch callback.
Expand Down Expand Up @@ -1309,7 +1321,7 @@ - (void)handleParsedObjectForFetcher:(GTMSessionFetcher *)fetcher
[ticket releaseTicketCallbacks];
[ticket endBackgroundTask];

// Even if the ticket has been canceled, it should notify that it's stopped.
// Even if the ticket has been cancelled, it should notify that it's stopped.
[ticket notifyStarting:NO];

// Release query callback blocks.
Expand Down Expand Up @@ -1597,7 +1609,7 @@ - (void)simulateFetchWithTicket:(GTLRServiceTicket *)ticket
ticket.executingQuery = originalQuery;

testBlock(ticket, ^(id testObject, NSError *testError) {
dispatch_async(ticket.callbackQueue, ^{
dispatch_group_async(ticket.callbackGroup, ticket.callbackQueue, ^{
if (testError) {
// During simulation, we invoke any retry block, but ignore the result.
const BOOL willRetry = NO;
Expand All @@ -1622,12 +1634,12 @@ - (void)simulateFetchWithTicket:(GTLRServiceTicket *)ticket
deliveredBytes:(unsigned long long)totalSentSoFar
totalBytes:(unsigned long long)uploadLength];
}
[[self class] postNotificationOnMainThreadWithName:kGTLRServiceTicketParsingStartedNotification
object:ticket
userInfo:nil];
[[self class] postNotificationOnMainThreadWithName:kGTLRServiceTicketParsingStoppedNotification
object:ticket
userInfo:nil];
[ticket postNotificationOnMainThreadWithName:kGTLRServiceTicketParsingStartedNotification
object:ticket
userInfo:nil];
[ticket postNotificationOnMainThreadWithName:kGTLRServiceTicketParsingStoppedNotification
object:ticket
userInfo:nil];
}
}

Expand Down Expand Up @@ -1657,7 +1669,7 @@ - (void)simulateFetchWithTicket:(GTLRServiceTicket *)ticket
[ticket notifyStarting:NO];

[originalQuery invalidateQuery];
}); // dispatch_async
}); // dispatch_group_async
}); // testBlock
}

Expand Down Expand Up @@ -2113,17 +2125,6 @@ - (void)setUserAgent:(NSString *)userAgent {
[self setExactUserAgent:str];
}

+ (void)postNotificationOnMainThreadWithName:(NSString *)name
object:(id)object
userInfo:(NSDictionary *)userInfo {
// We always post these async to ensure they remain in order.
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:name
object:object
userInfo:userInfo];
});
}

#pragma mark -

+ (NSDictionary<NSString *, Class> *)kindStringToClassMap {
Expand Down Expand Up @@ -2241,17 +2242,32 @@ + (instancetype)mockServiceWithFakedObject:(id)objectOrNil

- (BOOL)waitForTicket:(GTLRServiceTicket *)ticket
timeout:(NSTimeInterval)timeoutInSeconds {
// Loop until the fetch completes with an object or an error,
// or until the timeout has expired.
// Loop until the fetch completes or is cancelled, or until the timeout has expired.
NSDate *giveUpDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds];

while (!ticket.hasCalledCallback && giveUpDate.timeIntervalSinceNow > 0) {
BOOL hasTimedOut = NO;
while (1) {
int64_t delta = (int64_t)(100 * NSEC_PER_MSEC); // 100 ms
BOOL areCallbacksPending =
(dispatch_group_wait(ticket.callbackGroup, dispatch_time(DISPATCH_TIME_NOW, delta)) != 0);

if (!areCallbacksPending && (ticket.hasCalledCallback || ticket.cancelled)) break;

hasTimedOut = (giveUpDate.timeIntervalSinceNow <= 0);
if (hasTimedOut) {
if (areCallbacksPending) {
// A timeout while waiting for the dispatch group to finish is seriously unexpected.
GTLR_DEBUG_LOG(@"%s timed out while waiting for the dispatch group", __PRETTY_FUNCTION__);
}
break;
}

// Run the current run loop 1/1000 of a second to give the networking
// code a chance to work.
NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:0.001];
[[NSRunLoop currentRunLoop] runUntilDate:stopDate];
}
return ticket.hasCalledCallback;
return !hasTimedOut;
}

@end
Expand All @@ -2267,6 +2283,7 @@ @implementation GTLRServiceTicket {
allowInsecureQueries = _allowInsecureQueries,
authorizer = _authorizer,
cancelled = _cancelled,
callbackGroup = _callbackGroup,
callbackQueue = _callbackQueue,
creationDate = _creationDate,
executingQuery = _executingQuery,
Expand Down Expand Up @@ -2338,6 +2355,7 @@ - (instancetype)initWithService:(GTLRService *)service
_testBlock = params.testBlock ?: service.testBlock;

_callbackQueue = ((_Nonnull dispatch_queue_t)params.callbackQueue) ?: service.callbackQueue;
_callbackGroup = dispatch_group_create();

_apiKey = [service.APIKey copy];
_allowInsecureQueries = service.allowInsecureQueries;
Expand Down Expand Up @@ -2367,6 +2385,17 @@ - (NSString *)description {
[self class], self, _service, devKeyInfo, authorizerInfo, _objectFetcher];
}

- (void)postNotificationOnMainThreadWithName:(NSString *)name
object:(id)object
userInfo:(NSDictionary *)userInfo {
// We always post these async to ensure they remain in order.
dispatch_group_async(self.callbackGroup, dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:name
object:object
userInfo:userInfo];
});
}

- (void)pauseUpload {
GTMSessionFetcher *fetcher = self.objectFetcher;
BOOL canPause = [fetcher respondsToSelector:@selector(pauseFetching)];
Expand Down Expand Up @@ -2507,9 +2536,9 @@ - (void)notifyStarting:(BOOL)isStarting {
name = kGTLRServiceTicketStoppedNotification;
_needsStopNotification = NO;
}
[GTLRService postNotificationOnMainThreadWithName:name
object:self
userInfo:nil];
[self postNotificationOnMainThreadWithName:name
object:self
userInfo:nil];
}

- (id)service {
Expand Down