Açıklama Yok

GTMSessionFetcherService.m 45KB

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