You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1381 lines
46 KiB

6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
  1. /* Copyright 2014 Google Inc. All rights reserved.
  2. *
  3. * Licensed under the Apache License, Version 2.0 (the "License");
  4. * you may not use this file except in compliance with the License.
  5. * You may obtain a copy of the License at
  6. *
  7. * http://www.apache.org/licenses/LICENSE-2.0
  8. *
  9. * Unless required by applicable law or agreed to in writing, software
  10. * distributed under the License is distributed on an "AS IS" BASIS,
  11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. * See the License for the specific language governing permissions and
  13. * limitations under the License.
  14. */
  15. #if !defined(__has_feature) || !__has_feature(objc_arc)
  16. #error "This file requires ARC support."
  17. #endif
  18. #import "GTMSessionFetcherService.h"
  19. NSString *const kGTMSessionFetcherServiceSessionBecameInvalidNotification
  20. = @"kGTMSessionFetcherServiceSessionBecameInvalidNotification";
  21. NSString *const kGTMSessionFetcherServiceSessionKey
  22. = @"kGTMSessionFetcherServiceSessionKey";
  23. #if !GTMSESSION_BUILD_COMBINED_SOURCES
  24. @interface GTMSessionFetcher (ServiceMethods)
  25. - (BOOL)beginFetchMayDelay:(BOOL)mayDelay
  26. mayAuthorize:(BOOL)mayAuthorize;
  27. @end
  28. #endif // !GTMSESSION_BUILD_COMBINED_SOURCES
  29. @interface GTMSessionFetcherService ()
  30. @property(atomic, strong, readwrite) NSDictionary *delayedFetchersByHost;
  31. @property(atomic, strong, readwrite) NSDictionary *runningFetchersByHost;
  32. @end
  33. // Since NSURLSession doesn't support a separate delegate per task (!), instances of this
  34. // class serve as a session delegate trampoline.
  35. //
  36. // This class maps a session's tasks to fetchers, and resends delegate messages to the task's
  37. // fetcher.
  38. @interface GTMSessionFetcherSessionDelegateDispatcher : NSObject<NSURLSessionDelegate>
  39. // The session for the tasks in this dispatcher's task-to-fetcher map.
  40. @property(atomic) NSURLSession *session;
  41. // The timer interval for invalidating a session that has no active tasks.
  42. @property(atomic) NSTimeInterval discardInterval;
  43. // The current discard timer.
  44. @property(atomic, readonly) NSTimer *discardTimer;
  45. - (instancetype)initWithParentService:(GTMSessionFetcherService *)parentService
  46. sessionDiscardInterval:(NSTimeInterval)discardInterval;
  47. - (void)setFetcher:(GTMSessionFetcher *)fetcher
  48. forTask:(NSURLSessionTask *)task;
  49. - (void)removeFetcher:(GTMSessionFetcher *)fetcher;
  50. // Before using a session, tells the delegate dispatcher to stop the discard timer.
  51. - (void)startSessionUsage;
  52. // When abandoning a delegate dispatcher, we want to avoid the session retaining
  53. // the delegate after tasks complete.
  54. - (void)abandon;
  55. @end
  56. @implementation GTMSessionFetcherService {
  57. NSMutableDictionary *_delayedFetchersByHost;
  58. NSMutableDictionary *_runningFetchersByHost;
  59. NSUInteger _maxRunningFetchersPerHost;
  60. // When this ivar is nil, the service will not reuse sessions.
  61. GTMSessionFetcherSessionDelegateDispatcher *_delegateDispatcher;
  62. // Fetchers will wait on this if another fetcher is creating the shared NSURLSession.
  63. dispatch_semaphore_t _sessionCreationSemaphore;
  64. dispatch_queue_t _callbackQueue;
  65. NSOperationQueue *_delegateQueue;
  66. NSHTTPCookieStorage *_cookieStorage;
  67. NSString *_userAgent;
  68. NSTimeInterval _timeout;
  69. NSURLCredential *_credential; // Username & password.
  70. NSURLCredential *_proxyCredential; // Credential supplied to proxy servers.
  71. NSInteger _cookieStorageMethod;
  72. id<GTMFetcherAuthorizationProtocol> _authorizer;
  73. // For waitForCompletionOfAllFetchersWithTimeout: we need to wait on stopped fetchers since
  74. // they've not yet finished invoking their queued callbacks. This array is nil except when
  75. // waiting on fetchers.
  76. NSMutableArray *_stoppedFetchersToWaitFor;
  77. // For fetchers that enqueued their callbacks before stopAllFetchers was called on the service,
  78. // set a barrier so the callbacks know to bail out.
  79. NSDate *_stoppedAllFetchersDate;
  80. }
  81. @synthesize maxRunningFetchersPerHost = _maxRunningFetchersPerHost,
  82. configuration = _configuration,
  83. configurationBlock = _configurationBlock,
  84. cookieStorage = _cookieStorage,
  85. userAgent = _userAgent,
  86. challengeBlock = _challengeBlock,
  87. credential = _credential,
  88. proxyCredential = _proxyCredential,
  89. allowedInsecureSchemes = _allowedInsecureSchemes,
  90. allowLocalhostRequest = _allowLocalhostRequest,
  91. allowInvalidServerCertificates = _allowInvalidServerCertificates,
  92. retryEnabled = _retryEnabled,
  93. retryBlock = _retryBlock,
  94. maxRetryInterval = _maxRetryInterval,
  95. minRetryInterval = _minRetryInterval,
  96. metricsCollectionBlock = _metricsCollectionBlock,
  97. properties = _properties,
  98. unusedSessionTimeout = _unusedSessionTimeout,
  99. testBlock = _testBlock;
  100. #if GTM_BACKGROUND_TASK_FETCHING
  101. @synthesize skipBackgroundTask = _skipBackgroundTask;
  102. #endif
  103. - (instancetype)init {
  104. self = [super init];
  105. if (self) {
  106. _delayedFetchersByHost = [[NSMutableDictionary alloc] init];
  107. _runningFetchersByHost = [[NSMutableDictionary alloc] init];
  108. _maxRunningFetchersPerHost = 10;
  109. _cookieStorageMethod = -1;
  110. _unusedSessionTimeout = 60.0;
  111. _delegateDispatcher =
  112. [[GTMSessionFetcherSessionDelegateDispatcher alloc] initWithParentService:self
  113. sessionDiscardInterval:_unusedSessionTimeout];
  114. _callbackQueue = dispatch_get_main_queue();
  115. _delegateQueue = [[NSOperationQueue alloc] init];
  116. _delegateQueue.maxConcurrentOperationCount = 1;
  117. _delegateQueue.name = @"com.google.GTMSessionFetcher.NSURLSessionDelegateQueue";
  118. _sessionCreationSemaphore = dispatch_semaphore_create(1);
  119. // Starting with the SDKs for OS X 10.11/iOS 9, the service has a default useragent.
  120. // Apps can remove this and get the default system "CFNetwork" useragent by setting the
  121. // fetcher service's userAgent property to nil.
  122. #if (!TARGET_OS_IPHONE && defined(MAC_OS_X_VERSION_10_11) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_11) \
  123. || (TARGET_OS_IPHONE && defined(__IPHONE_9_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_9_0)
  124. _userAgent = GTMFetcherStandardUserAgentString(nil);
  125. #endif
  126. }
  127. return self;
  128. }
  129. - (void)dealloc {
  130. [self detachAuthorizer];
  131. [_delegateDispatcher abandon];
  132. }
  133. #pragma mark Generate a new fetcher
  134. // Clients may override this method. Clients should not override any other library methods.
  135. - (id)fetcherWithRequest:(NSURLRequest *)request
  136. fetcherClass:(Class)fetcherClass {
  137. GTMSessionFetcher *fetcher = [[fetcherClass alloc] initWithRequest:request
  138. configuration:self.configuration];
  139. fetcher.callbackQueue = self.callbackQueue;
  140. fetcher.sessionDelegateQueue = self.sessionDelegateQueue;
  141. fetcher.challengeBlock = self.challengeBlock;
  142. fetcher.credential = self.credential;
  143. fetcher.proxyCredential = self.proxyCredential;
  144. fetcher.authorizer = self.authorizer;
  145. fetcher.cookieStorage = self.cookieStorage;
  146. fetcher.allowedInsecureSchemes = self.allowedInsecureSchemes;
  147. fetcher.allowLocalhostRequest = self.allowLocalhostRequest;
  148. fetcher.allowInvalidServerCertificates = self.allowInvalidServerCertificates;
  149. fetcher.configurationBlock = self.configurationBlock;
  150. fetcher.retryEnabled = self.retryEnabled;
  151. fetcher.retryBlock = self.retryBlock;
  152. fetcher.maxRetryInterval = self.maxRetryInterval;
  153. fetcher.minRetryInterval = self.minRetryInterval;
  154. if (@available(iOS 10.0, macOS 10.12, tvOS 10.0, watchOS 3.0, *)) {
  155. fetcher.metricsCollectionBlock = self.metricsCollectionBlock;
  156. }
  157. fetcher.properties = self.properties;
  158. fetcher.service = self;
  159. if (self.cookieStorageMethod >= 0) {
  160. [fetcher setCookieStorageMethod:self.cookieStorageMethod];
  161. }
  162. #if GTM_BACKGROUND_TASK_FETCHING
  163. fetcher.skipBackgroundTask = self.skipBackgroundTask;
  164. #endif
  165. NSString *userAgent = self.userAgent;
  166. if (userAgent.length > 0
  167. && [request valueForHTTPHeaderField:@"User-Agent"] == nil) {
  168. [fetcher setRequestValue:userAgent
  169. forHTTPHeaderField:@"User-Agent"];
  170. }
  171. fetcher.testBlock = self.testBlock;
  172. return fetcher;
  173. }
  174. - (GTMSessionFetcher *)fetcherWithRequest:(NSURLRequest *)request {
  175. return [self fetcherWithRequest:request
  176. fetcherClass:[GTMSessionFetcher class]];
  177. }
  178. - (GTMSessionFetcher *)fetcherWithURL:(NSURL *)requestURL {
  179. return [self fetcherWithRequest:[NSURLRequest requestWithURL:requestURL]];
  180. }
  181. - (GTMSessionFetcher *)fetcherWithURLString:(NSString *)requestURLString {
  182. NSURL *url = [NSURL URLWithString:requestURLString];
  183. return [self fetcherWithURL:url];
  184. }
  185. // Returns a session for the fetcher's host, or nil.
  186. - (NSURLSession *)session {
  187. @synchronized(self) {
  188. GTMSessionMonitorSynchronized(self);
  189. NSURLSession *session = _delegateDispatcher.session;
  190. return session;
  191. }
  192. }
  193. // Returns a session for the fetcher's host, or nil. For shared sessions, this
  194. // waits on a semaphore, blocking other fetchers while the caller creates the
  195. // session if needed.
  196. - (NSURLSession *)sessionForFetcherCreation {
  197. @synchronized(self) {
  198. GTMSessionMonitorSynchronized(self);
  199. if (!_delegateDispatcher) {
  200. // This fetcher is creating a non-shared session, so skip the semaphore usage.
  201. return nil;
  202. }
  203. }
  204. // Wait if another fetcher is currently creating a session; avoid waiting
  205. // inside the @synchronized block, as that can deadlock.
  206. dispatch_semaphore_wait(_sessionCreationSemaphore, DISPATCH_TIME_FOREVER);
  207. @synchronized(self) {
  208. GTMSessionMonitorSynchronized(self);
  209. // Before getting the NSURLSession for task creation, it is
  210. // important to invalidate and nil out the session discard timer; otherwise
  211. // the session can be invalidated between when it is returned to the
  212. // fetcher, and when the fetcher attempts to create its NSURLSessionTask.
  213. [_delegateDispatcher startSessionUsage];
  214. NSURLSession *session = _delegateDispatcher.session;
  215. if (session) {
  216. // The calling fetcher will receive a preexisting session, so
  217. // we can allow other fetchers to create a session.
  218. dispatch_semaphore_signal(_sessionCreationSemaphore);
  219. } else {
  220. // No existing session was obtained, so the calling fetcher will create the session;
  221. // it *must* invoke fetcherDidCreateSession: to signal the dispatcher's semaphore after
  222. // the session has been created (or fails to be created) to avoid a hang.
  223. }
  224. return session;
  225. }
  226. }
  227. - (id<NSURLSessionDelegate>)sessionDelegate {
  228. @synchronized(self) {
  229. GTMSessionMonitorSynchronized(self);
  230. return _delegateDispatcher;
  231. }
  232. }
  233. #pragma mark Queue Management
  234. - (void)addRunningFetcher:(GTMSessionFetcher *)fetcher
  235. forHost:(NSString *)host {
  236. // Add to the array of running fetchers for this host, creating the array if needed.
  237. NSMutableArray *runningForHost = [_runningFetchersByHost objectForKey:host];
  238. if (runningForHost == nil) {
  239. runningForHost = [NSMutableArray arrayWithObject:fetcher];
  240. [_runningFetchersByHost setObject:runningForHost forKey:host];
  241. } else {
  242. [runningForHost addObject:fetcher];
  243. }
  244. }
  245. - (void)addDelayedFetcher:(GTMSessionFetcher *)fetcher
  246. forHost:(NSString *)host {
  247. // Add to the array of delayed fetchers for this host, creating the array if needed.
  248. NSMutableArray *delayedForHost = [_delayedFetchersByHost objectForKey:host];
  249. if (delayedForHost == nil) {
  250. delayedForHost = [NSMutableArray arrayWithObject:fetcher];
  251. [_delayedFetchersByHost setObject:delayedForHost forKey:host];
  252. } else {
  253. [delayedForHost addObject:fetcher];
  254. }
  255. }
  256. - (BOOL)isDelayingFetcher:(GTMSessionFetcher *)fetcher {
  257. @synchronized(self) {
  258. GTMSessionMonitorSynchronized(self);
  259. NSString *host = fetcher.request.URL.host;
  260. if (host == nil) {
  261. return NO;
  262. }
  263. NSArray *delayedForHost = [_delayedFetchersByHost objectForKey:host];
  264. NSUInteger idx = [delayedForHost indexOfObjectIdenticalTo:fetcher];
  265. BOOL isDelayed = (delayedForHost != nil) && (idx != NSNotFound);
  266. return isDelayed;
  267. }
  268. }
  269. - (BOOL)fetcherShouldBeginFetching:(GTMSessionFetcher *)fetcher {
  270. // Entry point from the fetcher
  271. NSURL *requestURL = fetcher.request.URL;
  272. NSString *host = requestURL.host;
  273. // Addresses "file:///path" case where localhost is the implicit host.
  274. if (host.length == 0 && [requestURL isFileURL]) {
  275. host = @"localhost";
  276. }
  277. if (host.length == 0) {
  278. // Data URIs legitimately have no host, reject other hostless URLs.
  279. GTMSESSION_ASSERT_DEBUG([[requestURL scheme] isEqual:@"data"], @"%@ lacks host", fetcher);
  280. return YES;
  281. }
  282. BOOL shouldBeginResult;
  283. @synchronized(self) {
  284. GTMSessionMonitorSynchronized(self);
  285. NSMutableArray *runningForHost = [_runningFetchersByHost objectForKey:host];
  286. if (runningForHost != nil
  287. && [runningForHost indexOfObjectIdenticalTo:fetcher] != NSNotFound) {
  288. GTMSESSION_ASSERT_DEBUG(NO, @"%@ was already running", fetcher);
  289. return YES;
  290. }
  291. BOOL shouldRunNow = (fetcher.usingBackgroundSession
  292. || _maxRunningFetchersPerHost == 0
  293. || _maxRunningFetchersPerHost >
  294. [[self class] numberOfNonBackgroundSessionFetchers:runningForHost]);
  295. if (shouldRunNow) {
  296. [self addRunningFetcher:fetcher forHost:host];
  297. shouldBeginResult = YES;
  298. } else {
  299. [self addDelayedFetcher:fetcher forHost:host];
  300. shouldBeginResult = NO;
  301. }
  302. } // @synchronized(self)
  303. // We'll save the host that serves as the key for this fetcher's array
  304. // to avoid any chance of the underlying request changing, stranding
  305. // the fetcher in the wrong array
  306. fetcher.serviceHost = host;
  307. return shouldBeginResult;
  308. }
  309. - (void)startFetcher:(GTMSessionFetcher *)fetcher {
  310. [fetcher beginFetchMayDelay:NO
  311. mayAuthorize:YES];
  312. }
  313. // Internal utility. Returns a fetcher's delegate if it's a dispatcher, or nil if the fetcher
  314. // is its own delegate (possibly via proxy) and has no dispatcher.
  315. - (GTMSessionFetcherSessionDelegateDispatcher *)delegateDispatcherForFetcher:(GTMSessionFetcher *)fetcher {
  316. GTMSessionCheckNotSynchronized(self);
  317. NSURLSession *fetcherSession = fetcher.session;
  318. if (fetcherSession) {
  319. id<NSURLSessionDelegate> fetcherDelegate = fetcherSession.delegate;
  320. // If the delegate is non-nil and claims to be a GTMSessionFetcher, there is no dispatcher;
  321. // assume the fetcher is the delegate or has been proxied (some third-party frameworks
  322. // are known to swizzle NSURLSession to proxy its delegate).
  323. BOOL hasDispatcher = (fetcherDelegate != nil &&
  324. ![fetcherDelegate isKindOfClass:[GTMSessionFetcher class]]);
  325. if (hasDispatcher) {
  326. GTMSESSION_ASSERT_DEBUG([fetcherDelegate isKindOfClass:[GTMSessionFetcherSessionDelegateDispatcher class]],
  327. @"Fetcher delegate class: %@", [fetcherDelegate class]);
  328. return (GTMSessionFetcherSessionDelegateDispatcher *)fetcherDelegate;
  329. }
  330. }
  331. return nil;
  332. }
  333. - (void)fetcherDidCreateSession:(GTMSessionFetcher *)fetcher {
  334. if (fetcher.canShareSession) {
  335. NSURLSession *fetcherSession = fetcher.session;
  336. GTMSESSION_ASSERT_DEBUG(fetcherSession != nil, @"Fetcher missing its session: %@", fetcher);
  337. GTMSessionFetcherSessionDelegateDispatcher *delegateDispatcher =
  338. [self delegateDispatcherForFetcher:fetcher];
  339. if (delegateDispatcher) {
  340. GTMSESSION_ASSERT_DEBUG(delegateDispatcher.session == nil,
  341. @"Fetcher made an extra session: %@", fetcher);
  342. // Save this fetcher's session.
  343. delegateDispatcher.session = fetcherSession;
  344. // Allow other fetchers to request this session now.
  345. dispatch_semaphore_signal(_sessionCreationSemaphore);
  346. }
  347. }
  348. }
  349. - (void)fetcherDidBeginFetching:(GTMSessionFetcher *)fetcher {
  350. // If this fetcher has a separate delegate with a shared session, then
  351. // this fetcher should be added to the delegate's map of tasks to fetchers.
  352. GTMSessionFetcherSessionDelegateDispatcher *delegateDispatcher =
  353. [self delegateDispatcherForFetcher:fetcher];
  354. if (delegateDispatcher) {
  355. GTMSESSION_ASSERT_DEBUG(fetcher.canShareSession,
  356. @"Inappropriate shared session: %@", fetcher);
  357. // There should already be a session, from this or a previous fetcher.
  358. //
  359. // Sanity check that the fetcher's session is the delegate's shared session.
  360. NSURLSession *sharedSession = delegateDispatcher.session;
  361. NSURLSession *fetcherSession = fetcher.session;
  362. GTMSESSION_ASSERT_DEBUG(sharedSession != nil, @"Missing delegate session: %@", fetcher);
  363. GTMSESSION_ASSERT_DEBUG(fetcherSession == sharedSession,
  364. @"Inconsistent session: %@ %@ (shared: %@)",
  365. fetcher, fetcherSession, sharedSession);
  366. if (sharedSession != nil && fetcherSession == sharedSession) {
  367. NSURLSessionTask *task = fetcher.sessionTask;
  368. GTMSESSION_ASSERT_DEBUG(task != nil, @"Missing session task: %@", fetcher);
  369. if (task) {
  370. [delegateDispatcher setFetcher:fetcher
  371. forTask:task];
  372. }
  373. }
  374. }
  375. }
  376. - (void)stopFetcher:(GTMSessionFetcher *)fetcher {
  377. [fetcher stopFetching];
  378. }
  379. - (void)fetcherDidStop:(GTMSessionFetcher *)fetcher {
  380. // Entry point from the fetcher
  381. NSString *host = fetcher.serviceHost;
  382. if (!host) {
  383. // fetcher has been stopped previously
  384. return;
  385. }
  386. // This removeFetcher: invocation is a fallback; typically, fetchers are removed from the task
  387. // map when the task completes.
  388. GTMSessionFetcherSessionDelegateDispatcher *delegateDispatcher =
  389. [self delegateDispatcherForFetcher:fetcher];
  390. [delegateDispatcher removeFetcher:fetcher];
  391. NSMutableArray *fetchersToStart;
  392. @synchronized(self) {
  393. GTMSessionMonitorSynchronized(self);
  394. // If a test is waiting for all fetchers to stop, it needs to wait for this one
  395. // to invoke its callbacks on the callback queue.
  396. [_stoppedFetchersToWaitFor addObject:fetcher];
  397. NSMutableArray *runningForHost = [_runningFetchersByHost objectForKey:host];
  398. [runningForHost removeObject:fetcher];
  399. NSMutableArray *delayedForHost = [_delayedFetchersByHost objectForKey:host];
  400. [delayedForHost removeObject:fetcher];
  401. while (delayedForHost.count > 0
  402. && [[self class] numberOfNonBackgroundSessionFetchers:runningForHost]
  403. < _maxRunningFetchersPerHost) {
  404. // Start another delayed fetcher running, scanning for the minimum
  405. // priority value, defaulting to FIFO for equal priorities
  406. GTMSessionFetcher *nextFetcher = nil;
  407. for (GTMSessionFetcher *delayedFetcher in delayedForHost) {
  408. if (nextFetcher == nil
  409. || delayedFetcher.servicePriority < nextFetcher.servicePriority) {
  410. nextFetcher = delayedFetcher;
  411. }
  412. }
  413. if (nextFetcher) {
  414. [self addRunningFetcher:nextFetcher forHost:host];
  415. runningForHost = [_runningFetchersByHost objectForKey:host];
  416. [delayedForHost removeObjectIdenticalTo:nextFetcher];
  417. if (!fetchersToStart) {
  418. fetchersToStart = [NSMutableArray array];
  419. }
  420. [fetchersToStart addObject:nextFetcher];
  421. }
  422. }
  423. if (runningForHost.count == 0) {
  424. // None left; remove the empty array
  425. [_runningFetchersByHost removeObjectForKey:host];
  426. }
  427. if (delayedForHost.count == 0) {
  428. [_delayedFetchersByHost removeObjectForKey:host];
  429. }
  430. } // @synchronized(self)
  431. // Start fetchers outside of the synchronized block to avoid a deadlock.
  432. for (GTMSessionFetcher *nextFetcher in fetchersToStart) {
  433. [self startFetcher:nextFetcher];
  434. }
  435. // The fetcher is no longer in the running or the delayed array,
  436. // so remove its host and thread properties
  437. fetcher.serviceHost = nil;
  438. }
  439. - (NSUInteger)numberOfFetchers {
  440. NSUInteger running = [self numberOfRunningFetchers];
  441. NSUInteger delayed = [self numberOfDelayedFetchers];
  442. return running + delayed;
  443. }
  444. - (NSUInteger)numberOfRunningFetchers {
  445. @synchronized(self) {
  446. GTMSessionMonitorSynchronized(self);
  447. NSUInteger sum = 0;
  448. for (NSString *host in _runningFetchersByHost) {
  449. NSArray *fetchers = [_runningFetchersByHost objectForKey:host];
  450. sum += fetchers.count;
  451. }
  452. return sum;
  453. }
  454. }
  455. - (NSUInteger)numberOfDelayedFetchers {
  456. @synchronized(self) {
  457. GTMSessionMonitorSynchronized(self);
  458. NSUInteger sum = 0;
  459. for (NSString *host in _delayedFetchersByHost) {
  460. NSArray *fetchers = [_delayedFetchersByHost objectForKey:host];
  461. sum += fetchers.count;
  462. }
  463. return sum;
  464. }
  465. }
  466. - (NSArray *)issuedFetchers {
  467. @synchronized(self) {
  468. GTMSessionMonitorSynchronized(self);
  469. NSMutableArray *allFetchers = [NSMutableArray array];
  470. void (^accumulateFetchers)(id, id, BOOL *) = ^(NSString *host,
  471. NSArray *fetchersForHost,
  472. BOOL *stop) {
  473. [allFetchers addObjectsFromArray:fetchersForHost];
  474. };
  475. [_runningFetchersByHost enumerateKeysAndObjectsUsingBlock:accumulateFetchers];
  476. [_delayedFetchersByHost enumerateKeysAndObjectsUsingBlock:accumulateFetchers];
  477. GTMSESSION_ASSERT_DEBUG(allFetchers.count == [NSSet setWithArray:allFetchers].count,
  478. @"Fetcher appears multiple times\n running: %@\n delayed: %@",
  479. _runningFetchersByHost, _delayedFetchersByHost);
  480. return allFetchers.count > 0 ? allFetchers : nil;
  481. }
  482. }
  483. - (NSArray *)issuedFetchersWithRequestURL:(NSURL *)requestURL {
  484. NSString *host = requestURL.host;
  485. if (host.length == 0) return nil;
  486. NSURL *targetURL = [requestURL absoluteURL];
  487. NSArray *allFetchers = [self issuedFetchers];
  488. NSIndexSet *indexes = [allFetchers indexesOfObjectsPassingTest:^BOOL(GTMSessionFetcher *fetcher,
  489. NSUInteger idx,
  490. BOOL *stop) {
  491. NSURL *fetcherURL = [fetcher.request.URL absoluteURL];
  492. return [fetcherURL isEqual:targetURL];
  493. }];
  494. NSArray *result = nil;
  495. if (indexes.count > 0) {
  496. result = [allFetchers objectsAtIndexes:indexes];
  497. }
  498. return result;
  499. }
  500. - (void)stopAllFetchers {
  501. NSArray *delayedFetchersByHost;
  502. NSArray *runningFetchersByHost;
  503. @synchronized(self) {
  504. GTMSessionMonitorSynchronized(self);
  505. // Set the time barrier so fetchers know not to call back even if
  506. // the stop calls below occur after the fetchers naturally
  507. // stopped and so were removed from _runningFetchersByHost,
  508. // but while the callbacks were already enqueued before stopAllFetchers
  509. // was invoked.
  510. _stoppedAllFetchersDate = [[NSDate alloc] init];
  511. // Remove fetchers from the delayed list to avoid fetcherDidStop: from
  512. // starting more fetchers running as a side effect of stopping one
  513. delayedFetchersByHost = _delayedFetchersByHost.allValues;
  514. [_delayedFetchersByHost removeAllObjects];
  515. runningFetchersByHost = _runningFetchersByHost.allValues;
  516. [_runningFetchersByHost removeAllObjects];
  517. }
  518. for (NSArray *delayedForHost in delayedFetchersByHost) {
  519. for (GTMSessionFetcher *fetcher in delayedForHost) {
  520. [self stopFetcher:fetcher];
  521. }
  522. }
  523. for (NSArray *runningForHost in runningFetchersByHost) {
  524. for (GTMSessionFetcher *fetcher in runningForHost) {
  525. [self stopFetcher:fetcher];
  526. }
  527. }
  528. }
  529. - (NSDate *)stoppedAllFetchersDate {
  530. @synchronized(self) {
  531. GTMSessionMonitorSynchronized(self);
  532. return _stoppedAllFetchersDate;
  533. }
  534. }
  535. #pragma mark Accessors
  536. - (BOOL)reuseSession {
  537. @synchronized(self) {
  538. GTMSessionMonitorSynchronized(self);
  539. return _delegateDispatcher != nil;
  540. }
  541. }
  542. - (void)setReuseSession:(BOOL)shouldReuse {
  543. @synchronized(self) {
  544. GTMSessionMonitorSynchronized(self);
  545. BOOL wasReusing = (_delegateDispatcher != nil);
  546. if (shouldReuse != wasReusing) {
  547. [self abandonDispatcher];
  548. if (shouldReuse) {
  549. _delegateDispatcher =
  550. [[GTMSessionFetcherSessionDelegateDispatcher alloc] initWithParentService:self
  551. sessionDiscardInterval:_unusedSessionTimeout];
  552. } else {
  553. _delegateDispatcher = nil;
  554. }
  555. }
  556. }
  557. }
  558. - (void)resetSession {
  559. GTMSessionCheckNotSynchronized(self);
  560. dispatch_semaphore_wait(_sessionCreationSemaphore, DISPATCH_TIME_FOREVER);
  561. @synchronized(self) {
  562. GTMSessionMonitorSynchronized(self);
  563. [self resetSessionInternal];
  564. }
  565. dispatch_semaphore_signal(_sessionCreationSemaphore);
  566. }
  567. - (void)resetSessionInternal {
  568. GTMSessionCheckSynchronized(self);
  569. // The old dispatchers may be retained as delegates of any ongoing sessions by those sessions.
  570. if (_delegateDispatcher) {
  571. [self abandonDispatcher];
  572. _delegateDispatcher =
  573. [[GTMSessionFetcherSessionDelegateDispatcher alloc] initWithParentService:self
  574. sessionDiscardInterval:_unusedSessionTimeout];
  575. }
  576. }
  577. - (void)resetSessionForDispatcherDiscardTimer:(NSTimer *)timer {
  578. GTMSessionCheckNotSynchronized(self);
  579. dispatch_semaphore_wait(_sessionCreationSemaphore, DISPATCH_TIME_FOREVER);
  580. @synchronized(self) {
  581. GTMSessionMonitorSynchronized(self);
  582. if (_delegateDispatcher.discardTimer == timer) {
  583. // If the delegate dispatcher's current discardTimer is the same object as the timer
  584. // that fired, no fetcher has recently attempted to start using the session by calling
  585. // startSessionUsage, which invalidates and nils out the timer.
  586. [self resetSessionInternal];
  587. } else {
  588. // A fetcher has invalidated the timer between its triggering and now, potentially
  589. // meaning a fetcher has requested access to the NSURLSession, and may be in the process
  590. // of starting a new task. The dispatcher should not be abandoned, as this can lead
  591. // to a race condition between calling -finishTasksAndInvalidate on the NSURLSession
  592. // and the fetcher attempting to create a new task.
  593. }
  594. }
  595. dispatch_semaphore_signal(_sessionCreationSemaphore);
  596. }
  597. - (NSTimeInterval)unusedSessionTimeout {
  598. @synchronized(self) {
  599. GTMSessionMonitorSynchronized(self);
  600. return _unusedSessionTimeout;
  601. }
  602. }
  603. - (void)setUnusedSessionTimeout:(NSTimeInterval)timeout {
  604. @synchronized(self) {
  605. GTMSessionMonitorSynchronized(self);
  606. _unusedSessionTimeout = timeout;
  607. _delegateDispatcher.discardInterval = timeout;
  608. }
  609. }
  610. // This method should be called inside of @synchronized(self)
  611. - (void)abandonDispatcher {
  612. GTMSessionCheckSynchronized(self);
  613. [_delegateDispatcher abandon];
  614. }
  615. - (NSDictionary *)runningFetchersByHost {
  616. @synchronized(self) {
  617. GTMSessionMonitorSynchronized(self);
  618. return [_runningFetchersByHost copy];
  619. }
  620. }
  621. - (void)setRunningFetchersByHost:(NSDictionary *)dict {
  622. @synchronized(self) {
  623. GTMSessionMonitorSynchronized(self);
  624. _runningFetchersByHost = [dict mutableCopy];
  625. }
  626. }
  627. - (NSDictionary *)delayedFetchersByHost {
  628. @synchronized(self) {
  629. GTMSessionMonitorSynchronized(self);
  630. return [_delayedFetchersByHost copy];
  631. }
  632. }
  633. - (void)setDelayedFetchersByHost:(NSDictionary *)dict {
  634. @synchronized(self) {
  635. GTMSessionMonitorSynchronized(self);
  636. _delayedFetchersByHost = [dict mutableCopy];
  637. }
  638. }
  639. - (id<GTMFetcherAuthorizationProtocol>)authorizer {
  640. @synchronized(self) {
  641. GTMSessionMonitorSynchronized(self);
  642. return _authorizer;
  643. }
  644. }
  645. - (void)setAuthorizer:(id<GTMFetcherAuthorizationProtocol>)obj {
  646. @synchronized(self) {
  647. GTMSessionMonitorSynchronized(self);
  648. if (obj != _authorizer) {
  649. [self detachAuthorizer];
  650. }
  651. _authorizer = obj;
  652. }
  653. // Use the fetcher service for the authorization fetches if the auth
  654. // object supports fetcher services
  655. if ([obj respondsToSelector:@selector(setFetcherService:)]) {
  656. #if GTM_USE_SESSION_FETCHER
  657. [obj setFetcherService:self];
  658. #else
  659. [obj setFetcherService:(id)self];
  660. #endif
  661. }
  662. }
  663. // This should be called inside a @synchronized(self) block except during dealloc.
  664. - (void)detachAuthorizer {
  665. // This method is called by the fetcher service's dealloc and setAuthorizer:
  666. // methods; do not override.
  667. //
  668. // The fetcher service retains the authorizer, and the authorizer has a
  669. // weak pointer to the fetcher service (a non-zeroing pointer for
  670. // compatibility with iOS 4 and Mac OS X 10.5/10.6.)
  671. //
  672. // When this fetcher service no longer uses the authorizer, we want to remove
  673. // the authorizer's dependence on the fetcher service. Authorizers can still
  674. // function without a fetcher service.
  675. if ([_authorizer respondsToSelector:@selector(fetcherService)]) {
  676. id authFetcherService = [_authorizer fetcherService];
  677. if (authFetcherService == self) {
  678. [_authorizer setFetcherService:nil];
  679. }
  680. }
  681. }
  682. - (dispatch_queue_t GTM_NONNULL_TYPE)callbackQueue {
  683. @synchronized(self) {
  684. GTMSessionMonitorSynchronized(self);
  685. return _callbackQueue;
  686. } // @synchronized(self)
  687. }
  688. - (void)setCallbackQueue:(dispatch_queue_t GTM_NULLABLE_TYPE)queue {
  689. @synchronized(self) {
  690. GTMSessionMonitorSynchronized(self);
  691. _callbackQueue = queue ?: dispatch_get_main_queue();
  692. } // @synchronized(self)
  693. }
  694. - (NSOperationQueue * GTM_NONNULL_TYPE)sessionDelegateQueue {
  695. @synchronized(self) {
  696. GTMSessionMonitorSynchronized(self);
  697. return _delegateQueue;
  698. } // @synchronized(self)
  699. }
  700. - (void)setSessionDelegateQueue:(NSOperationQueue * GTM_NULLABLE_TYPE)queue {
  701. @synchronized(self) {
  702. GTMSessionMonitorSynchronized(self);
  703. _delegateQueue = queue ?: [NSOperationQueue mainQueue];
  704. } // @synchronized(self)
  705. }
  706. - (NSOperationQueue *)delegateQueue {
  707. // Provided for compatibility with the old fetcher service. The gtm-oauth2 code respects
  708. // any custom delegate queue for calling the app.
  709. return nil;
  710. }
  711. + (NSUInteger)numberOfNonBackgroundSessionFetchers:(NSArray *)fetchers {
  712. NSUInteger sum = 0;
  713. for (GTMSessionFetcher *fetcher in fetchers) {
  714. if (!fetcher.usingBackgroundSession) {
  715. ++sum;
  716. }
  717. }
  718. return sum;
  719. }
  720. @end
  721. @implementation GTMSessionFetcherService (TestingSupport)
  722. + (instancetype)mockFetcherServiceWithFakedData:(NSData *)fakedDataOrNil
  723. fakedError:(NSError *)fakedErrorOrNil {
  724. #if !GTM_DISABLE_FETCHER_TEST_BLOCK
  725. NSURL *url = [NSURL URLWithString:@"http://example.invalid"];
  726. NSHTTPURLResponse *fakedResponse =
  727. [[NSHTTPURLResponse alloc] initWithURL:url
  728. statusCode:(fakedErrorOrNil ? 500 : 200)
  729. HTTPVersion:@"HTTP/1.1"
  730. headerFields:nil];
  731. return [self mockFetcherServiceWithFakedData:fakedDataOrNil
  732. fakedResponse:fakedResponse
  733. fakedError:fakedErrorOrNil];
  734. #else
  735. GTMSESSION_ASSERT_DEBUG(0, @"Test blocks disabled");
  736. return nil;
  737. #endif // GTM_DISABLE_FETCHER_TEST_BLOCK
  738. }
  739. + (instancetype)mockFetcherServiceWithFakedData:(NSData *)fakedDataOrNil
  740. fakedResponse:(NSHTTPURLResponse *)fakedResponse
  741. fakedError:(NSError *)fakedErrorOrNil {
  742. #if !GTM_DISABLE_FETCHER_TEST_BLOCK
  743. GTMSessionFetcherService *service = [[self alloc] init];
  744. service.allowedInsecureSchemes = @[ @"http" ];
  745. service.testBlock = ^(GTMSessionFetcher *fetcherToTest,
  746. GTMSessionFetcherTestResponse testResponse) {
  747. testResponse(fakedResponse, fakedDataOrNil, fakedErrorOrNil);
  748. };
  749. return service;
  750. #else
  751. GTMSESSION_ASSERT_DEBUG(0, @"Test blocks disabled");
  752. return nil;
  753. #endif // GTM_DISABLE_FETCHER_TEST_BLOCK
  754. }
  755. #pragma mark Synchronous Wait for Unit Testing
  756. - (BOOL)waitForCompletionOfAllFetchersWithTimeout:(NSTimeInterval)timeoutInSeconds {
  757. NSDate *giveUpDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds];
  758. _stoppedFetchersToWaitFor = [NSMutableArray array];
  759. BOOL shouldSpinRunLoop = [NSThread isMainThread];
  760. const NSTimeInterval kSpinInterval = 0.001;
  761. BOOL didTimeOut = NO;
  762. while (([self numberOfFetchers] > 0 || _stoppedFetchersToWaitFor.count > 0)) {
  763. didTimeOut = [giveUpDate timeIntervalSinceNow] < 0;
  764. if (didTimeOut) break;
  765. GTMSessionFetcher *stoppedFetcher = _stoppedFetchersToWaitFor.firstObject;
  766. if (stoppedFetcher) {
  767. [_stoppedFetchersToWaitFor removeObject:stoppedFetcher];
  768. [stoppedFetcher waitForCompletionWithTimeout:10.0 * kSpinInterval];
  769. }
  770. if (shouldSpinRunLoop) {
  771. NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:kSpinInterval];
  772. [[NSRunLoop currentRunLoop] runUntilDate:stopDate];
  773. } else {
  774. [NSThread sleepForTimeInterval:kSpinInterval];
  775. }
  776. }
  777. _stoppedFetchersToWaitFor = nil;
  778. return !didTimeOut;
  779. }
  780. @end
  781. @implementation GTMSessionFetcherService (BackwardsCompatibilityOnly)
  782. - (NSInteger)cookieStorageMethod {
  783. @synchronized(self) {
  784. GTMSessionMonitorSynchronized(self);
  785. return _cookieStorageMethod;
  786. }
  787. }
  788. - (void)setCookieStorageMethod:(NSInteger)cookieStorageMethod {
  789. @synchronized(self) {
  790. GTMSessionMonitorSynchronized(self);
  791. _cookieStorageMethod = cookieStorageMethod;
  792. }
  793. }
  794. @end
  795. @implementation GTMSessionFetcherSessionDelegateDispatcher {
  796. __weak GTMSessionFetcherService *_parentService;
  797. NSURLSession *_session;
  798. // The task map maps NSURLSessionTasks to GTMSessionFetchers
  799. NSMutableDictionary *_taskToFetcherMap;
  800. // The discard timer will invalidate sessions after the session's last task completes.
  801. NSTimer *_discardTimer;
  802. NSTimeInterval _discardInterval;
  803. }
  804. @synthesize discardInterval = _discardInterval,
  805. session = _session;
  806. - (instancetype)init {
  807. [self doesNotRecognizeSelector:_cmd];
  808. return nil;
  809. }
  810. - (instancetype)initWithParentService:(GTMSessionFetcherService *)parentService
  811. sessionDiscardInterval:(NSTimeInterval)discardInterval {
  812. self = [super init];
  813. if (self) {
  814. _discardInterval = discardInterval;
  815. _parentService = parentService;
  816. }
  817. return self;
  818. }
  819. - (NSString *)description {
  820. return [NSString stringWithFormat:@"%@ %p %@ %@",
  821. [self class], self,
  822. _session ?: @"<no session>",
  823. _taskToFetcherMap.count > 0 ? _taskToFetcherMap : @"<no tasks>"];
  824. }
  825. - (NSTimer *)discardTimer {
  826. GTMSessionCheckNotSynchronized(self);
  827. @synchronized(self) {
  828. return _discardTimer;
  829. }
  830. }
  831. // This method should be called inside of a @synchronized(self) block.
  832. - (void)startDiscardTimer {
  833. GTMSessionCheckSynchronized(self);
  834. [_discardTimer invalidate];
  835. _discardTimer = nil;
  836. if (_discardInterval > 0) {
  837. _discardTimer = [NSTimer timerWithTimeInterval:_discardInterval
  838. target:self
  839. selector:@selector(discardTimerFired:)
  840. userInfo:nil
  841. repeats:NO];
  842. [_discardTimer setTolerance:(_discardInterval / 10)];
  843. [[NSRunLoop mainRunLoop] addTimer:_discardTimer forMode:NSRunLoopCommonModes];
  844. }
  845. }
  846. // This method should be called inside of a @synchronized(self) block.
  847. - (void)destroyDiscardTimer {
  848. GTMSessionCheckSynchronized(self);
  849. [_discardTimer invalidate];
  850. _discardTimer = nil;
  851. }
  852. - (void)discardTimerFired:(NSTimer *)timer {
  853. GTMSessionFetcherService *service;
  854. @synchronized(self) {
  855. GTMSessionMonitorSynchronized(self);
  856. NSUInteger numberOfTasks = _taskToFetcherMap.count;
  857. if (numberOfTasks == 0) {
  858. service = _parentService;
  859. }
  860. }
  861. // Inform the service that the discard timer has fired, and should check whether the
  862. // service can abandon us. -resetSession cannot be called directly, as there is a
  863. // race condition that must be guarded against with the NSURLSession being returned
  864. // from sessionForFetcherCreation outside other locks. The service can take steps
  865. // to prevent resetting the session if that has occurred.
  866. //
  867. // The service must be called from outside the @synchronized block.
  868. [service resetSessionForDispatcherDiscardTimer:timer];
  869. }
  870. - (void)abandon {
  871. @synchronized(self) {
  872. GTMSessionMonitorSynchronized(self);
  873. [self destroySessionAndTimer];
  874. }
  875. }
  876. - (void)startSessionUsage {
  877. @synchronized(self) {
  878. GTMSessionMonitorSynchronized(self);
  879. [self destroyDiscardTimer];
  880. }
  881. }
  882. // This method should be called inside of a @synchronized(self) block.
  883. - (void)destroySessionAndTimer {
  884. GTMSessionCheckSynchronized(self);
  885. [self destroyDiscardTimer];
  886. // Break any retain cycle from the session holding the delegate.
  887. [_session finishTasksAndInvalidate];
  888. // Immediately clear the session so no new task may be issued with it.
  889. //
  890. // The _taskToFetcherMap needs to stay valid until the outstanding tasks finish.
  891. _session = nil;
  892. }
  893. - (void)setFetcher:(GTMSessionFetcher *)fetcher forTask:(NSURLSessionTask *)task {
  894. GTMSESSION_ASSERT_DEBUG(fetcher != nil, @"missing fetcher");
  895. @synchronized(self) {
  896. GTMSessionMonitorSynchronized(self);
  897. if (_taskToFetcherMap == nil) {
  898. _taskToFetcherMap = [[NSMutableDictionary alloc] init];
  899. }
  900. if (fetcher) {
  901. [_taskToFetcherMap setObject:fetcher forKey:task];
  902. [self destroyDiscardTimer];
  903. }
  904. }
  905. }
  906. - (void)removeFetcher:(GTMSessionFetcher *)fetcher {
  907. @synchronized(self) {
  908. GTMSessionMonitorSynchronized(self);
  909. // Typically, a fetcher should be removed when its task invokes
  910. // URLSession:task:didCompleteWithError:.
  911. //
  912. // When fetching with a testBlock, though, the task completed delegate
  913. // method may not be invoked, requiring cleanup here.
  914. NSArray *tasks = [_taskToFetcherMap allKeysForObject:fetcher];
  915. GTMSESSION_ASSERT_DEBUG(tasks.count <= 1, @"fetcher task not unmapped: %@", tasks);
  916. [_taskToFetcherMap removeObjectsForKeys:tasks];
  917. if (_taskToFetcherMap.count == 0) {
  918. [self startDiscardTimer];
  919. }
  920. }
  921. }
  922. // This helper method provides synchronized access to the task map for the delegate
  923. // methods below.
  924. - (id)fetcherForTask:(NSURLSessionTask *)task {
  925. @synchronized(self) {
  926. GTMSessionMonitorSynchronized(self);
  927. return [_taskToFetcherMap objectForKey:task];
  928. }
  929. }
  930. - (void)removeTaskFromMap:(NSURLSessionTask *)task {
  931. @synchronized(self) {
  932. GTMSessionMonitorSynchronized(self);
  933. [_taskToFetcherMap removeObjectForKey:task];
  934. }
  935. }
  936. - (void)setSession:(NSURLSession *)session {
  937. @synchronized(self) {
  938. GTMSessionMonitorSynchronized(self);
  939. _session = session;
  940. }
  941. }
  942. - (NSURLSession *)session {
  943. @synchronized(self) {
  944. GTMSessionMonitorSynchronized(self);
  945. return _session;
  946. }
  947. }
  948. - (NSTimeInterval)discardInterval {
  949. @synchronized(self) {
  950. GTMSessionMonitorSynchronized(self);
  951. return _discardInterval;
  952. }
  953. }
  954. - (void)setDiscardInterval:(NSTimeInterval)interval {
  955. @synchronized(self) {
  956. GTMSessionMonitorSynchronized(self);
  957. _discardInterval = interval;
  958. }
  959. }
  960. // NSURLSessionDelegate protocol methods.
  961. // - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session;
  962. //
  963. // TODO(seh): How do we route this to an appropriate fetcher?
  964. - (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error {
  965. GTM_LOG_SESSION_DELEGATE(@"%@ %p URLSession:%@ didBecomeInvalidWithError:%@",
  966. [self class], self, session, error);
  967. NSDictionary *localTaskToFetcherMap;
  968. @synchronized(self) {
  969. GTMSessionMonitorSynchronized(self);
  970. _session = nil;
  971. localTaskToFetcherMap = [_taskToFetcherMap copy];
  972. }
  973. // Any "suspended" tasks may not have received callbacks from NSURLSession when the session
  974. // completes; we'll call them now.
  975. [localTaskToFetcherMap enumerateKeysAndObjectsUsingBlock:^(NSURLSessionTask *task,
  976. GTMSessionFetcher *fetcher,
  977. BOOL *stop) {
  978. if (fetcher.session == session) {
  979. // Our delegate method URLSession:task:didCompleteWithError: will rely on
  980. // _taskToFetcherMap so that should still contain this fetcher.
  981. NSError *canceledError = [NSError errorWithDomain:NSURLErrorDomain
  982. code:NSURLErrorCancelled
  983. userInfo:nil];
  984. [self URLSession:session task:task didCompleteWithError:canceledError];
  985. } else {
  986. GTMSESSION_ASSERT_DEBUG(0, @"Unexpected session in fetcher: %@ has %@ (expected %@)",
  987. fetcher, fetcher.session, session);
  988. }
  989. }];
  990. // Our tests rely on this notification to know the session discard timer fired.
  991. NSDictionary *userInfo = @{ kGTMSessionFetcherServiceSessionKey : session };
  992. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  993. [nc postNotificationName:kGTMSessionFetcherServiceSessionBecameInvalidNotification
  994. object:_parentService
  995. userInfo:userInfo];
  996. }
  997. #pragma mark - NSURLSessionTaskDelegate
  998. // NSURLSessionTaskDelegate protocol methods.
  999. //
  1000. // We won't test here if the fetcher responds to these since we only want this
  1001. // class to implement the same delegate methods the fetcher does (so NSURLSession's
  1002. // tests for respondsToSelector: will have the same result whether the session
  1003. // delegate is the fetcher or this dispatcher.)
  1004. - (void)URLSession:(NSURLSession *)session
  1005. task:(NSURLSessionTask *)task
  1006. willPerformHTTPRedirection:(NSHTTPURLResponse *)response
  1007. newRequest:(NSURLRequest *)request
  1008. completionHandler:(void (^)(NSURLRequest *))completionHandler {
  1009. id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task];
  1010. [fetcher URLSession:session
  1011. task:task
  1012. willPerformHTTPRedirection:response
  1013. newRequest:request
  1014. completionHandler:completionHandler];
  1015. }
  1016. - (void)URLSession:(NSURLSession *)session
  1017. task:(NSURLSessionTask *)task
  1018. didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
  1019. completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))handler {
  1020. id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task];
  1021. [fetcher URLSession:session
  1022. task:task
  1023. didReceiveChallenge:challenge
  1024. completionHandler:handler];
  1025. }
  1026. - (void)URLSession:(NSURLSession *)session
  1027. task:(NSURLSessionTask *)task
  1028. needNewBodyStream:(void (^)(NSInputStream *bodyStream))handler {
  1029. id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task];
  1030. [fetcher URLSession:session
  1031. task:task
  1032. needNewBodyStream:handler];
  1033. }
  1034. - (void)URLSession:(NSURLSession *)session
  1035. task:(NSURLSessionTask *)task
  1036. didSendBodyData:(int64_t)bytesSent
  1037. totalBytesSent:(int64_t)totalBytesSent
  1038. totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
  1039. id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task];
  1040. [fetcher URLSession:session
  1041. task:task
  1042. didSendBodyData:bytesSent
  1043. totalBytesSent:totalBytesSent
  1044. totalBytesExpectedToSend:totalBytesExpectedToSend];
  1045. }
  1046. - (void)URLSession:(NSURLSession *)session
  1047. task:(NSURLSessionTask *)task
  1048. didCompleteWithError:(NSError *)error {
  1049. id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task];
  1050. // This is the usual way tasks are removed from the task map.
  1051. [self removeTaskFromMap:task];
  1052. [fetcher URLSession:session
  1053. task:task
  1054. didCompleteWithError:error];
  1055. }
  1056. - (void)URLSession:(NSURLSession *)session
  1057. task:(NSURLSessionTask *)task
  1058. didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics
  1059. API_AVAILABLE(ios(10.0), macosx(10.12), tvos(10.0), watchos(3.0)) {
  1060. id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task];
  1061. [fetcher URLSession:session task:task didFinishCollectingMetrics:metrics];
  1062. }
  1063. // NSURLSessionDataDelegate protocol methods.
  1064. - (void)URLSession:(NSURLSession *)session
  1065. dataTask:(NSURLSessionDataTask *)dataTask
  1066. didReceiveResponse:(NSURLResponse *)response
  1067. completionHandler:(void (^)(NSURLSessionResponseDisposition))handler {
  1068. id<NSURLSessionDataDelegate> fetcher = [self fetcherForTask:dataTask];
  1069. [fetcher URLSession:session
  1070. dataTask:dataTask
  1071. didReceiveResponse:response
  1072. completionHandler:handler];
  1073. }
  1074. - (void)URLSession:(NSURLSession *)session
  1075. dataTask:(NSURLSessionDataTask *)dataTask
  1076. didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask {
  1077. id<NSURLSessionDataDelegate> fetcher = [self fetcherForTask:dataTask];
  1078. GTMSESSION_ASSERT_DEBUG(fetcher != nil, @"Missing fetcher for %@", dataTask);
  1079. [self removeTaskFromMap:dataTask];
  1080. if (fetcher) {
  1081. GTMSESSION_ASSERT_DEBUG([fetcher isKindOfClass:[GTMSessionFetcher class]],
  1082. @"Expecting GTMSessionFetcher");
  1083. [self setFetcher:(GTMSessionFetcher *)fetcher forTask:downloadTask];
  1084. }
  1085. [fetcher URLSession:session
  1086. dataTask:dataTask
  1087. didBecomeDownloadTask:downloadTask];
  1088. }
  1089. - (void)URLSession:(NSURLSession *)session
  1090. dataTask:(NSURLSessionDataTask *)dataTask
  1091. didReceiveData:(NSData *)data {
  1092. id<NSURLSessionDataDelegate> fetcher = [self fetcherForTask:dataTask];
  1093. [fetcher URLSession:session
  1094. dataTask:dataTask
  1095. didReceiveData:data];
  1096. }
  1097. - (void)URLSession:(NSURLSession *)session
  1098. dataTask:(NSURLSessionDataTask *)dataTask
  1099. willCacheResponse:(NSCachedURLResponse *)proposedResponse
  1100. completionHandler:(void (^)(NSCachedURLResponse *))handler {
  1101. id<NSURLSessionDataDelegate> fetcher = [self fetcherForTask:dataTask];
  1102. [fetcher URLSession:session
  1103. dataTask:dataTask
  1104. willCacheResponse:proposedResponse
  1105. completionHandler:handler];
  1106. }
  1107. // NSURLSessionDownloadDelegate protocol methods.
  1108. - (void)URLSession:(NSURLSession *)session
  1109. downloadTask:(NSURLSessionDownloadTask *)downloadTask
  1110. didFinishDownloadingToURL:(NSURL *)location {
  1111. id<NSURLSessionDownloadDelegate> fetcher = [self fetcherForTask:downloadTask];
  1112. [fetcher URLSession:session
  1113. downloadTask:downloadTask
  1114. didFinishDownloadingToURL:location];
  1115. }
  1116. - (void)URLSession:(NSURLSession *)session
  1117. downloadTask:(NSURLSessionDownloadTask *)downloadTask
  1118. didWriteData:(int64_t)bytesWritten
  1119. totalBytesWritten:(int64_t)totalWritten
  1120. totalBytesExpectedToWrite:(int64_t)totalExpected {
  1121. id<NSURLSessionDownloadDelegate> fetcher = [self fetcherForTask:downloadTask];
  1122. [fetcher URLSession:session
  1123. downloadTask:downloadTask
  1124. didWriteData:bytesWritten
  1125. totalBytesWritten:totalWritten
  1126. totalBytesExpectedToWrite:totalExpected];
  1127. }
  1128. - (void)URLSession:(NSURLSession *)session
  1129. downloadTask:(NSURLSessionDownloadTask *)downloadTask
  1130. didResumeAtOffset:(int64_t)fileOffset
  1131. expectedTotalBytes:(int64_t)expectedTotalBytes {
  1132. id<NSURLSessionDownloadDelegate> fetcher = [self fetcherForTask:downloadTask];
  1133. [fetcher URLSession:session
  1134. downloadTask:downloadTask
  1135. didResumeAtOffset:fileOffset
  1136. expectedTotalBytes:expectedTotalBytes];
  1137. }
  1138. @end