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.

1352 lines
45 KiB

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