1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369 |
- /* 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,
- 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;
- 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];
- }
-
- // 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
|