GTMSessionFetcherService.m 46 KB

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