GTMSessionFetcherService.m 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380
  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. [self fetcherDidStop:fetcher callbacksPending:false];
  400. }
  401. - (void)fetcherDidStop:(GTMSessionFetcher *)fetcher callbacksPending:(BOOL) callbacksPending {
  402. // Entry point from the fetcher
  403. NSString *host = fetcher.serviceHost;
  404. if (!host) {
  405. // fetcher has been stopped previously
  406. return;
  407. }
  408. // This removeFetcher: invocation is a fallback; typically, fetchers are removed from the task
  409. // map when the task completes.
  410. if (!callbacksPending) {
  411. GTMSessionFetcherSessionDelegateDispatcher *delegateDispatcher =
  412. [self delegateDispatcherForFetcher:fetcher];
  413. [delegateDispatcher removeFetcher:fetcher];
  414. }
  415. NSMutableArray *fetchersToStart;
  416. @synchronized(self) {
  417. GTMSessionMonitorSynchronized(self);
  418. // If a test is waiting for all fetchers to stop, it needs to wait for this one
  419. // to invoke its callbacks on the callback queue.
  420. [_stoppedFetchersToWaitFor addObject:fetcher];
  421. NSMutableArray *runningForHost = [_runningFetchersByHost objectForKey:host];
  422. [runningForHost removeObject:fetcher];
  423. NSMutableArray *delayedForHost = [_delayedFetchersByHost objectForKey:host];
  424. [delayedForHost removeObject:fetcher];
  425. while (delayedForHost.count > 0 &&
  426. [[self class] numberOfNonBackgroundSessionFetchers:runningForHost] <
  427. _maxRunningFetchersPerHost) {
  428. // Start another delayed fetcher running, scanning for the minimum
  429. // priority value, defaulting to FIFO for equal priorities
  430. GTMSessionFetcher *nextFetcher = nil;
  431. for (GTMSessionFetcher *delayedFetcher in delayedForHost) {
  432. if (nextFetcher == nil || delayedFetcher.servicePriority < nextFetcher.servicePriority) {
  433. nextFetcher = delayedFetcher;
  434. }
  435. }
  436. if (nextFetcher) {
  437. [self addRunningFetcher:nextFetcher forHost:host];
  438. runningForHost = [_runningFetchersByHost objectForKey:host];
  439. [delayedForHost removeObjectIdenticalTo:nextFetcher];
  440. if (!fetchersToStart) {
  441. fetchersToStart = [NSMutableArray array];
  442. }
  443. [fetchersToStart addObject:nextFetcher];
  444. }
  445. }
  446. if (runningForHost.count == 0) {
  447. // None left; remove the empty array
  448. [_runningFetchersByHost removeObjectForKey:host];
  449. }
  450. if (delayedForHost.count == 0) {
  451. [_delayedFetchersByHost removeObjectForKey:host];
  452. }
  453. } // @synchronized(self)
  454. // Start fetchers outside of the synchronized block to avoid a deadlock.
  455. for (GTMSessionFetcher *nextFetcher in fetchersToStart) {
  456. [self startFetcher:nextFetcher];
  457. }
  458. // The fetcher is no longer in the running or the delayed array,
  459. // so remove its host and thread properties
  460. fetcher.serviceHost = nil;
  461. }
  462. - (NSUInteger)numberOfFetchers {
  463. NSUInteger running = [self numberOfRunningFetchers];
  464. NSUInteger delayed = [self numberOfDelayedFetchers];
  465. return running + delayed;
  466. }
  467. - (NSUInteger)numberOfRunningFetchers {
  468. @synchronized(self) {
  469. GTMSessionMonitorSynchronized(self);
  470. NSUInteger sum = 0;
  471. for (NSString *host in _runningFetchersByHost) {
  472. NSArray *fetchers = [_runningFetchersByHost objectForKey:host];
  473. sum += fetchers.count;
  474. }
  475. return sum;
  476. }
  477. }
  478. - (NSUInteger)numberOfDelayedFetchers {
  479. @synchronized(self) {
  480. GTMSessionMonitorSynchronized(self);
  481. NSUInteger sum = 0;
  482. for (NSString *host in _delayedFetchersByHost) {
  483. NSArray *fetchers = [_delayedFetchersByHost objectForKey:host];
  484. sum += fetchers.count;
  485. }
  486. return sum;
  487. }
  488. }
  489. - (NSArray *)issuedFetchers {
  490. @synchronized(self) {
  491. GTMSessionMonitorSynchronized(self);
  492. NSMutableArray *allFetchers = [NSMutableArray array];
  493. void (^accumulateFetchers)(id, id, BOOL *) =
  494. ^(NSString *host, NSArray *fetchersForHost, BOOL *stop) {
  495. [allFetchers addObjectsFromArray:fetchersForHost];
  496. };
  497. [_runningFetchersByHost enumerateKeysAndObjectsUsingBlock:accumulateFetchers];
  498. [_delayedFetchersByHost enumerateKeysAndObjectsUsingBlock:accumulateFetchers];
  499. GTMSESSION_ASSERT_DEBUG(allFetchers.count == [NSSet setWithArray:allFetchers].count,
  500. @"Fetcher appears multiple times\n running: %@\n delayed: %@",
  501. _runningFetchersByHost, _delayedFetchersByHost);
  502. return allFetchers.count > 0 ? allFetchers : nil;
  503. }
  504. }
  505. - (NSArray *)issuedFetchersWithRequestURL:(NSURL *)requestURL {
  506. NSString *host = requestURL.host;
  507. if (host.length == 0) return nil;
  508. NSURL *targetURL = [requestURL absoluteURL];
  509. NSArray *allFetchers = [self issuedFetchers];
  510. NSIndexSet *indexes = [allFetchers
  511. indexesOfObjectsPassingTest:^BOOL(GTMSessionFetcher *fetcher, NSUInteger idx, BOOL *stop) {
  512. NSURL *fetcherURL = [fetcher.request.URL absoluteURL];
  513. return [fetcherURL isEqual:targetURL];
  514. }];
  515. NSArray *result = nil;
  516. if (indexes.count > 0) {
  517. result = [allFetchers objectsAtIndexes:indexes];
  518. }
  519. return result;
  520. }
  521. - (void)stopAllFetchers {
  522. NSArray *delayedFetchersByHost;
  523. NSArray *runningFetchersByHost;
  524. @synchronized(self) {
  525. GTMSessionMonitorSynchronized(self);
  526. // Set the time barrier so fetchers know not to call back even if
  527. // the stop calls below occur after the fetchers naturally
  528. // stopped and so were removed from _runningFetchersByHost,
  529. // but while the callbacks were already enqueued before stopAllFetchers
  530. // was invoked.
  531. _stoppedAllFetchersDate = [[NSDate alloc] init];
  532. // Remove fetchers from the delayed list to avoid fetcherDidStop: from
  533. // starting more fetchers running as a side effect of stopping one
  534. delayedFetchersByHost = _delayedFetchersByHost.allValues;
  535. [_delayedFetchersByHost removeAllObjects];
  536. runningFetchersByHost = _runningFetchersByHost.allValues;
  537. [_runningFetchersByHost removeAllObjects];
  538. }
  539. for (NSArray *delayedForHost in delayedFetchersByHost) {
  540. for (GTMSessionFetcher *fetcher in delayedForHost) {
  541. [self stopFetcher:fetcher];
  542. }
  543. }
  544. for (NSArray *runningForHost in runningFetchersByHost) {
  545. for (GTMSessionFetcher *fetcher in runningForHost) {
  546. [self stopFetcher:fetcher];
  547. }
  548. }
  549. }
  550. - (NSDate *)stoppedAllFetchersDate {
  551. @synchronized(self) {
  552. GTMSessionMonitorSynchronized(self);
  553. return _stoppedAllFetchersDate;
  554. }
  555. }
  556. #pragma mark Accessors
  557. - (BOOL)reuseSession {
  558. @synchronized(self) {
  559. GTMSessionMonitorSynchronized(self);
  560. return _delegateDispatcher != nil;
  561. }
  562. }
  563. - (void)setReuseSession:(BOOL)shouldReuse {
  564. @synchronized(self) {
  565. GTMSessionMonitorSynchronized(self);
  566. BOOL wasReusing = (_delegateDispatcher != nil);
  567. if (shouldReuse != wasReusing) {
  568. [self abandonDispatcher];
  569. if (shouldReuse) {
  570. _delegateDispatcher = [[GTMSessionFetcherSessionDelegateDispatcher alloc]
  571. initWithParentService:self
  572. sessionDiscardInterval:_unusedSessionTimeout];
  573. } else {
  574. _delegateDispatcher = nil;
  575. }
  576. }
  577. }
  578. }
  579. - (void)resetSession {
  580. GTMSessionCheckNotSynchronized(self);
  581. os_unfair_lock_lock(&_sessionCreationLock);
  582. @synchronized(self) {
  583. GTMSessionMonitorSynchronized(self);
  584. [self resetSessionInternal];
  585. }
  586. os_unfair_lock_unlock(&_sessionCreationLock);
  587. }
  588. - (void)resetSessionInternal {
  589. GTMSessionCheckSynchronized(self);
  590. // The old dispatchers may be retained as delegates of any ongoing sessions by those sessions.
  591. if (_delegateDispatcher) {
  592. [self abandonDispatcher];
  593. _delegateDispatcher = [[GTMSessionFetcherSessionDelegateDispatcher alloc]
  594. initWithParentService:self
  595. sessionDiscardInterval:_unusedSessionTimeout];
  596. }
  597. }
  598. - (void)resetSessionForDispatcherDiscardTimer:(NSTimer *)timer {
  599. GTMSessionCheckNotSynchronized(self);
  600. os_unfair_lock_lock(&_sessionCreationLock);
  601. @synchronized(self) {
  602. GTMSessionMonitorSynchronized(self);
  603. if (_delegateDispatcher.discardTimer == timer) {
  604. // If the delegate dispatcher's current discardTimer is the same object as the timer
  605. // that fired, no fetcher has recently attempted to start using the session by calling
  606. // startSessionUsage, which invalidates and nils out the timer.
  607. [self resetSessionInternal];
  608. } else {
  609. // A fetcher has invalidated the timer between its triggering and now, potentially
  610. // meaning a fetcher has requested access to the NSURLSession, and may be in the process
  611. // of starting a new task. The dispatcher should not be abandoned, as this can lead
  612. // to a race condition between calling -finishTasksAndInvalidate on the NSURLSession
  613. // and the fetcher attempting to create a new task.
  614. }
  615. }
  616. os_unfair_lock_unlock(&_sessionCreationLock);
  617. }
  618. - (NSTimeInterval)unusedSessionTimeout {
  619. @synchronized(self) {
  620. GTMSessionMonitorSynchronized(self);
  621. return _unusedSessionTimeout;
  622. }
  623. }
  624. - (void)setUnusedSessionTimeout:(NSTimeInterval)timeout {
  625. @synchronized(self) {
  626. GTMSessionMonitorSynchronized(self);
  627. _unusedSessionTimeout = timeout;
  628. _delegateDispatcher.discardInterval = timeout;
  629. }
  630. }
  631. // This method should be called inside of @synchronized(self)
  632. - (void)abandonDispatcher {
  633. GTMSessionCheckSynchronized(self);
  634. [_delegateDispatcher abandon];
  635. }
  636. - (NSDictionary *)runningFetchersByHost {
  637. @synchronized(self) {
  638. GTMSessionMonitorSynchronized(self);
  639. return [_runningFetchersByHost copy];
  640. }
  641. }
  642. - (void)setRunningFetchersByHost:(NSDictionary *)dict {
  643. @synchronized(self) {
  644. GTMSessionMonitorSynchronized(self);
  645. _runningFetchersByHost = [dict mutableCopy];
  646. }
  647. }
  648. - (NSDictionary *)delayedFetchersByHost {
  649. @synchronized(self) {
  650. GTMSessionMonitorSynchronized(self);
  651. return [_delayedFetchersByHost copy];
  652. }
  653. }
  654. - (void)setDelayedFetchersByHost:(NSDictionary *)dict {
  655. @synchronized(self) {
  656. GTMSessionMonitorSynchronized(self);
  657. _delayedFetchersByHost = [dict mutableCopy];
  658. }
  659. }
  660. #pragma clang diagnostic push
  661. #pragma clang diagnostic ignored "-Wdeprecated"
  662. - (id<GTMFetcherAuthorizationProtocol>)authorizer {
  663. @synchronized(self) {
  664. GTMSessionMonitorSynchronized(self);
  665. return _authorizer;
  666. }
  667. }
  668. - (void)setAuthorizer:(id<GTMFetcherAuthorizationProtocol>)obj {
  669. @synchronized(self) {
  670. GTMSessionMonitorSynchronized(self);
  671. if (obj != _authorizer) {
  672. [self detachAuthorizer];
  673. }
  674. _authorizer = obj;
  675. }
  676. // Use the fetcher service for the authorization fetches if the auth
  677. // object supports fetcher services
  678. if ([obj respondsToSelector:@selector(setFetcherService:)]) {
  679. [obj setFetcherService:self];
  680. }
  681. }
  682. #pragma clang diagnostic pop
  683. // This should be called inside a @synchronized(self) block except during dealloc.
  684. - (void)detachAuthorizer {
  685. // This method is called by the fetcher service's dealloc and setAuthorizer:
  686. // methods; do not override.
  687. //
  688. // The fetcher service retains the authorizer, and the authorizer has a
  689. // weak pointer to the fetcher service (a non-zeroing pointer for
  690. // compatibility with iOS 4 and Mac OS X 10.5/10.6.)
  691. //
  692. // When this fetcher service no longer uses the authorizer, we want to remove
  693. // the authorizer's dependence on the fetcher service. Authorizers can still
  694. // function without a fetcher service.
  695. if ([_authorizer respondsToSelector:@selector(fetcherService)]) {
  696. id authFetcherService = [_authorizer fetcherService];
  697. if (authFetcherService == self) {
  698. [_authorizer setFetcherService:nil];
  699. }
  700. }
  701. }
  702. - (nonnull dispatch_queue_t)callbackQueue {
  703. @synchronized(self) {
  704. GTMSessionMonitorSynchronized(self);
  705. return _callbackQueue;
  706. } // @synchronized(self)
  707. }
  708. - (void)setCallbackQueue:(dispatch_queue_t)queue {
  709. [self setCallbackQueue:queue isConcurrent:NO];
  710. }
  711. - (void)setConcurrentCallbackQueue:(dispatch_queue_t)queue {
  712. [self setCallbackQueue:queue isConcurrent:YES];
  713. }
  714. - (void)setCallbackQueue:(dispatch_queue_t)queue isConcurrent:(BOOL)isConcurrent {
  715. @synchronized(self) {
  716. GTMSessionMonitorSynchronized(self);
  717. #if DEBUG
  718. // Warn when changing from a concurrent queue to a serial queue.
  719. if (_callbackQueueIsConcurrent && (!isConcurrent || !queue)) {
  720. GTMSESSION_LOG_DEBUG(
  721. @"WARNING: Resetting the service callback queue from concurrent to serial");
  722. }
  723. #endif // DEBUG
  724. _callbackQueue = queue ?: dispatch_get_main_queue();
  725. _callbackQueueIsConcurrent = queue ? isConcurrent : NO;
  726. } // @synchronized(self)
  727. }
  728. - (NSOperationQueue *)sessionDelegateQueue {
  729. @synchronized(self) {
  730. GTMSessionMonitorSynchronized(self);
  731. return _delegateQueue;
  732. } // @synchronized(self)
  733. }
  734. - (void)setSessionDelegateQueue:(NSOperationQueue *)queue {
  735. @synchronized(self) {
  736. GTMSessionMonitorSynchronized(self);
  737. _delegateQueue = queue ?: [NSOperationQueue mainQueue];
  738. } // @synchronized(self)
  739. }
  740. - (NSOperationQueue *)delegateQueue {
  741. // Provided for compatibility with the old fetcher service. The gtm-oauth2 code respects
  742. // any custom delegate queue for calling the app.
  743. return nil;
  744. }
  745. + (NSUInteger)numberOfNonBackgroundSessionFetchers:(NSArray *)fetchers {
  746. NSUInteger sum = 0;
  747. for (GTMSessionFetcher *fetcher in fetchers) {
  748. if (!fetcher.usingBackgroundSession) {
  749. ++sum;
  750. }
  751. }
  752. return sum;
  753. }
  754. @end
  755. @implementation GTMSessionFetcherService (TestingSupport)
  756. + (instancetype)mockFetcherServiceWithFakedData:(NSData *)fakedDataOrNil
  757. fakedError:(NSError *)fakedErrorOrNil {
  758. #if !GTM_DISABLE_FETCHER_TEST_BLOCK
  759. NSURL *url = [NSURL URLWithString:@"http://example.invalid"];
  760. NSHTTPURLResponse *fakedResponse =
  761. [[NSHTTPURLResponse alloc] initWithURL:url
  762. statusCode:(fakedErrorOrNil ? 500 : 200)HTTPVersion:@"HTTP/1.1"
  763. headerFields:nil];
  764. return [self mockFetcherServiceWithFakedData:fakedDataOrNil
  765. fakedResponse:fakedResponse
  766. fakedError:fakedErrorOrNil];
  767. #else
  768. GTMSESSION_ASSERT_DEBUG(0, @"Test blocks disabled");
  769. return nil;
  770. #endif // GTM_DISABLE_FETCHER_TEST_BLOCK
  771. }
  772. + (instancetype)mockFetcherServiceWithFakedData:(NSData *)fakedDataOrNil
  773. fakedResponse:(NSHTTPURLResponse *)fakedResponse
  774. fakedError:(NSError *)fakedErrorOrNil {
  775. #if !GTM_DISABLE_FETCHER_TEST_BLOCK
  776. GTMSessionFetcherService *service = [[self alloc] init];
  777. service.allowedInsecureSchemes = @[ @"http" ];
  778. service.testBlock =
  779. ^(GTMSessionFetcher *fetcherToTest, GTMSessionFetcherTestResponse testResponse) {
  780. testResponse(fakedResponse, fakedDataOrNil, fakedErrorOrNil);
  781. };
  782. return service;
  783. #else
  784. GTMSESSION_ASSERT_DEBUG(0, @"Test blocks disabled");
  785. return nil;
  786. #endif // GTM_DISABLE_FETCHER_TEST_BLOCK
  787. }
  788. #pragma mark Synchronous Wait for Unit Testing
  789. - (BOOL)waitForCompletionOfAllFetchersWithTimeout:(NSTimeInterval)timeoutInSeconds {
  790. NSDate *giveUpDate = [NSDate dateWithTimeIntervalSinceNow:timeoutInSeconds];
  791. _stoppedFetchersToWaitFor = [NSMutableArray array];
  792. BOOL shouldSpinRunLoop = [NSThread isMainThread];
  793. const NSTimeInterval kSpinInterval = 0.001;
  794. BOOL didTimeOut = NO;
  795. while (([self numberOfFetchers] > 0 || _stoppedFetchersToWaitFor.count > 0)) {
  796. didTimeOut = [giveUpDate timeIntervalSinceNow] < 0;
  797. if (didTimeOut) break;
  798. GTMSessionFetcher *stoppedFetcher = _stoppedFetchersToWaitFor.firstObject;
  799. if (stoppedFetcher) {
  800. [_stoppedFetchersToWaitFor removeObject:stoppedFetcher];
  801. [stoppedFetcher waitForCompletionWithTimeout:10.0 * kSpinInterval];
  802. }
  803. if (shouldSpinRunLoop) {
  804. NSDate *stopDate = [NSDate dateWithTimeIntervalSinceNow:kSpinInterval];
  805. [[NSRunLoop currentRunLoop] runUntilDate:stopDate];
  806. } else {
  807. [NSThread sleepForTimeInterval:kSpinInterval];
  808. }
  809. }
  810. _stoppedFetchersToWaitFor = nil;
  811. return !didTimeOut;
  812. }
  813. @end
  814. @implementation GTMSessionFetcherSessionDelegateDispatcher {
  815. __weak GTMSessionFetcherService *_parentService;
  816. NSURLSession *_session;
  817. // The task map maps NSURLSessionTasks to GTMSessionFetchers
  818. NSMutableDictionary *_taskToFetcherMap;
  819. // The discard timer will invalidate sessions after the session's last task completes.
  820. NSTimer *_discardTimer;
  821. NSTimeInterval _discardInterval;
  822. }
  823. @synthesize discardInterval = _discardInterval, session = _session;
  824. - (instancetype)init {
  825. [self doesNotRecognizeSelector:_cmd];
  826. return nil;
  827. }
  828. - (instancetype)initWithParentService:(GTMSessionFetcherService *)parentService
  829. sessionDiscardInterval:(NSTimeInterval)discardInterval {
  830. self = [super init];
  831. if (self) {
  832. _discardInterval = discardInterval;
  833. _parentService = parentService;
  834. }
  835. return self;
  836. }
  837. - (NSString *)description {
  838. return
  839. [NSString stringWithFormat:@"%@ %p %@ %@", [self class], self, _session ?: @"<no session>",
  840. _taskToFetcherMap.count > 0 ? _taskToFetcherMap : @"<no tasks>"];
  841. }
  842. - (NSTimer *)discardTimer {
  843. GTMSessionCheckNotSynchronized(self);
  844. @synchronized(self) {
  845. return _discardTimer;
  846. }
  847. }
  848. // This method should be called inside of a @synchronized(self) block.
  849. - (void)startDiscardTimer {
  850. GTMSessionCheckSynchronized(self);
  851. [_discardTimer invalidate];
  852. _discardTimer = nil;
  853. if (_discardInterval > 0) {
  854. _discardTimer = [NSTimer timerWithTimeInterval:_discardInterval
  855. target:self
  856. selector:@selector(discardTimerFired:)
  857. userInfo:nil
  858. repeats:NO];
  859. [_discardTimer setTolerance:(_discardInterval / 10)];
  860. [[NSRunLoop mainRunLoop] addTimer:_discardTimer forMode:NSRunLoopCommonModes];
  861. }
  862. }
  863. // This method should be called inside of a @synchronized(self) block.
  864. - (void)destroyDiscardTimer {
  865. GTMSessionCheckSynchronized(self);
  866. [_discardTimer invalidate];
  867. _discardTimer = nil;
  868. }
  869. - (void)discardTimerFired:(NSTimer *)timer {
  870. GTMSessionFetcherService *service;
  871. @synchronized(self) {
  872. GTMSessionMonitorSynchronized(self);
  873. NSUInteger numberOfTasks = _taskToFetcherMap.count;
  874. if (numberOfTasks == 0) {
  875. service = _parentService;
  876. }
  877. }
  878. // Inform the service that the discard timer has fired, and should check whether the
  879. // service can abandon us. -resetSession cannot be called directly, as there is a
  880. // race condition that must be guarded against with the NSURLSession being returned
  881. // from sessionForFetcherCreation outside other locks. The service can take steps
  882. // to prevent resetting the session if that has occurred.
  883. //
  884. // The service must be called from outside the @synchronized block.
  885. [service resetSessionForDispatcherDiscardTimer:timer];
  886. }
  887. - (void)abandon {
  888. @synchronized(self) {
  889. GTMSessionMonitorSynchronized(self);
  890. [self destroySessionAndTimer];
  891. }
  892. }
  893. - (void)startSessionUsage {
  894. @synchronized(self) {
  895. GTMSessionMonitorSynchronized(self);
  896. [self destroyDiscardTimer];
  897. }
  898. }
  899. // This method should be called inside of a @synchronized(self) block.
  900. - (void)destroySessionAndTimer {
  901. GTMSessionCheckSynchronized(self);
  902. [self destroyDiscardTimer];
  903. // Break any retain cycle from the session holding the delegate.
  904. [_session finishTasksAndInvalidate];
  905. // Immediately clear the session so no new task may be issued with it.
  906. //
  907. // The _taskToFetcherMap needs to stay valid until the outstanding tasks finish.
  908. _session = nil;
  909. }
  910. - (void)setFetcher:(GTMSessionFetcher *)fetcher forTask:(NSURLSessionTask *)task {
  911. GTMSESSION_ASSERT_DEBUG(fetcher != nil, @"missing fetcher");
  912. @synchronized(self) {
  913. GTMSessionMonitorSynchronized(self);
  914. if (_taskToFetcherMap == nil) {
  915. _taskToFetcherMap = [[NSMutableDictionary alloc] init];
  916. }
  917. if (fetcher) {
  918. [_taskToFetcherMap setObject:fetcher forKey:task];
  919. [self destroyDiscardTimer];
  920. }
  921. }
  922. }
  923. - (void)removeFetcher:(GTMSessionFetcher *)fetcher {
  924. @synchronized(self) {
  925. GTMSessionMonitorSynchronized(self);
  926. // Typically, a fetcher should be removed when its task invokes
  927. // URLSession:task:didCompleteWithError:.
  928. //
  929. // When fetching with a testBlock, though, the task completed delegate
  930. // method may not be invoked, requiring cleanup here.
  931. NSArray *tasks = [_taskToFetcherMap allKeysForObject:fetcher];
  932. GTMSESSION_ASSERT_DEBUG(tasks.count <= 1, @"fetcher task not unmapped: %@", tasks);
  933. [_taskToFetcherMap removeObjectsForKeys:tasks];
  934. if (_taskToFetcherMap.count == 0) {
  935. [self startDiscardTimer];
  936. }
  937. }
  938. }
  939. // This helper method provides synchronized access to the task map for the delegate
  940. // methods below.
  941. - (id)fetcherForTask:(NSURLSessionTask *)task {
  942. @synchronized(self) {
  943. GTMSessionMonitorSynchronized(self);
  944. return [_taskToFetcherMap objectForKey:task];
  945. }
  946. }
  947. - (void)removeTaskFromMap:(NSURLSessionTask *)task {
  948. @synchronized(self) {
  949. GTMSessionMonitorSynchronized(self);
  950. [_taskToFetcherMap removeObjectForKey:task];
  951. }
  952. }
  953. - (void)setSession:(NSURLSession *)session {
  954. @synchronized(self) {
  955. GTMSessionMonitorSynchronized(self);
  956. _session = session;
  957. }
  958. }
  959. - (NSURLSession *)session {
  960. @synchronized(self) {
  961. GTMSessionMonitorSynchronized(self);
  962. return _session;
  963. }
  964. }
  965. - (NSTimeInterval)discardInterval {
  966. @synchronized(self) {
  967. GTMSessionMonitorSynchronized(self);
  968. return _discardInterval;
  969. }
  970. }
  971. - (void)setDiscardInterval:(NSTimeInterval)interval {
  972. @synchronized(self) {
  973. GTMSessionMonitorSynchronized(self);
  974. _discardInterval = interval;
  975. }
  976. }
  977. // NSURLSessionDelegate protocol methods.
  978. // - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session;
  979. //
  980. // TODO(seh): How do we route this to an appropriate fetcher?
  981. - (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error {
  982. GTMSESSION_LOG_DEBUG_VERBOSE(@"%@ %p URLSession:%@ didBecomeInvalidWithError:%@", [self class],
  983. self, session, error);
  984. NSDictionary *localTaskToFetcherMap;
  985. @synchronized(self) {
  986. GTMSessionMonitorSynchronized(self);
  987. _session = nil;
  988. localTaskToFetcherMap = [_taskToFetcherMap copy];
  989. }
  990. // Any "suspended" tasks may not have received callbacks from NSURLSession when the session
  991. // completes; we'll call them now.
  992. [localTaskToFetcherMap enumerateKeysAndObjectsUsingBlock:^(
  993. NSURLSessionTask *task, GTMSessionFetcher *fetcher, BOOL *stop) {
  994. if (fetcher.session == session) {
  995. // Our delegate method URLSession:task:didCompleteWithError: will rely on
  996. // _taskToFetcherMap so that should still contain this fetcher.
  997. NSError *canceledError = [NSError errorWithDomain:NSURLErrorDomain
  998. code:NSURLErrorCancelled
  999. userInfo:nil];
  1000. [self URLSession:session task:task didCompleteWithError:canceledError];
  1001. } else {
  1002. GTMSESSION_ASSERT_DEBUG(0, @"Unexpected session in fetcher: %@ has %@ (expected %@)", fetcher,
  1003. fetcher.session, session);
  1004. }
  1005. }];
  1006. // Our tests rely on this notification to know the session discard timer fired.
  1007. NSDictionary *userInfo = @{kGTMSessionFetcherServiceSessionKey : session};
  1008. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  1009. [nc postNotificationName:kGTMSessionFetcherServiceSessionBecameInvalidNotification
  1010. object:_parentService
  1011. userInfo:userInfo];
  1012. }
  1013. #pragma mark - NSURLSessionTaskDelegate
  1014. // NSURLSessionTaskDelegate protocol methods.
  1015. //
  1016. // We won't test here if the fetcher responds to these since we only want this
  1017. // class to implement the same delegate methods the fetcher does (so NSURLSession's
  1018. // tests for respondsToSelector: will have the same result whether the session
  1019. // delegate is the fetcher or this dispatcher.)
  1020. - (void)URLSession:(NSURLSession *)session
  1021. task:(NSURLSessionTask *)task
  1022. willPerformHTTPRedirection:(NSHTTPURLResponse *)response
  1023. newRequest:(NSURLRequest *)request
  1024. completionHandler:(void (^)(NSURLRequest *))completionHandler {
  1025. id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task];
  1026. [fetcher URLSession:session
  1027. task:task
  1028. willPerformHTTPRedirection:response
  1029. newRequest:request
  1030. completionHandler:completionHandler];
  1031. }
  1032. - (void)URLSession:(NSURLSession *)session
  1033. task:(NSURLSessionTask *)task
  1034. didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
  1035. completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))handler {
  1036. id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task];
  1037. [fetcher URLSession:session task:task didReceiveChallenge:challenge completionHandler:handler];
  1038. }
  1039. - (void)URLSession:(NSURLSession *)session
  1040. task:(NSURLSessionTask *)task
  1041. needNewBodyStream:(void (^)(NSInputStream *bodyStream))handler {
  1042. id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task];
  1043. [fetcher URLSession:session task:task needNewBodyStream:handler];
  1044. }
  1045. - (void)URLSession:(NSURLSession *)session
  1046. task:(NSURLSessionTask *)task
  1047. didSendBodyData:(int64_t)bytesSent
  1048. totalBytesSent:(int64_t)totalBytesSent
  1049. totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend {
  1050. id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task];
  1051. [fetcher URLSession:session
  1052. task:task
  1053. didSendBodyData:bytesSent
  1054. totalBytesSent:totalBytesSent
  1055. totalBytesExpectedToSend:totalBytesExpectedToSend];
  1056. }
  1057. - (void)URLSession:(NSURLSession *)session
  1058. task:(NSURLSessionTask *)task
  1059. didCompleteWithError:(NSError *)error {
  1060. id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task];
  1061. // This is the usual way tasks are removed from the task map.
  1062. [self removeTaskFromMap:task];
  1063. [fetcher URLSession:session task:task didCompleteWithError:error];
  1064. }
  1065. - (void)URLSession:(NSURLSession *)session
  1066. task:(NSURLSessionTask *)task
  1067. didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics
  1068. API_AVAILABLE(ios(10.0), macosx(10.12), tvos(10.0), watchos(6.0)) {
  1069. id<NSURLSessionTaskDelegate> fetcher = [self fetcherForTask:task];
  1070. [fetcher URLSession:session task:task didFinishCollectingMetrics:metrics];
  1071. }
  1072. // NSURLSessionDataDelegate protocol methods.
  1073. - (void)URLSession:(NSURLSession *)session
  1074. dataTask:(NSURLSessionDataTask *)dataTask
  1075. didReceiveResponse:(NSURLResponse *)response
  1076. completionHandler:(void (^)(NSURLSessionResponseDisposition))handler {
  1077. id<NSURLSessionDataDelegate> fetcher = [self fetcherForTask:dataTask];
  1078. [fetcher URLSession:session
  1079. dataTask:dataTask
  1080. didReceiveResponse:response
  1081. completionHandler:handler];
  1082. }
  1083. - (void)URLSession:(NSURLSession *)session
  1084. dataTask:(NSURLSessionDataTask *)dataTask
  1085. didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask {
  1086. id<NSURLSessionDataDelegate> fetcher = [self fetcherForTask:dataTask];
  1087. GTMSESSION_ASSERT_DEBUG(fetcher != nil, @"Missing fetcher for %@", dataTask);
  1088. [self removeTaskFromMap:dataTask];
  1089. if (fetcher) {
  1090. GTMSESSION_ASSERT_DEBUG([fetcher isKindOfClass:[GTMSessionFetcher class]],
  1091. @"Expecting GTMSessionFetcher");
  1092. [self setFetcher:(GTMSessionFetcher *)fetcher forTask:downloadTask];
  1093. }
  1094. [fetcher URLSession:session dataTask:dataTask didBecomeDownloadTask:downloadTask];
  1095. }
  1096. - (void)URLSession:(NSURLSession *)session
  1097. dataTask:(NSURLSessionDataTask *)dataTask
  1098. didReceiveData:(NSData *)data {
  1099. id<NSURLSessionDataDelegate> fetcher = [self fetcherForTask:dataTask];
  1100. [fetcher URLSession:session dataTask:dataTask didReceiveData:data];
  1101. }
  1102. - (void)URLSession:(NSURLSession *)session
  1103. dataTask:(NSURLSessionDataTask *)dataTask
  1104. willCacheResponse:(NSCachedURLResponse *)proposedResponse
  1105. completionHandler:(void (^)(NSCachedURLResponse *))handler {
  1106. id<NSURLSessionDataDelegate> fetcher = [self fetcherForTask:dataTask];
  1107. [fetcher URLSession:session
  1108. dataTask:dataTask
  1109. willCacheResponse:proposedResponse
  1110. completionHandler:handler];
  1111. }
  1112. // NSURLSessionDownloadDelegate protocol methods.
  1113. - (void)URLSession:(NSURLSession *)session
  1114. downloadTask:(NSURLSessionDownloadTask *)downloadTask
  1115. didFinishDownloadingToURL:(NSURL *)location {
  1116. id<NSURLSessionDownloadDelegate> fetcher = [self fetcherForTask:downloadTask];
  1117. [fetcher URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location];
  1118. }
  1119. - (void)URLSession:(NSURLSession *)session
  1120. downloadTask:(NSURLSessionDownloadTask *)downloadTask
  1121. didWriteData:(int64_t)bytesWritten
  1122. totalBytesWritten:(int64_t)totalWritten
  1123. totalBytesExpectedToWrite:(int64_t)totalExpected {
  1124. id<NSURLSessionDownloadDelegate> fetcher = [self fetcherForTask:downloadTask];
  1125. [fetcher URLSession:session
  1126. downloadTask:downloadTask
  1127. didWriteData:bytesWritten
  1128. totalBytesWritten:totalWritten
  1129. totalBytesExpectedToWrite:totalExpected];
  1130. }
  1131. - (void)URLSession:(NSURLSession *)session
  1132. downloadTask:(NSURLSessionDownloadTask *)downloadTask
  1133. didResumeAtOffset:(int64_t)fileOffset
  1134. expectedTotalBytes:(int64_t)expectedTotalBytes {
  1135. id<NSURLSessionDownloadDelegate> fetcher = [self fetcherForTask:downloadTask];
  1136. [fetcher URLSession:session
  1137. downloadTask:downloadTask
  1138. didResumeAtOffset:fileOffset
  1139. expectedTotalBytes:expectedTotalBytes];
  1140. }
  1141. @end