Nessuna descrizione

GTMSessionFetcherService.m 46KB

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