summaryrefslogtreecommitdiff
path: root/StoneIsland/platforms/ios/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.m
diff options
context:
space:
mode:
authorJules Laplace <julescarbon@gmail.com>2020-08-31 23:07:20 +0200
committerJules Laplace <julescarbon@gmail.com>2020-08-31 23:07:20 +0200
commit22721a013bdd10d5eb395ba18453585f5f3f1f7f (patch)
tree5a920e31d6026ed5dc55265e5fd057febccc50e3 /StoneIsland/platforms/ios/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.m
parentd22d51a1ae49680015326857360eb699f31efced (diff)
rebuild the ios platform and the plugins
Diffstat (limited to 'StoneIsland/platforms/ios/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.m')
-rw-r--r--StoneIsland/platforms/ios/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.m1381
1 files changed, 1381 insertions, 0 deletions
diff --git a/StoneIsland/platforms/ios/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.m b/StoneIsland/platforms/ios/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.m
new file mode 100644
index 00000000..f9942c01
--- /dev/null
+++ b/StoneIsland/platforms/ios/Pods/GTMSessionFetcher/Source/GTMSessionFetcherService.m
@@ -0,0 +1,1381 @@
+/* Copyright 2014 Google Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if !defined(__has_feature) || !__has_feature(objc_arc)
+#error "This file requires ARC support."
+#endif
+
+#import "GTMSessionFetcherService.h"
+
+NSString *const kGTMSessionFetcherServiceSessionBecameInvalidNotification
+ = @"kGTMSessionFetcherServiceSessionBecameInvalidNotification";
+NSString *const kGTMSessionFetcherServiceSessionKey
+ = @"kGTMSessionFetcherServiceSessionKey";
+
+#if !GTMSESSION_BUILD_COMBINED_SOURCES
+@interface GTMSessionFetcher (ServiceMethods)
+- (BOOL)beginFetchMayDelay:(BOOL)mayDelay
+ mayAuthorize:(BOOL)mayAuthorize;
+@end
+#endif // !GTMSESSION_BUILD_COMBINED_SOURCES
+
+@interface GTMSessionFetcherService ()
+
+@property(atomic, strong, readwrite) NSDictionary *delayedFetchersByHost;
+@property(atomic, strong, readwrite) NSDictionary *runningFetchersByHost;
+
+@end
+
+// Since NSURLSession doesn't support a separate delegate per task (!), instances of this
+// class serve as a session delegate trampoline.
+//
+// This class maps a session's tasks to fetchers, and resends delegate messages to the task's
+// fetcher.
+@interface GTMSessionFetcherSessionDelegateDispatcher : NSObject<NSURLSessionDelegate>
+
+// The session for the tasks in this dispatcher's task-to-fetcher map.
+@property(atomic) NSURLSession *session;
+
+// The timer interval for invalidating a session that has no active tasks.
+@property(atomic) NSTimeInterval discardInterval;
+
+// The current discard timer.
+@property(atomic, readonly) NSTimer *discardTimer;
+
+
+- (instancetype)initWithParentService:(GTMSessionFetcherService *)parentService
+ sessionDiscardInterval:(NSTimeInterval)discardInterval;
+
+- (void)setFetcher:(GTMSessionFetcher *)fetcher
+ forTask:(NSURLSessionTask *)task;
+- (void)removeFetcher:(GTMSessionFetcher *)fetcher;
+
+// Before using a session, tells the delegate dispatcher to stop the discard timer.
+- (void)startSessionUsage;
+
+// When abandoning a delegate dispatcher, we want to avoid the session retaining
+// the delegate after tasks complete.
+- (void)abandon;
+
+@end
+
+
+@implementation GTMSessionFetcherService {
+ NSMutableDictionary *_delayedFetchersByHost;
+ NSMutableDictionary *_runningFetchersByHost;
+ NSUInteger _maxRunningFetchersPerHost;
+
+ // When this ivar is nil, the service will not reuse sessions.
+ GTMSessionFetcherSessionDelegateDispatcher *_delegateDispatcher;
+
+ // Fetchers will wait on this if another fetcher is creating the shared NSURLSession.
+ dispatch_semaphore_t _sessionCreationSemaphore;
+
+ dispatch_queue_t _callbackQueue;
+ NSOperationQueue *_delegateQueue;
+ NSHTTPCookieStorage *_cookieStorage;
+ NSString *_userAgent;
+ NSTimeInterval _timeout;
+
+ NSURLCredential *_credential; // Username & password.
+ NSURLCredential *_proxyCredential; // Credential supplied to proxy servers.
+
+ NSInteger _cookieStorageMethod;
+
+ id<GTMFetcherAuthorizationProtocol> _authorizer;
+
+ // For waitForCompletionOfAllFetchersWithTimeout: we need to wait on stopped fetchers since
+ // they've not yet finished invoking their queued callbacks. This array is nil except when
+ // waiting on fetchers.
+ NSMutableArray *_stoppedFetchersToWaitFor;
+
+ // For fetchers that enqueued their callbacks before stopAllFetchers was called on the service,
+ // set a barrier so the callbacks know to bail out.
+ NSDate *_stoppedAllFetchersDate;
+}
+
+@synthesize maxRunningFetchersPerHost = _maxRunningFetchersPerHost,
+ configuration = _configuration,
+ configurationBlock = _configurationBlock,
+ cookieStorage = _cookieStorage,
+ userAgent = _userAgent,
+ challengeBlock = _challengeBlock,
+ credential = _credential,
+ proxyCredential = _proxyCredential,
+ allowedInsecureSchemes = _allowedInsecureSchemes,
+ allowLocalhostRequest = _allowLocalhostRequest,
+ allowInvalidServerCertificates = _allowInvalidServerCertificates,
+ retryEnabled = _retryEnabled,
+ retryBlock = _retryBlock,
+ maxRetryInterval = _maxRetryInterval,
+ minRetryInterval = _minRetryInterval,
+ metricsCollectionBlock = _metricsCollectionBlock,
+ properties = _properties,
+ unusedSessionTimeout = _unusedSessionTimeout,
+ testBlock = _testBlock;
+
+#if GTM_BACKGROUND_TASK_FETCHING
+@synthesize skipBackgroundTask = _skipBackgroundTask;
+#endif
+
+- (instancetype)init {
+ self = [super init];
+ if (self) {
+ _delayedFetchersByHost = [[NSMutableDictionary alloc] init];
+ _runningFetchersByHost = [[NSMutableDictionary alloc] init];
+ _maxRunningFetchersPerHost = 10;
+ _cookieStorageMethod = -1;
+ _unusedSessionTimeout = 60.0;
+ _delegateDispatcher =
+ [[GTMSessionFetcherSessionDelegateDispatcher alloc] initWithParentService:self
+ sessionDiscardInterval:_unusedSessionTimeout];
+ _callbackQueue = dispatch_get_main_queue();
+
+ _delegateQueue = [[NSOperationQueue alloc] init];
+ _delegateQueue.maxConcurrentOperationCount = 1;
+ _delegateQueue.name = @"com.google.GTMSessionFetcher.NSURLSessionDelegateQueue";
+
+ _sessionCreationSemaphore = dispatch_semaphore_create(1);
+
+ // Starting with the SDKs for OS X 10.11/iOS 9, the service has a default useragent.
+ // Apps can remove this and get the default system "CFNetwork" useragent by setting the
+ // fetcher service's userAgent property to nil.
+#if (!TARGET_OS_IPHONE && defined(MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_11) \
+ || (TARGET_OS_IPHONE && defined(__IPHONE_9_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0)
+ _userAgent = GTMFetcherStandardUserAgentString(nil);
+#endif
+ }
+ return self;
+}
+
+- (void)dealloc {
+ [self detachAuthorizer];
+ [_delegateDispatcher abandon];
+}
+
+#pragma mark Generate a new fetcher
+
+// Clients may override this method. Clients should not override any other library methods.
+- (id)fetcherWithRequest:(NSURLRequest *)request
+ fetcherClass:(Class)fetcherClass {
+ GTMSessionFetcher *fetcher = [[fetcherClass alloc] initWithRequest:request
+ configuration:self.configuration];
+ fetcher.callbackQueue = self.callbackQueue;
+ fetcher.sessionDelegateQueue = self.sessionDelegateQueue;
+ fetcher.challengeBlock = self.challengeBlock;
+ fetcher.credential = self.credential;
+ fetcher.proxyCredential = self.proxyCredential;
+ fetcher.authorizer = self.authorizer;
+ fetcher.cookieStorage = self.cookieStorage;
+ fetcher.allowedInsecureSchemes = self.allowedInsecureSchemes;
+ fetcher.allowLocalhostRequest = self.allowLocalhostRequest;
+ fetcher.allowInvalidServerCertificates = self.allowInvalidServerCertificates;
+ fetcher.configurationBlock = self.configurationBlock;
+ fetcher.retryEnabled = self.retryEnabled;
+ fetcher.retryBlock = self.retryBlock;
+ fetcher.maxRetryInterval = self.maxRetryInterval;
+ fetcher.minRetryInterval = self.minRetryInterval;
+ if (@available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *)) {
+ fetcher.metricsCollectionBlock = self.metricsCollectionBlock;
+ }
+ fetcher.properties = self.properties;
+ fetcher.service = self;
+ if (self.cookieStorageMethod >= 0) {
+ [fetcher setCookieStorageMethod:self.cookieStorageMethod];
+ }
+
+#if GTM_BACKGROUND_TASK_FETCHING
+ fetcher.skipBackgroundTask = self.skipBackgroundTask;
+#endif
+
+ NSString *userAgent = self.userAgent;
+ if (userAgent.length > 0
+ && [request valueForHTTPHeaderField:@"User-Agent"] == nil) {
+ [fetcher setRequestValue:userAgent
+ forHTTPHeaderField:@"User-Agent"];
+ }
+ fetcher.testBlock = self.testBlock;
+
+ return fetcher;
+}
+
+- (GTMSessionFetcher *)fetcherWithRequest:(NSURLRequest *)request {
+ return [self fetcherWithRequest:request
+ fetcherClass:[GTMSessionFetcher class]];
+}
+
+- (GTMSessionFetcher *)fetcherWithURL:(NSURL *)requestURL {
+ return [self fetcherWithRequest:[NSURLRequest requestWithURL:requestURL]];
+}
+
+- (GTMSessionFetcher *)fetcherWithURLString:(NSString *)requestURLString {
+ NSURL *url = [NSURL URLWithString:requestURLString];
+ return [self fetcherWithURL:url];
+}
+
+// Returns a session for the fetcher's host, or nil.
+- (NSURLSession *)session {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ NSURLSession *session = _delegateDispatcher.session;
+ return session;
+ }
+}
+
+// Returns a session for the fetcher's host, or nil. For shared sessions, this
+// waits on a semaphore, blocking other fetchers while the caller creates the
+// session if needed.
+- (NSURLSession *)sessionForFetcherCreation {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+ if (!_delegateDispatcher) {
+ // This fetcher is creating a non-shared session, so skip the semaphore usage.
+ return nil;
+ }
+ }
+
+ // Wait if another fetcher is currently creating a session; avoid waiting
+ // inside the @synchronized block, as that can deadlock.
+ dispatch_semaphore_wait(_sessionCreationSemaphore, DISPATCH_TIME_FOREVER);
+
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ // Before getting the NSURLSession for task creation, it is
+ // important to invalidate and nil out the session discard timer; otherwise
+ // the session can be invalidated between when it is returned to the
+ // fetcher, and when the fetcher attempts to create its NSURLSessionTask.
+ [_delegateDispatcher startSessionUsage];
+
+ NSURLSession *session = _delegateDispatcher.session;
+ if (session) {
+ // The calling fetcher will receive a preexisting session, so
+ // we can allow other fetchers to create a session.
+ dispatch_semaphore_signal(_sessionCreationSemaphore);
+ } else {
+ // No existing session was obtained, so the calling fetcher will create the session;
+ // it *must* invoke fetcherDidCreateSession: to signal the dispatcher's semaphore after
+ // the session has been created (or fails to be created) to avoid a hang.
+ }
+ return session;
+ }
+}
+
+- (id<NSURLSessionDelegate>)sessionDelegate {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ return _delegateDispatcher;
+ }
+}
+
+#pragma mark Queue Management
+
+- (void)addRunningFetcher:(GTMSessionFetcher *)fetcher
+ forHost:(NSString *)host {
+ // Add to the array of running fetchers for this host, creating the array if needed.
+ NSMutableArray *runningForHost = [_runningFetchersByHost objectForKey:host];
+ if (runningForHost == nil) {
+ runningForHost = [NSMutableArray arrayWithObject:fetcher];
+ [_runningFetchersByHost setObject:runningForHost forKey:host];
+ } else {
+ [runningForHost addObject:fetcher];
+ }
+}
+
+- (void)addDelayedFetcher:(GTMSessionFetcher *)fetcher
+ forHost:(NSString *)host {
+ // Add to the array of delayed fetchers for this host, creating the array if needed.
+ NSMutableArray *delayedForHost = [_delayedFetchersByHost objectForKey:host];
+ if (delayedForHost == nil) {
+ delayedForHost = [NSMutableArray arrayWithObject:fetcher];
+ [_delayedFetchersByHost setObject:delayedForHost forKey:host];
+ } else {
+ [delayedForHost addObject:fetcher];
+ }
+}
+
+- (BOOL)isDelayingFetcher:(GTMSessionFetcher *)fetcher {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ NSString *host = fetcher.request.URL.host;
+ if (host == nil) {
+ return NO;
+ }
+ NSArray *delayedForHost = [_delayedFetchersByHost objectForKey:host];
+ NSUInteger idx = [delayedForHost indexOfObjectIdenticalTo:fetcher];
+ BOOL isDelayed = (delayedForHost != nil) && (idx != NSNotFound);
+ return isDelayed;
+ }
+}
+
+- (BOOL)fetcherShouldBeginFetching:(GTMSessionFetcher *)fetcher {
+ // Entry point from the fetcher
+ NSURL *requestURL = fetcher.request.URL;
+ NSString *host = requestURL.host;
+
+ // Addresses "file:///path" case where localhost is the implicit host.
+ if (host.length == 0 && [requestURL isFileURL]) {
+ host = @"localhost";
+ }
+
+ if (host.length == 0) {
+ // Data URIs legitimately have no host, reject other hostless URLs.
+ GTMSESSION_ASSERT_DEBUG([[requestURL scheme] isEqual:@"data"], @"%@ lacks host", fetcher);
+ return YES;
+ }
+
+ BOOL shouldBeginResult;
+
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ NSMutableArray *runningForHost = [_runningFetchersByHost objectForKey:host];
+ if (runningForHost != nil
+ && [runningForHost indexOfObjectIdenticalTo:fetcher] != NSNotFound) {
+ GTMSESSION_ASSERT_DEBUG(NO, @"%@ was already running", fetcher);
+ return YES;
+ }
+
+ BOOL shouldRunNow = (fetcher.usingBackgroundSession
+ || _maxRunningFetchersPerHost == 0
+ || _maxRunningFetchersPerHost >
+ [[self class] numberOfNonBackgroundSessionFetchers:runningForHost]);
+ if (shouldRunNow) {
+ [self addRunningFetcher:fetcher forHost:host];
+ shouldBeginResult = YES;
+ } else {
+ [self addDelayedFetcher:fetcher forHost:host];
+ shouldBeginResult = NO;
+ }
+ } // @synchronized(self)
+
+ // We'll save the host that serves as the key for this fetcher's array
+ // to avoid any chance of the underlying request changing, stranding
+ // the fetcher in the wrong array
+ fetcher.serviceHost = host;
+
+ return shouldBeginResult;
+}
+
+- (void)startFetcher:(GTMSessionFetcher *)fetcher {
+ [fetcher beginFetchMayDelay:NO
+ mayAuthorize:YES];
+}
+
+// Internal utility. Returns a fetcher's delegate if it's a dispatcher, or nil if the fetcher
+// is its own delegate (possibly via proxy) and has no dispatcher.
+- (GTMSessionFetcherSessionDelegateDispatcher *)delegateDispatcherForFetcher:(GTMSessionFetcher *)fetcher {
+ GTMSessionCheckNotSynchronized(self);
+
+ NSURLSession *fetcherSession = fetcher.session;
+ if (fetcherSession) {
+ id<NSURLSessionDelegate> fetcherDelegate = fetcherSession.delegate;
+ // If the delegate is non-nil and claims to be a GTMSessionFetcher, there is no dispatcher;
+ // assume the fetcher is the delegate or has been proxied (some third-party frameworks
+ // are known to swizzle NSURLSession to proxy its delegate).
+ BOOL hasDispatcher = (fetcherDelegate != nil &&
+ ![fetcherDelegate isKindOfClass:[GTMSessionFetcher class]]);
+ if (hasDispatcher) {
+ GTMSESSION_ASSERT_DEBUG([fetcherDelegate isKindOfClass:[GTMSessionFetcherSessionDelegateDispatcher class]],
+ @"Fetcher delegate class: %@", [fetcherDelegate class]);
+ return (GTMSessionFetcherSessionDelegateDispatcher *)fetcherDelegate;
+ }
+ }
+ return nil;
+}
+
+- (void)fetcherDidCreateSession:(GTMSessionFetcher *)fetcher {
+ if (fetcher.canShareSession) {
+ NSURLSession *fetcherSession = fetcher.session;
+ GTMSESSION_ASSERT_DEBUG(fetcherSession != nil, @"Fetcher missing its session: %@", fetcher);
+
+ GTMSessionFetcherSessionDelegateDispatcher *delegateDispatcher =
+ [self delegateDispatcherForFetcher:fetcher];
+ if (delegateDispatcher) {
+ GTMSESSION_ASSERT_DEBUG(delegateDispatcher.session == nil,
+ @"Fetcher made an extra session: %@", fetcher);
+
+ // Save this fetcher's session.
+ delegateDispatcher.session = fetcherSession;
+
+ // Allow other fetchers to request this session now.
+ dispatch_semaphore_signal(_sessionCreationSemaphore);
+ }
+ }
+}
+
+- (void)fetcherDidBeginFetching:(GTMSessionFetcher *)fetcher {
+ // If this fetcher has a separate delegate with a shared session, then
+ // this fetcher should be added to the delegate's map of tasks to fetchers.
+ GTMSessionFetcherSessionDelegateDispatcher *delegateDispatcher =
+ [self delegateDispatcherForFetcher:fetcher];
+ if (delegateDispatcher) {
+ GTMSESSION_ASSERT_DEBUG(fetcher.canShareSession,
+ @"Inappropriate shared session: %@", fetcher);
+
+ // There should already be a session, from this or a previous fetcher.
+ //
+ // Sanity check that the fetcher's session is the delegate's shared session.
+ NSURLSession *sharedSession = delegateDispatcher.session;
+ NSURLSession *fetcherSession = fetcher.session;
+ GTMSESSION_ASSERT_DEBUG(sharedSession != nil, @"Missing delegate session: %@", fetcher);
+ GTMSESSION_ASSERT_DEBUG(fetcherSession == sharedSession,
+ @"Inconsistent session: %@ %@ (shared: %@)",
+ fetcher, fetcherSession, sharedSession);
+
+ if (sharedSession != nil && fetcherSession == sharedSession) {
+ NSURLSessionTask *task = fetcher.sessionTask;
+ GTMSESSION_ASSERT_DEBUG(task != nil, @"Missing session task: %@", fetcher);
+
+ if (task) {
+ [delegateDispatcher setFetcher:fetcher
+ forTask:task];
+ }
+ }
+ }
+}
+
+- (void)stopFetcher:(GTMSessionFetcher *)fetcher {
+ [fetcher stopFetching];
+}
+
+- (void)fetcherDidStop:(GTMSessionFetcher *)fetcher {
+ // Entry point from the fetcher
+ NSString *host = fetcher.serviceHost;
+ if (!host) {
+ // fetcher has been stopped previously
+ return;
+ }
+
+ // This removeFetcher: invocation is a fallback; typically, fetchers are removed from the task
+ // map when the task completes.
+ GTMSessionFetcherSessionDelegateDispatcher *delegateDispatcher =
+ [self delegateDispatcherForFetcher:fetcher];
+ [delegateDispatcher removeFetcher:fetcher];
+
+ NSMutableArray *fetchersToStart;
+
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ // If a test is waiting for all fetchers to stop, it needs to wait for this one
+ // to invoke its callbacks on the callback queue.
+ [_stoppedFetchersToWaitFor addObject:fetcher];
+
+ NSMutableArray *runningForHost = [_runningFetchersByHost objectForKey:host];
+ [runningForHost removeObject:fetcher];
+
+ NSMutableArray *delayedForHost = [_delayedFetchersByHost objectForKey:host];
+ [delayedForHost removeObject:fetcher];
+
+ while (delayedForHost.count > 0
+ && [[self class] numberOfNonBackgroundSessionFetchers:runningForHost]
+ < _maxRunningFetchersPerHost) {
+ // Start another delayed fetcher running, scanning for the minimum
+ // priority value, defaulting to FIFO for equal priorities
+ GTMSessionFetcher *nextFetcher = nil;
+ for (GTMSessionFetcher *delayedFetcher in delayedForHost) {
+ if (nextFetcher == nil
+ || delayedFetcher.servicePriority < nextFetcher.servicePriority) {
+ nextFetcher = delayedFetcher;
+ }
+ }
+
+ if (nextFetcher) {
+ [self addRunningFetcher:nextFetcher forHost:host];
+ runningForHost = [_runningFetchersByHost objectForKey:host];
+
+ [delayedForHost removeObjectIdenticalTo:nextFetcher];
+
+ if (!fetchersToStart) {
+ fetchersToStart = [NSMutableArray array];
+ }
+ [fetchersToStart addObject:nextFetcher];
+ }
+ }
+
+ if (runningForHost.count == 0) {
+ // None left; remove the empty array
+ [_runningFetchersByHost removeObjectForKey:host];
+ }
+
+ if (delayedForHost.count == 0) {
+ [_delayedFetchersByHost removeObjectForKey:host];
+ }
+ } // @synchronized(self)
+
+ // Start fetchers outside of the synchronized block to avoid a deadlock.
+ for (GTMSessionFetcher *nextFetcher in fetchersToStart) {
+ [self startFetcher:nextFetcher];
+ }
+
+ // The fetcher is no longer in the running or the delayed array,
+ // so remove its host and thread properties
+ fetcher.serviceHost = nil;
+}
+
+- (NSUInteger)numberOfFetchers {
+ NSUInteger running = [self numberOfRunningFetchers];
+ NSUInteger delayed = [self numberOfDelayedFetchers];
+ return running + delayed;
+}
+
+- (NSUInteger)numberOfRunningFetchers {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ NSUInteger sum = 0;
+ for (NSString *host in _runningFetchersByHost) {
+ NSArray *fetchers = [_runningFetchersByHost objectForKey:host];
+ sum += fetchers.count;
+ }
+ return sum;
+ }
+}
+
+- (NSUInteger)numberOfDelayedFetchers {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ NSUInteger sum = 0;
+ for (NSString *host in _delayedFetchersByHost) {
+ NSArray *fetchers = [_delayedFetchersByHost objectForKey:host];
+ sum += fetchers.count;
+ }
+ return sum;
+ }
+}
+
+- (NSArray *)issuedFetchers {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ NSMutableArray *allFetchers = [NSMutableArray array];
+ void (^accumulateFetchers)(id, id, BOOL *) = ^(NSString *host,
+ NSArray *fetchersForHost,
+ BOOL *stop) {
+ [allFetchers addObjectsFromArray:fetchersForHost];
+ };
+ [_runningFetchersByHost enumerateKeysAndObjectsUsingBlock:accumulateFetchers];
+ [_delayedFetchersByHost enumerateKeysAndObjectsUsingBlock:accumulateFetchers];
+
+ GTMSESSION_ASSERT_DEBUG(allFetchers.count == [NSSet setWithArray:allFetchers].count,
+ @"Fetcher appears multiple times\n running: %@\n delayed: %@",
+ _runningFetchersByHost, _delayedFetchersByHost);
+
+ return allFetchers.count > 0 ? allFetchers : nil;
+ }
+}
+
+- (NSArray *)issuedFetchersWithRequestURL:(NSURL *)requestURL {
+ NSString *host = requestURL.host;
+ if (host.length == 0) return nil;
+
+ NSURL *targetURL = [requestURL absoluteURL];
+
+ NSArray *allFetchers = [self issuedFetchers];
+ NSIndexSet *indexes = [allFetchers indexesOfObjectsPassingTest:^BOOL(GTMSessionFetcher *fetcher,
+ NSUInteger idx,
+ BOOL *stop) {
+ NSURL *fetcherURL = [fetcher.request.URL absoluteURL];
+ return [fetcherURL isEqual:targetURL];
+ }];
+
+ NSArray *result = nil;
+ if (indexes.count > 0) {
+ result = [allFetchers objectsAtIndexes:indexes];
+ }
+ return result;
+}
+
+- (void)stopAllFetchers {
+ NSArray *delayedFetchersByHost;
+ NSArray *runningFetchersByHost;
+
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ // Set the time barrier so fetchers know not to call back even if
+ // the stop calls below occur after the fetchers naturally
+ // stopped and so were removed from _runningFetchersByHost,
+ // but while the callbacks were already enqueued before stopAllFetchers
+ // was invoked.
+ _stoppedAllFetchersDate = [[NSDate alloc] init];
+
+ // Remove fetchers from the delayed list to avoid fetcherDidStop: from
+ // starting more fetchers running as a side effect of stopping one
+ delayedFetchersByHost = _delayedFetchersByHost.allValues;
+ [_delayedFetchersByHost removeAllObjects];
+
+ runningFetchersByHost = _runningFetchersByHost.allValues;
+ [_runningFetchersByHost removeAllObjects];
+ }
+
+ for (NSArray *delayedForHost in delayedFetchersByHost) {
+ for (GTMSessionFetcher *fetcher in delayedForHost) {
+ [self stopFetcher:fetcher];
+ }
+ }
+
+ for (NSArray *runningForHost in runningFetchersByHost) {
+ for (GTMSessionFetcher *fetcher in runningForHost) {
+ [self stopFetcher:fetcher];
+ }
+ }
+}
+
+- (NSDate *)stoppedAllFetchersDate {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ return _stoppedAllFetchersDate;
+ }
+}
+
+#pragma mark Accessors
+
+- (BOOL)reuseSession {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ return _delegateDispatcher != nil;
+ }
+}
+
+- (void)setReuseSession:(BOOL)shouldReuse {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ BOOL wasReusing = (_delegateDispatcher != nil);
+ if (shouldReuse != wasReusing) {
+ [self abandonDispatcher];
+ if (shouldReuse) {
+ _delegateDispatcher =
+ [[GTMSessionFetcherSessionDelegateDispatcher alloc] initWithParentService:self
+ sessionDiscardInterval:_unusedSessionTimeout];
+ } else {
+ _delegateDispatcher = nil;
+ }
+ }
+ }
+}
+
+- (void)resetSession {
+ GTMSessionCheckNotSynchronized(self);
+ dispatch_semaphore_wait(_sessionCreationSemaphore, DISPATCH_TIME_FOREVER);
+
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+ [self resetSessionInternal];
+ }
+
+ dispatch_semaphore_signal(_sessionCreationSemaphore);
+}
+
+- (void)resetSessionInternal {
+ GTMSessionCheckSynchronized(self);
+
+ // The old dispatchers may be retained as delegates of any ongoing sessions by those sessions.
+ if (_delegateDispatcher) {
+ [self abandonDispatcher];
+ _delegateDispatcher =
+ [[GTMSessionFetcherSessionDelegateDispatcher alloc] initWithParentService:self
+ sessionDiscardInterval:_unusedSessionTimeout];
+ }
+}
+
+- (void)resetSessionForDispatcherDiscardTimer:(NSTimer *)timer {
+ GTMSessionCheckNotSynchronized(self);
+
+ dispatch_semaphore_wait(_sessionCreationSemaphore, DISPATCH_TIME_FOREVER);
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ if (_delegateDispatcher.discardTimer == timer) {
+ // If the delegate dispatcher's current discardTimer is the same object as the timer
+ // that fired, no fetcher has recently attempted to start using the session by calling
+ // startSessionUsage, which invalidates and nils out the timer.
+ [self resetSessionInternal];
+ } else {
+ // A fetcher has invalidated the timer between its triggering and now, potentially
+ // meaning a fetcher has requested access to the NSURLSession, and may be in the process
+ // of starting a new task. The dispatcher should not be abandoned, as this can lead
+ // to a race condition between calling -finishTasksAndInvalidate on the NSURLSession
+ // and the fetcher attempting to create a new task.
+ }
+ }
+
+ dispatch_semaphore_signal(_sessionCreationSemaphore);
+}
+
+- (NSTimeInterval)unusedSessionTimeout {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ return _unusedSessionTimeout;
+ }
+}
+
+- (void)setUnusedSessionTimeout:(NSTimeInterval)timeout {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ _unusedSessionTimeout = timeout;
+ _delegateDispatcher.discardInterval = timeout;
+ }
+}
+
+// This method should be called inside of @synchronized(self)
+- (void)abandonDispatcher {
+ GTMSessionCheckSynchronized(self);
+ [_delegateDispatcher abandon];
+}
+
+- (NSDictionary *)runningFetchersByHost {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ return [_runningFetchersByHost copy];
+ }
+}
+
+- (void)setRunningFetchersByHost:(NSDictionary *)dict {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ _runningFetchersByHost = [dict mutableCopy];
+ }
+}
+
+- (NSDictionary *)delayedFetchersByHost {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ return [_delayedFetchersByHost copy];
+ }
+}
+
+- (void)setDelayedFetchersByHost:(NSDictionary *)dict {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ _delayedFetchersByHost = [dict mutableCopy];
+ }
+}
+
+- (id<GTMFetcherAuthorizationProtocol>)authorizer {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ return _authorizer;
+ }
+}
+
+- (void)setAuthorizer:(id<GTMFetcherAuthorizationProtocol>)obj {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ if (obj != _authorizer) {
+ [self detachAuthorizer];
+ }
+
+ _authorizer = obj;
+ }
+
+ // Use the fetcher service for the authorization fetches if the auth
+ // object supports fetcher services
+ if ([obj respondsToSelector:@selector(setFetcherService:)]) {
+#if GTM_USE_SESSION_FETCHER
+ [obj setFetcherService:self];
+#else
+ [obj setFetcherService:(id)self];
+#endif
+ }
+}
+
+// This should be called inside a @synchronized(self) block except during dealloc.
+- (void)detachAuthorizer {
+ // This method is called by the fetcher service's dealloc and setAuthorizer:
+ // methods; do not override.
+ //
+ // The fetcher service retains the authorizer, and the authorizer has a
+ // weak pointer to the fetcher service (a non-zeroing pointer for
+ // compatibility with iOS 4 and Mac OS X 10.5/10.6.)
+ //
+ // When this fetcher service no longer uses the authorizer, we want to remove
+ // the authorizer's dependence on the fetcher service. Authorizers can still
+ // function without a fetcher service.
+ if ([_authorizer respondsToSelector:@selector(fetcherService)]) {
+ id authFetcherService = [_authorizer fetcherService];
+ if (authFetcherService == self) {
+ [_authorizer setFetcherService:nil];
+ }
+ }
+}
+
+- (dispatch_queue_t GTM_NONNULL_TYPE)callbackQueue {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ return _callbackQueue;
+ } // @synchronized(self)
+}
+
+- (void)setCallbackQueue:(dispatch_queue_t GTM_NULLABLE_TYPE)queue {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ _callbackQueue = queue ?: dispatch_get_main_queue();
+ } // @synchronized(self)
+}
+
+- (NSOperationQueue * GTM_NONNULL_TYPE)sessionDelegateQueue {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ return _delegateQueue;
+ } // @synchronized(self)
+}
+
+- (void)setSessionDelegateQueue:(NSOperationQueue * GTM_NULLABLE_TYPE)queue {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ _delegateQueue = queue ?: [NSOperationQueue mainQueue];
+ } // @synchronized(self)
+}
+
+- (NSOperationQueue *)delegateQueue {
+ // Provided for compatibility with the old fetcher service. The gtm-oauth2 code respects
+ // any custom delegate queue for calling the app.
+ return nil;
+}
+
++ (NSUInteger)numberOfNonBackgroundSessionFetchers:(NSArray *)fetchers {
+ NSUInteger sum = 0;
+ for (GTMSessionFetcher *fetcher in fetchers) {
+ if (!fetcher.usingBackgroundSession) {
+ ++sum;
+ }
+ }
+ return sum;
+}
+
+@end
+
+@implementation GTMSessionFetcherService (TestingSupport)
+
++ (instancetype)mockFetcherServiceWithFakedData:(NSData *)fakedDataOrNil
+ fakedError:(NSError *)fakedErrorOrNil {
+#if !GTM_DISABLE_FETCHER_TEST_BLOCK
+ NSURL *url = [NSURL URLWithString:@"http://example.invalid"];
+ NSHTTPURLResponse *fakedResponse =
+ [[NSHTTPURLResponse alloc] initWithURL:url
+ statusCode:(fakedErrorOrNil ? 500 : 200)
+ HTTPVersion:@"HTTP/1.1"
+ headerFields:nil];
+ return [self mockFetcherServiceWithFakedData:fakedDataOrNil
+ fakedResponse:fakedResponse
+ fakedError:fakedErrorOrNil];
+#else
+ GTMSESSION_ASSERT_DEBUG(0, @"Test blocks disabled");
+ return nil;
+#endif // GTM_DISABLE_FETCHER_TEST_BLOCK
+}
+
++ (instancetype)mockFetcherServiceWithFakedData:(NSData *)fakedDataOrNil
+ fakedResponse:(NSHTTPURLResponse *)fakedResponse
+ fakedError:(NSError *)fakedErrorOrNil {
+#if !GTM_DISABLE_FETCHER_TEST_BLOCK
+ GTMSessionFetcherService *service = [[self alloc] init];
+ service.allowedInsecureSchemes = @[ @"http" ];
+ service.testBlock = ^(GTMSessionFetcher *fetcherToTest,
+ GTMSessionFetcherTestResponse testResponse) {
+ testResponse(fakedResponse, fakedDataOrNil, fakedErrorOrNil);
+ };
+ return service;
+#else
+ GTMSESSION_ASSERT_DEBUG(0, @"Test blocks disabled");
+ return nil;
+#endif // GTM_DISABLE_FETCHER_TEST_BLOCK
+}
+
+#pragma mark Synchronous Wait for Unit Testing
+
+- (BOOL)waitForCompletionOfAllFetchersWithTimeout:(NSTimeInterval)timeoutInSeconds {
+ NSDate *giveUpDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds];
+ _stoppedFetchersToWaitFor = [NSMutableArray array];
+
+ BOOL shouldSpinRunLoop = [NSThread isMainThread];
+ const NSTimeInterval kSpinInterval = 0.001;
+ BOOL didTimeOut = NO;
+ while (([self numberOfFetchers] > 0 || _stoppedFetchersToWaitFor.count > 0)) {
+ didTimeOut = [giveUpDate timeIntervalSinceNow] < 0;
+ if (didTimeOut) break;
+
+ GTMSessionFetcher *stoppedFetcher = _stoppedFetchersToWaitFor.firstObject;
+ if (stoppedFetcher) {
+ [_stoppedFetchersToWaitFor removeObject:stoppedFetcher];
+ [stoppedFetcher waitForCompletionWithTimeout:10.0 * kSpinInterval];
+ }
+
+ if (shouldSpinRunLoop) {
+ NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:kSpinInterval];
+ [[NSRunLoop currentRunLoop] runUntilDate:stopDate];
+ } else {
+ [NSThread sleepForTimeInterval:kSpinInterval];
+ }
+ }
+ _stoppedFetchersToWaitFor = nil;
+
+ return !didTimeOut;
+}
+
+@end
+
+@implementation GTMSessionFetcherService (BackwardsCompatibilityOnly)
+
+- (NSInteger)cookieStorageMethod {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ return _cookieStorageMethod;
+ }
+}
+
+- (void)setCookieStorageMethod:(NSInteger)cookieStorageMethod {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ _cookieStorageMethod = cookieStorageMethod;
+ }
+}
+
+@end
+
+@implementation GTMSessionFetcherSessionDelegateDispatcher {
+ __weak GTMSessionFetcherService *_parentService;
+ NSURLSession *_session;
+
+ // The task map maps NSURLSessionTasks to GTMSessionFetchers
+ NSMutableDictionary *_taskToFetcherMap;
+ // The discard timer will invalidate sessions after the session's last task completes.
+ NSTimer *_discardTimer;
+ NSTimeInterval _discardInterval;
+}
+
+@synthesize discardInterval = _discardInterval,
+ session = _session;
+
+- (instancetype)init {
+ [self doesNotRecognizeSelector:_cmd];
+ return nil;
+}
+
+- (instancetype)initWithParentService:(GTMSessionFetcherService *)parentService
+ sessionDiscardInterval:(NSTimeInterval)discardInterval {
+ self = [super init];
+ if (self) {
+ _discardInterval = discardInterval;
+ _parentService = parentService;
+ }
+ return self;
+}
+
+- (NSString *)description {
+ return [NSString stringWithFormat:@"%@ %p %@ %@",
+ [self class], self,
+ _session ?: @"<no session>",
+ _taskToFetcherMap.count > 0 ? _taskToFetcherMap : @"<no tasks>"];
+}
+
+- (NSTimer *)discardTimer {
+ GTMSessionCheckNotSynchronized(self);
+ @synchronized(self) {
+ return _discardTimer;
+ }
+}
+
+// This method should be called inside of a @synchronized(self) block.
+- (void)startDiscardTimer {
+ GTMSessionCheckSynchronized(self);
+ [_discardTimer invalidate];
+ _discardTimer = nil;
+ if (_discardInterval > 0) {
+ _discardTimer = [NSTimer timerWithTimeInterval:_discardInterval
+ target:self
+ selector:@selector(discardTimerFired:)
+ userInfo:nil
+ repeats:NO];
+ [_discardTimer setTolerance:(_discardInterval / 10)];
+ [[NSRunLoop mainRunLoop] addTimer:_discardTimer forMode:NSRunLoopCommonModes];
+ }
+}
+
+// This method should be called inside of a @synchronized(self) block.
+- (void)destroyDiscardTimer {
+ GTMSessionCheckSynchronized(self);
+ [_discardTimer invalidate];
+ _discardTimer = nil;
+}
+
+- (void)discardTimerFired:(NSTimer *)timer {
+ GTMSessionFetcherService *service;
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ NSUInteger numberOfTasks = _taskToFetcherMap.count;
+ if (numberOfTasks == 0) {
+ service = _parentService;
+ }
+ }
+
+ // Inform the service that the discard timer has fired, and should check whether the
+ // service can abandon us. -resetSession cannot be called directly, as there is a
+ // race condition that must be guarded against with the NSURLSession being returned
+ // from sessionForFetcherCreation outside other locks. The service can take steps
+ // to prevent resetting the session if that has occurred.
+ //
+ // The service must be called from outside the @synchronized block.
+ [service resetSessionForDispatcherDiscardTimer:timer];
+}
+
+- (void)abandon {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ [self destroySessionAndTimer];
+ }
+}
+
+- (void)startSessionUsage {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ [self destroyDiscardTimer];
+ }
+}
+
+// This method should be called inside of a @synchronized(self) block.
+- (void)destroySessionAndTimer {
+ GTMSessionCheckSynchronized(self);
+ [self destroyDiscardTimer];
+
+ // Break any retain cycle from the session holding the delegate.
+ [_session finishTasksAndInvalidate];
+
+ // Immediately clear the session so no new task may be issued with it.
+ //
+ // The _taskToFetcherMap needs to stay valid until the outstanding tasks finish.
+ _session = nil;
+}
+
+- (void)setFetcher:(GTMSessionFetcher *)fetcher forTask:(NSURLSessionTask *)task {
+ GTMSESSION_ASSERT_DEBUG(fetcher != nil, @"missing fetcher");
+
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ if (_taskToFetcherMap == nil) {
+ _taskToFetcherMap = [[NSMutableDictionary alloc] init];
+ }
+
+ if (fetcher) {
+ [_taskToFetcherMap setObject:fetcher forKey:task];
+ [self destroyDiscardTimer];
+ }
+ }
+}
+
+- (void)removeFetcher:(GTMSessionFetcher *)fetcher {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ // Typically, a fetcher should be removed when its task invokes
+ // URLSession:task:didCompleteWithError:.
+ //
+ // When fetching with a testBlock, though, the task completed delegate
+ // method may not be invoked, requiring cleanup here.
+ NSArray *tasks = [_taskToFetcherMap allKeysForObject:fetcher];
+ GTMSESSION_ASSERT_DEBUG(tasks.count <= 1, @"fetcher task not unmapped: %@", tasks);
+ [_taskToFetcherMap removeObjectsForKeys:tasks];
+
+ if (_taskToFetcherMap.count == 0) {
+ [self startDiscardTimer];
+ }
+ }
+}
+
+// This helper method provides synchronized access to the task map for the delegate
+// methods below.
+- (id)fetcherForTask:(NSURLSessionTask *)task {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ return [_taskToFetcherMap objectForKey:task];
+ }
+}
+
+- (void)removeTaskFromMap:(NSURLSessionTask *)task {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ [_taskToFetcherMap removeObjectForKey:task];
+ }
+}
+
+- (void)setSession:(NSURLSession *)session {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ _session = session;
+ }
+}
+
+- (NSURLSession *)session {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ return _session;
+ }
+}
+
+- (NSTimeInterval)discardInterval {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ return _discardInterval;
+ }
+}
+
+- (void)setDiscardInterval:(NSTimeInterval)interval {
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ _discardInterval = interval;
+ }
+}
+
+// NSURLSessionDelegate protocol methods.
+
+// - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session;
+//
+// TODO(seh): How do we route this to an appropriate fetcher?
+
+
+- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error {
+ GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ didBecomeInvalidWithError:%@",
+ [self class], self, session, error);
+ NSDictionary *localTaskToFetcherMap;
+ @synchronized(self) {
+ GTMSessionMonitorSynchronized(self);
+
+ _session = nil;
+
+ localTaskToFetcherMap = [_taskToFetcherMap copy];
+ }
+
+ // Any "suspended" tasks may not have received callbacks from NSURLSession when the session
+ // completes; we'll call them now.
+ [localTaskToFetcherMap enumerateKeysAndObjectsUsingBlock:^(NSURLSessionTask *task,
+ GTMSessionFetcher *fetcher,
+ BOOL *stop) {
+ if (fetcher.session == session) {
+ // Our delegate method URLSession:task:didCompleteWithError: will rely on
+ // _taskToFetcherMap so that should still contain this fetcher.
+ NSError *canceledError = [NSError errorWithDomain:NSURLErrorDomain
+ code:NSURLErrorCancelled
+ userInfo:nil];
+ [self URLSession:session task:task didCompleteWithError:canceledError];
+ } else {
+ GTMSESSION_ASSERT_DEBUG(0, @"Unexpected session in fetcher: %@ has %@ (expected %@)",
+ fetcher, fetcher.session, session);
+ }
+ }];
+
+ // Our tests rely on this notification to know the session discard timer fired.
+ NSDictionary *userInfo = @{ kGTMSessionFetcherServiceSessionKey : session };
+ NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
+ [nc postNotificationName:kGTMSessionFetcherServiceSessionBecameInvalidNotification
+ object:_parentService
+ userInfo:userInfo];
+}
+
+
+#pragma mark - NSURLSessionTaskDelegate
+
+// NSURLSessionTaskDelegate protocol methods.
+//
+// We won't test here if the fetcher responds to these since we only want this
+// class to implement the same delegate methods the fetcher does (so NSURLSession's
+// tests for respondsToSelector: will have the same result whether the session
+// delegate is the fetcher or this dispatcher.)
+
+- (void)URLSession:(NSURLSession *)session
+ task:(NSURLSessionTask *)task
+willPerformHTTPRedirection:(NSHTTPURLResponse *)response
+ newRequest:(NSURLRequest *)request
+ completionHandler:(void (^)(NSURLRequest *))completionHandler {
+ id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task];
+ [fetcher URLSession:session
+ task:task
+willPerformHTTPRedirection:response
+ newRequest:request
+ completionHandler:completionHandler];
+}
+
+- (void)URLSession:(NSURLSession *)session
+ task:(NSURLSessionTask *)task
+didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
+ completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))handler {
+ id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task];
+ [fetcher URLSession:session
+ task:task
+ didReceiveChallenge:challenge
+ completionHandler:handler];
+}
+
+- (void)URLSession:(NSURLSession *)session
+ task:(NSURLSessionTask *)task
+ needNewBodyStream:(void (^)(NSInputStream *bodyStream))handler {
+ id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task];
+ [fetcher URLSession:session
+ task:task
+ needNewBodyStream:handler];
+}
+
+- (void)URLSession:(NSURLSession *)session
+ task:(NSURLSessionTask *)task
+ didSendBodyData:(int64_t)bytesSent
+ totalBytesSent:(int64_t)totalBytesSent
+totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
+ id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task];
+ [fetcher URLSession:session
+ task:task
+ didSendBodyData:bytesSent
+ totalBytesSent:totalBytesSent
+totalBytesExpectedToSend:totalBytesExpectedToSend];
+}
+
+- (void)URLSession:(NSURLSession *)session
+ task:(NSURLSessionTask *)task
+didCompleteWithError:(NSError *)error {
+ id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task];
+
+ // This is the usual way tasks are removed from the task map.
+ [self removeTaskFromMap:task];
+
+ [fetcher URLSession:session
+ task:task
+ didCompleteWithError:error];
+}
+
+- (void)URLSession:(NSURLSession *)session
+ task:(NSURLSessionTask *)task
+ didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics
+ API_AVAILABLE(ios(10.0), macosx(10.12), tvos(10.0), watchos(3.0)) {
+ id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task];
+ [fetcher URLSession:session task:task didFinishCollectingMetrics:metrics];
+}
+
+// NSURLSessionDataDelegate protocol methods.
+
+- (void)URLSession:(NSURLSession *)session
+ dataTask:(NSURLSessionDataTask *)dataTask
+didReceiveResponse:(NSURLResponse *)response
+ completionHandler:(void (^)(NSURLSessionResponseDisposition))handler {
+ id<NSURLSessionDataDelegate> fetcher = [self fetcherForTask:dataTask];
+ [fetcher URLSession:session
+ dataTask:dataTask
+ didReceiveResponse:response
+ completionHandler:handler];
+}
+
+- (void)URLSession:(NSURLSession *)session
+ dataTask:(NSURLSessionDataTask *)dataTask
+didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask {
+ id<NSURLSessionDataDelegate> fetcher = [self fetcherForTask:dataTask];
+ GTMSESSION_ASSERT_DEBUG(fetcher != nil, @"Missing fetcher for %@", dataTask);
+ [self removeTaskFromMap:dataTask];
+ if (fetcher) {
+ GTMSESSION_ASSERT_DEBUG([fetcher isKindOfClass:[GTMSessionFetcher class]],
+ @"Expecting GTMSessionFetcher");
+ [self setFetcher:(GTMSessionFetcher *)fetcher forTask:downloadTask];
+ }
+
+ [fetcher URLSession:session
+ dataTask:dataTask
+didBecomeDownloadTask:downloadTask];
+}
+
+- (void)URLSession:(NSURLSession *)session
+ dataTask:(NSURLSessionDataTask *)dataTask
+ didReceiveData:(NSData *)data {
+ id<NSURLSessionDataDelegate> fetcher = [self fetcherForTask:dataTask];
+ [fetcher URLSession:session
+ dataTask:dataTask
+ didReceiveData:data];
+}
+
+- (void)URLSession:(NSURLSession *)session
+ dataTask:(NSURLSessionDataTask *)dataTask
+ willCacheResponse:(NSCachedURLResponse *)proposedResponse
+ completionHandler:(void (^)(NSCachedURLResponse *))handler {
+ id<NSURLSessionDataDelegate> fetcher = [self fetcherForTask:dataTask];
+ [fetcher URLSession:session
+ dataTask:dataTask
+ willCacheResponse:proposedResponse
+ completionHandler:handler];
+}
+
+// NSURLSessionDownloadDelegate protocol methods.
+
+- (void)URLSession:(NSURLSession *)session
+ downloadTask:(NSURLSessionDownloadTask *)downloadTask
+didFinishDownloadingToURL:(NSURL *)location {
+ id<NSURLSessionDownloadDelegate> fetcher = [self fetcherForTask:downloadTask];
+ [fetcher URLSession:session
+ downloadTask:downloadTask
+didFinishDownloadingToURL:location];
+}
+
+- (void)URLSession:(NSURLSession *)session
+ downloadTask:(NSURLSessionDownloadTask *)downloadTask
+ didWriteData:(int64_t)bytesWritten
+ totalBytesWritten:(int64_t)totalWritten
+totalBytesExpectedToWrite:(int64_t)totalExpected {
+ id<NSURLSessionDownloadDelegate> fetcher = [self fetcherForTask:downloadTask];
+ [fetcher URLSession:session
+ downloadTask:downloadTask
+ didWriteData:bytesWritten
+ totalBytesWritten:totalWritten
+totalBytesExpectedToWrite:totalExpected];
+}
+
+- (void)URLSession:(NSURLSession *)session
+ downloadTask:(NSURLSessionDownloadTask *)downloadTask
+ didResumeAtOffset:(int64_t)fileOffset
+expectedTotalBytes:(int64_t)expectedTotalBytes {
+ id<NSURLSessionDownloadDelegate> fetcher = [self fetcherForTask:downloadTask];
+ [fetcher URLSession:session
+ downloadTask:downloadTask
+ didResumeAtOffset:fileOffset
+ expectedTotalBytes:expectedTotalBytes];
+}
+
+@end