Source/TNLURLSessionManager.m (1,515 lines of code) (raw):
//
// TNLURLSessionManager.m
// TwitterNetworkLayer
//
// Created on 10/23/15.
// Copyright © 2020 Twitter. All rights reserved.
//
#include <mach/mach_time.h>
#include <stdatomic.h>
#import "NSCachedURLResponse+TNLAdditions.h"
#import "NSDictionary+TNLAdditions.h"
#import "NSURLAuthenticationChallenge+TNLAdditions.h"
#import "NSURLResponse+TNLAdditions.h"
#import "NSURLSessionConfiguration+TNLAdditions.h"
#import "NSURLSessionTaskMetrics+TNLAdditions.h"
#import "TNL_Project.h"
#import "TNLAuthenticationChallengeHandler.h"
#import "TNLBackgroundURLSessionTaskOperationManager.h"
#import "TNLBackoff.h"
#import "TNLGlobalConfiguration_Project.h"
#import "TNLLRUCache.h"
#import "TNLNetwork.h"
#import "TNLRequestOperation_Project.h"
#import "TNLRequestOperationQueue_Project.h"
#import "TNLTimeoutOperation.h"
#import "TNLTiming.h"
#import "TNLURLSessionManager.h"
#import "TNLURLSessionTaskOperation.h"
NS_ASSUME_NONNULL_BEGIN
@class TNLURLSessionContextLRUCacheDelegate;
#pragma mark - Constants
static const NSUInteger kMaxURLSessionContextCount = 12;
static NSString * const kInAppURLSessionContextIdentifier = @"tnl.op.queue";
static NSString * const kManagerVersionKey = @"smv";
#pragma mark - Static Functions
static NSString *_GenerateReuseIdentifier(NSString * __nullable operationQueueId, NSString *URLSessionConfigurationIdentificationString, TNLRequestExecutionMode executionmode);
static void _ConfigureSessionConfigurationWithRequestConfiguration(NSURLSessionConfiguration * __nullable sessionConfig, TNLRequestConfiguration * requestConfig);
static NSString * __nullable _BackoffKeyFromURL(const TNLGlobalConfigurationBackoffMode mode, NSURL *URL, NSString * __nullable host);
static void TNLMutableParametersStripNonURLSessionProperties(TNLMutableParameterCollection *params);
static void TNLMutableParametersStripNonBackgroundURLSessionProperties(TNLMutableParameterCollection *params);
static void TNLMutableParametersStripOverriddenURLSessionProperties(TNLMutableParameterCollection *params);
typedef BOOL (^_FilterBlock)(id obj);
static NSArray *_FilterArray(NSArray *source, _FilterBlock filterBlock);
#pragma mark - Global Session Management
static void _PrepareSessionManagement(void);
static dispatch_queue_t sSynchronizeQueue;
static NSOperationQueue *sSynchronizeOperationQueue;
static NSOperationQueue *sURLSessionTaskOperationQueue;
static TNLURLSessionContextLRUCacheDelegate *sSessionContextsDelegate;
static TNLLRUCache *sAppSessionContexts;
static TNLLRUCache *sBackgroundSessionContexts;
static NSMutableSet<TNLURLSessionTaskOperation *> *sActiveURLSessionTaskOperations;
static NSMutableDictionary<NSString *, dispatch_block_t> *sBackgroundSessionCompletionHandlerDictionary;
static NSMutableDictionary<NSString *, NSHashTable<NSOperation *> *> *sOutstandingBackoffOperations = nil;
static NSMutableDictionary<NSString *, NSHashTable<NSOperation *> *> *sOutstandingSerializeOperations = nil;
static NSTimeInterval sSerialDelayDuration = 0.0;
static TNLGlobalConfigurationBackoffMode sBackoffMode = TNLGlobalConfigurationBackoffModeDisabled;
static id<TNLBackoffBehaviorProvider> sBackoffBehaviorProvider = nil;
#pragma mark - Session Context
TNL_OBJC_FINAL TNL_OBJC_DIRECT_MEMBERS
@interface TNLURLSessionContext : NSObject
@property (nonatomic, readonly) NSURLSession *URLSession;
@property (nonatomic, readonly, copy) NSString *reuseId;
@property (nonatomic, readonly) TNLRequestExecutionMode executionMode;
@property (nonatomic, readonly) NSArray<TNLURLSessionTaskOperation *> *URLSessionTaskOperations;
@property (nonatomic, readonly) uint64_t lastOperationRemovedMachTime;
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;
- (NSUInteger)operationCount;
- (void)addOperation:(TNLURLSessionTaskOperation *)op;
- (void)removeOperation:(TNLURLSessionTaskOperation *)op;
- (nullable TNLURLSessionTaskOperation *)operationForTask:(NSURLSessionTask *)task;
- (void)changeOperation:(TNLURLSessionTaskOperation *)op
fromTask:(NSURLSessionTask *)oldTask
toTask:(NSURLSessionTask *)newTask;
@end
TNL_OBJC_DIRECT_MEMBERS
@interface TNLURLSessionContext () <TNLLRUEntry>
- (instancetype)initWithURLSession:(NSURLSession *)URLSession
reuseId:(NSString *)reuseId
executionMode:(TNLRequestExecutionMode)mode NS_DESIGNATED_INITIALIZER;
@end
TNL_OBJC_DIRECT_MEMBERS
@interface TNLURLSessionContextLRUCacheDelegate : NSObject <TNLLRUCacheDelegate>
@end
#pragma mark - Session Manager Interfaces
@interface TNLURLSessionManagerV1 : NSObject <TNLURLSessionManager>
+ (instancetype)internalSharedInstance;
@end
TNL_OBJC_DIRECT_MEMBERS
@interface TNLURLSessionManagerV1 (Delegate) <NSURLSessionDataDelegate, NSURLSessionDownloadDelegate>
@end
TNL_OBJC_DIRECT_MEMBERS
@interface TNLURLSessionManagerV1 (Synchronize)
// TODO: see if some of these don't actually need a `self` argument
- (void)_synchronize_findURLSessionTaskOperationForRequestOperationQueue:(TNLRequestOperationQueue *)requestOperationQueue
requestOperation:(TNLRequestOperation *)requestOperation
completion:(TNLRequestOperationQueueFindTaskOperationCompleteBlock)complete;
- (NSURLSession *)_synchronize_associateTaskOperation:(TNLURLSessionTaskOperation *)taskOperation
withQueue:(TNLRequestOperationQueue *)requestOperationQueue
supportsTaskMetrics:(BOOL)supportsTaskMetrics;
- (void)_synchronize_dissassociateTaskOperation:(TNLURLSessionTaskOperation *)op;
- (nullable TNLURLSessionContext *)_synchronize_sessionContextWithQueueId:(nullable NSString *)operationQueueId
requestConfiguration:(TNLRequestConfiguration *)requestConfiguration
executionMode:(TNLRequestExecutionMode)executionMode
createIfNeeded:(BOOL)createIfNeeded;
- (nullable TNLURLSessionContext *)_synchronize_sessionContextFromURLSession:(NSURLSession *)session;
- (nullable TNLURLSessionContext *)_synchronize_sessionContextWithConfigurationIdentifier:(NSString *)identifier;
- (void)_synchronize_removeSessionContext:(TNLURLSessionContext *)context;
- (void)_synchronize_storeSessionContext:(TNLURLSessionContext *)context;
- (void)_synchronize_applyBackoffDependenciesToOperation:(NSOperation *)op
matchingURL:(NSURL *)URL
host:(nullable NSString *)host
isLongPoll:(BOOL)isLongPoll;
- (void)_synchronize_backoffSignalEncounteredForURL:(NSURL *)URL
host:(nullable NSString *)host
headers:(nullable NSDictionary<NSString *, NSString *> *)headers;
- (void)_synchronize_pruneSessionsToLimit;
- (void)_synchronize_pruneUnusedSessions;
- (void)_synchronize_pruneSessionWithConfig:(TNLRequestConfiguration *)config
operationQueueId:(nullable NSString *)operationQueueId;
static void _executeOnSynchronizeGCDQueueFromSynchronizeOperationQueue(dispatch_block_t block);
@end
/**
Subclass TNLURLSessionManagerV1
Implement URLSession:task:didFinishCollectingMetrics:
Has bugs on older OS versions
*/
@interface TNLURLSessionManagerV2 : TNLURLSessionManagerV1
@end
#pragma mark - Implementation
@implementation TNLURLSessionManager
+ (id<TNLURLSessionManager>)sharedInstance
{
if (![NSURLSessionConfiguration tnl_URLSessionCanUseTaskTransactionMetrics]) {
return [TNLURLSessionManagerV1 internalSharedInstance];
} else {
return [TNLURLSessionManagerV2 internalSharedInstance];
}
}
@end
@implementation TNLURLSessionManagerV1
+ (NSInteger)version
{
return 1;
}
+ (instancetype)internalSharedInstance
{
static TNLURLSessionManagerV1 *sInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sInstance = [[TNLURLSessionManagerV1 alloc] initInternal];
});
return sInstance;
}
- (instancetype)initInternal
{
if (self = [super init]) {
_PrepareSessionManagement();
}
return self;
}
- (void)cancelAllForQueue:(TNLRequestOperationQueue *)queue
source:(id<TNLRequestOperationCancelSource>)source
underlyingError:(nullable NSError *)optionalUnderlyingError
{
tnl_dispatch_async_autoreleasing(sSynchronizeQueue, ^{
NSSet *ops = [sActiveURLSessionTaskOperations copy];
for (TNLURLSessionTaskOperation *op in ops) {
if (op.requestOperationQueue == queue) {
[op cancelWithSource:source underlyingError:optionalUnderlyingError];
}
}
});
}
- (void)findURLSessionTaskOperationForRequestOperationQueue:(TNLRequestOperationQueue *)queue
requestOperation:(TNLRequestOperation *)op
complete:(TNLRequestOperationQueueFindTaskOperationCompleteBlock)complete
{
TNLAssert(op.URLSessionTaskOperation == nil);
tnl_dispatch_async_autoreleasing(sSynchronizeQueue, ^{
[self _synchronize_findURLSessionTaskOperationForRequestOperationQueue:queue
requestOperation:op
completion:complete];
});
}
- (void)getAllURLSessions:(TNLURLSessionManagerGetAllSessionsCallback)callback
{
tnl_dispatch_async_autoreleasing(sSynchronizeQueue, ^{
NSArray<TNLURLSessionContext *> *foregroundContexts = sAppSessionContexts.allEntries;
NSArray<TNLURLSessionContext *> *backgroundContexts = sBackgroundSessionContexts.allEntries;
tnl_dispatch_async_autoreleasing(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSMutableArray<NSURLSession *> *foregroundSessions = [[NSMutableArray alloc] initWithCapacity:foregroundContexts.count];
NSMutableArray<NSURLSession *> *backgroundSessions = [[NSMutableArray alloc] initWithCapacity:backgroundContexts.count];
for (TNLURLSessionContext *foregroundContext in foregroundContexts) {
[foregroundSessions addObject:foregroundContext.URLSession];
}
for (TNLURLSessionContext *backgroundContext in backgroundContexts) {
[backgroundSessions addObject:backgroundContext.URLSession];
}
callback(foregroundSessions, backgroundSessions);
});
});
}
- (BOOL)handleBackgroundURLSessionEvents:(NSString *)identifier
completionHandler:(dispatch_block_t)completionHandler
{
#if !TARGET_OS_IPHONE // == !(IOS + WATCH + TV)
return NO;
#else
if (!TNLURLSessionIdentifierIsTaggedForTNL(identifier)) {
return NO;
}
tnl_dispatch_async_autoreleasing(sSynchronizeQueue, ^{
// TODO:[nobrien] - this JUST handles the background task completing event.
// Any other events (specifically, auth challenges) are not currently handled.
TNLURLSessionContext *context = [self _synchronize_sessionContextWithConfigurationIdentifier:identifier];
TNLBackgroundURLSessionTaskOperationManager *bgManager = nil;
if (!context) {
bgManager = [[TNLBackgroundURLSessionTaskOperationManager alloc] init];
[bgManager handleBackgroundURLSessionEvents:identifier];
}
sBackgroundSessionCompletionHandlerDictionary[identifier] = [^{
(void)bgManager;
completionHandler();
} copy];
});
return YES;
#endif // TARGET_OS_IPHONE
}
- (void)URLSessionDidCompleteBackgroundEvents:(NSURLSession *)session
{
tnl_dispatch_async_autoreleasing(sSynchronizeQueue, ^{
NSString *identifier = session.configuration.identifier;
TNLAssert(identifier != nil);
if (identifier) {
dispatch_block_t handler = sBackgroundSessionCompletionHandlerDictionary[identifier];
[sBackgroundSessionCompletionHandlerDictionary removeObjectForKey:identifier];
TNLAssert(handler != NULL);
if (handler) {
tnl_dispatch_async_autoreleasing(dispatch_get_main_queue(), handler);
}
}
});
}
- (void)URLSessionDidCompleteBackgroundTask:(NSUInteger)taskIdentifier
sessionConfigIdentifier:(NSString *)sessionConfigIdentifier
sharedContainerIdentifier:(nullable NSString *)sharedContainerIdentifier
request:(NSURLRequest *)request
response:(TNLResponse *)response
{
tnl_dispatch_async_autoreleasing(sSynchronizeQueue, ^{
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
if (sessionConfigIdentifier) {
userInfo[TNLBackgroundRequestURLSessionConfigurationIdentifierKey] = sessionConfigIdentifier;
}
if (response) {
userInfo[TNLBackgroundRequestResponseKey] = response;
}
userInfo[TNLBackgroundRequestURLSessionTaskIdentifierKey] = @(taskIdentifier);
if (request) {
userInfo[TNLBackgroundRequestURLRequestKey] = request;
}
if (sharedContainerIdentifier) {
userInfo[TNLBackgroundRequestURLSessionSharedContainerIdentifierKey] = sharedContainerIdentifier;
}
tnl_dispatch_async_autoreleasing(dispatch_get_main_queue(), ^{
// notify off the global operation queue's queue
[[NSNotificationCenter defaultCenter] postNotificationName:TNLBackgroundRequestOperationDidCompleteNotification
object:nil
userInfo:userInfo];
});
});
}
- (void)syncAddURLSessionTaskOperation:(TNLURLSessionTaskOperation *)op
{
dispatch_sync(sSynchronizeQueue, ^{
const NSTimeInterval attemptTimeout = op.requestConfiguration.attemptTimeout;
const BOOL isLongPollRequest = (attemptTimeout < 1.0) || (attemptTimeout >= NSTimeIntervalSince1970);
NSURL *URL = op.hydratedURLRequest.URL;
NSString *host = nil;
if ([TNLGlobalConfiguration sharedInstance].shouldBackoffUseOriginalRequestHost) {
host = op.originalURLRequest.URL.host;
}
[self _synchronize_applyBackoffDependenciesToOperation:op
matchingURL:URL
host:host
isLongPoll:isLongPollRequest];
[sURLSessionTaskOperationQueue addOperation:op];
});
}
- (void)applyBackoffDependenciesToOperation:(NSOperation *)op
withURL:(NSURL *)URL
host:(nullable NSString *)host
isLongPollRequest:(BOOL)isLongPoll
{
dispatch_sync(sSynchronizeQueue, ^{
[self _synchronize_applyBackoffDependenciesToOperation:op
matchingURL:URL
host:host
isLongPoll:isLongPoll];
});
}
- (void)backoffSignalEncounteredForURL:(NSURL *)URL
host:(nullable NSString *)host
responseHTTPHeaders:(nullable NSDictionary<NSString *, NSString *> *)headers
{
tnl_dispatch_async_autoreleasing(sSynchronizeQueue, ^{
[self _synchronize_backoffSignalEncounteredForURL:URL host:host headers:headers];
});
}
- (void)setBackoffMode:(TNLGlobalConfigurationBackoffMode)mode
{
tnl_dispatch_async_autoreleasing(sSynchronizeQueue, ^{
if (sBackoffMode != mode) {
sBackoffMode = mode;
// reset our backoffs
[sOutstandingBackoffOperations removeAllObjects];
[sOutstandingSerializeOperations removeAllObjects];
}
});
}
- (TNLGlobalConfigurationBackoffMode)backoffMode
{
__block TNLGlobalConfigurationBackoffMode mode;
dispatch_sync(sSynchronizeQueue, ^{
mode = sBackoffMode;
});
return mode;
}
- (void)setBackoffBehaviorProvider:(nullable id<TNLBackoffBehaviorProvider>)provider
{
tnl_dispatch_async_autoreleasing(sSynchronizeQueue, ^{
sBackoffBehaviorProvider = provider ?: [[TNLSimpleBackoffBehaviorProvider alloc] init];
// does not affect our existing backoffs
});
}
- (id<TNLBackoffBehaviorProvider>)backoffBehaviorProvider
{
__block id<TNLBackoffBehaviorProvider> provider;
dispatch_sync(sSynchronizeQueue, ^{
provider = sBackoffBehaviorProvider;
});
return provider;
}
- (void)pruneUnusedURLSessions
{
tnl_dispatch_async_autoreleasing(sSynchronizeQueue, ^{
[self _synchronize_pruneUnusedSessions];
});
}
- (void)pruneURLSessionMatchingRequestConfiguration:(TNLRequestConfiguration *)config
operationQueueId:(nullable NSString *)operationQueueId
{
config = [config copy]; // force immutable
tnl_dispatch_async_autoreleasing(sSynchronizeQueue, ^{
[self _synchronize_pruneSessionWithConfig:config operationQueueId:operationQueueId];
});
}
@end
@implementation TNLURLSessionManagerV1 (Synchronize)
static void _executeOnSynchronizeGCDQueueFromSynchronizeOperationQueue(dispatch_block_t block)
{
@autoreleasepool {
if (dispatch_queue_get_label(sSynchronizeQueue) == dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)) {
block();
} else {
dispatch_sync(sSynchronizeQueue, block);
}
}
}
- (void)_synchronize_findURLSessionTaskOperationForRequestOperationQueue:(TNLRequestOperationQueue *)requestOperationQueue
requestOperation:(TNLRequestOperation *)requestOperation
completion:(TNLRequestOperationQueueFindTaskOperationCompleteBlock)complete
{
TNLAssert(requestOperation.URLSessionTaskOperation == nil);
TNLURLSessionTaskOperation *taskOperation = nil;
// This NEEDS to be the ONLY place we create a TNLURLSessionTaskOperation.
taskOperation = [[TNLURLSessionTaskOperation alloc] initWithRequestOperation:requestOperation
sessionManager:self];
NSURLSession *session = [self _synchronize_associateTaskOperation:taskOperation
withQueue:requestOperationQueue
supportsTaskMetrics:[self respondsToSelector:@selector(URLSession:task:didFinishCollectingMetrics:)]];
(void)session;
TNLAssert(session != nil);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
// Completion block will be cleared when it is called
taskOperation.completionBlock = ^{
tnl_dispatch_async_autoreleasing(sSynchronizeQueue, ^{
[self _synchronize_dissassociateTaskOperation:taskOperation];
});
};
#pragma clang diagnostic pop
TNLAssert(taskOperation.URLSession != nil);
complete(taskOperation);
}
- (NSURLSession *)_synchronize_associateTaskOperation:(TNLURLSessionTaskOperation *)taskOperation
withQueue:(TNLRequestOperationQueue *)requestOperationQueue
supportsTaskMetrics:(BOOL)supportsTaskMetrics
{
TNLRequestConfiguration *requestConfig = taskOperation.requestConfiguration;
TNLRequestExecutionMode mode = taskOperation.executionMode;
TNLAssert(requestConfig);
TNLURLSessionContext *context = [self _synchronize_sessionContextWithQueueId:requestOperationQueue.identifier
requestConfiguration:requestConfig
executionMode:mode
createIfNeeded:YES];
TNLAssert(context != nil);
TNLAssert(context.URLSession != nil);
[taskOperation setURLSession:context.URLSession supportsTaskMetrics:supportsTaskMetrics];
[context addOperation:taskOperation];
[sActiveURLSessionTaskOperations addObject:taskOperation];
return context.URLSession;
}
- (void)_synchronize_dissassociateTaskOperation:(TNLURLSessionTaskOperation *)op
{
TNLRequestOperationQueue *queue = op.requestOperationQueue;
TNLRequestConfiguration *requestConfig = op.requestConfiguration;
TNLRequestExecutionMode mode = op.executionMode;
TNLAssert(queue);
TNLAssert(requestConfig);
if (requestConfig) {
TNLURLSessionContext *context = [self _synchronize_sessionContextWithQueueId:queue.identifier
requestConfiguration:requestConfig
executionMode:mode
createIfNeeded:NO];
if (context) {
// remove the operation from the context
[context removeOperation:op];
// did the operation fail due to an invalidated NSURLSession?
const BOOL opHadAnInvalidSession = op.error &&
[op.error.domain isEqualToString:TNLErrorDomain] &&
op.error.code == TNLErrorCodeRequestOperationURLSessionInvalidated;
if (opHadAnInvalidSession) {
// was the invalid session the current context object's session?
if (op.URLSession == context.URLSession) {
// context is not longer viable, remove it from our store
[self _synchronize_removeSessionContext:context];
TNLLogError(@"Encountered invalid NSURLSession, removing from TNL store of sessions");
}
}
}
}
// prune
[self _synchronize_pruneSessionsToLimit];
const TNLGlobalConfigurationURLSessionPruneOptions pruneOptions = [TNLGlobalConfiguration sharedInstance].URLSessionPruneOptions;
if (TNL_BITMASK_INTERSECTS_FLAGS(pruneOptions, TNLGlobalConfigurationURLSessionPruneOptionAfterEveryTask)) {
[self _synchronize_pruneUnusedSessions];
}
[sActiveURLSessionTaskOperations removeObject:op];
}
- (nullable TNLURLSessionContext *)_synchronize_sessionContextWithQueueId:(nullable NSString *)operationQueueId
requestConfiguration:(TNLRequestConfiguration *)requestConfiguration
executionMode:(TNLRequestExecutionMode)executionMode
createIfNeeded:(BOOL)createIfNeeded
{
TNLAssert(requestConfiguration);
NSURLCache *canonicalCache = nil;
NSURLCredentialStorage *canonicalCredentialStorage = nil;
NSHTTPCookieStorage *canonicalCookieStorage = nil;
// use demuxers for increased NSURLSession reuse
canonicalCache = TNLGetURLCacheDemuxProxy();
canonicalCredentialStorage = TNLGetURLCredentialStorageDemuxProxy();
canonicalCookieStorage = TNLGetHTTPCookieStorageDemuxProxy();
TNLMutableParameterCollection *params = TNLMutableParametersFromRequestConfiguration(requestConfiguration,
canonicalCache,
canonicalCredentialStorage,
canonicalCookieStorage);
if (executionMode != TNLRequestExecutionModeBackground) {
// Let's aim for higher reusability in our foreground sessions and strip out any information
// that won't be relevant to identifying the session we wish to access
TNLMutableParametersStripNonURLSessionProperties(params);
if (executionMode != TNLRequestExecutionModeBackground) {
TNLMutableParametersStripNonBackgroundURLSessionProperties(params);
}
TNLMutableParametersStripOverriddenURLSessionProperties(params);
// We DO however need to keep track of our manager version
params[kManagerVersionKey] = @([[self class] version]);
}
NSString *identificationString = [params stableURLEncodedStringValue];
NSString *reuseId = _GenerateReuseIdentifier(operationQueueId, identificationString, executionMode);
TNLURLSessionContext *context = [self _synchronize_sessionContextWithConfigurationIdentifier:reuseId];
if (!context && createIfNeeded) {
NSURLSessionConfiguration *canonicalConfiguration;
canonicalConfiguration = [requestConfiguration generateCanonicalSessionConfigurationWithExecutionMode:executionMode
identifier:reuseId
canonicalURLCache:canonicalCache
canonicalURLCredentialStorage:canonicalCredentialStorage
canonicalCookieStorage:canonicalCookieStorage];
#if DEBUG
if (TNLRequestExecutionModeBackground == executionMode) {
TNLAssert([reuseId isEqualToString:canonicalConfiguration.identifier]);
}
#endif
NSURLSession *session = [NSURLSession sessionWithConfiguration:canonicalConfiguration
delegate:self
delegateQueue:sSynchronizeOperationQueue];
static volatile atomic_int_fast64_t __attribute__((aligned(8))) sSessionId = ATOMIC_VAR_INIT(0);
const int64_t sessionId = atomic_fetch_add(&sSessionId, 1);
context = [[TNLURLSessionContext alloc] initWithURLSession:session
reuseId:reuseId
executionMode:executionMode];
NSString *sessionDescription = [NSString stringWithFormat:@"%@#%lli", reuseId, sessionId];
session.sessionDescription = sessionDescription;
TNLAssert([context.URLSession.sessionDescription isEqualToString:sessionDescription]);
[self _synchronize_storeSessionContext:context];
}
return context;
}
static NSString *_stripSessionIdentifierFromSessionDescription(NSString *sessionDescription)
{
const NSRange range = [sessionDescription rangeOfString:@"#" options:NSBackwardsSearch];
if (range.location == NSNotFound) {
return sessionDescription;
}
return [sessionDescription substringToIndex:range.location];
}
- (nullable TNLURLSessionContext *)_synchronize_sessionContextFromURLSession:(NSURLSession *)session
{
if (!self) {
return nil;
}
NSString *reuseId = _stripSessionIdentifierFromSessionDescription(session.sessionDescription);
return [self _synchronize_sessionContextWithConfigurationIdentifier:reuseId];
}
- (nullable TNLURLSessionContext *)_synchronize_sessionContextWithConfigurationIdentifier:(NSString *)identifier
{
return [sAppSessionContexts entryWithIdentifier:identifier] ?: [sBackgroundSessionContexts entryWithIdentifier:identifier];
}
- (void)_synchronize_storeSessionContext:(TNLURLSessionContext *)context
{
if (context.executionMode == TNLRequestExecutionModeBackground) {
[sBackgroundSessionContexts addEntry:context];
// We don't cap the number of background sessions
} else {
[sAppSessionContexts addEntry:context];
[self _synchronize_pruneSessionsToLimit];
}
}
- (void)_synchronize_removeSessionContext:(TNLURLSessionContext *)context
{
if (context.executionMode == TNLRequestExecutionModeBackground) {
[sBackgroundSessionContexts removeEntry:context];
} else {
[sAppSessionContexts removeEntry:context];
}
}
- (void)_synchronize_pruneSessionsToLimit
{
if (sAppSessionContexts.numberOfEntries > kMaxURLSessionContextCount) {
// Get the least recently used context that doesn't have an associated operation
TNLURLSessionContext *tail = sAppSessionContexts.tailEntry;
while (tail && tail.operationCount > 0) {
tail = tail.previousLRUEntry;
}
// If we found an unused context, remove it
if (tail) {
[sAppSessionContexts removeEntry:tail];
}
}
}
- (void)_synchronize_pruneUnusedSessions
{
// Iterate through all entries (least recent to most recent)
TNLURLSessionContext *currentEntry = sAppSessionContexts.tailEntry;
while (currentEntry) {
TNLURLSessionContext *previous = currentEntry.previousLRUEntry;
// If the entry doesn't have any operations && hasn't been used recently, remove it
if (currentEntry.operationCount == 0) {
const uint64_t lastOperationRemovedMachTime = currentEntry.lastOperationRemovedMachTime;
if (lastOperationRemovedMachTime > 0) {
const NSTimeInterval duration = TNLComputeDuration(lastOperationRemovedMachTime, mach_absolute_time());
if (duration > [TNLGlobalConfiguration sharedInstance].URLSessionInactivityThreshold) {
[sAppSessionContexts removeEntry:currentEntry];
}
}
}
currentEntry = previous;
}
}
- (void)_synchronize_pruneSessionWithConfig:(TNLRequestConfiguration *)config
operationQueueId:(nullable NSString *)operationQueueId
{
TNLURLSessionContext *context = [self _synchronize_sessionContextWithQueueId:operationQueueId
requestConfiguration:config
executionMode:config.executionMode
createIfNeeded:NO];
if (context) {
if (context.operationCount == 0) {
[self _synchronize_removeSessionContext:context];
}
}
}
- (void)_synchronize_applyBackoffDependenciesToOperation:(NSOperation *)op
matchingURL:(NSURL *)URL
host:(nullable NSString *)host
isLongPoll:(BOOL)isLongPoll
{
// get the key (depends on the mode)
NSString *key = _BackoffKeyFromURL(sBackoffMode, URL, host);
if (!key) {
// no key, no dependencies to apply
return;
}
NSHashTable<NSOperation *> *serialOps = sOutstandingSerializeOperations[key];
NSArray<NSOperation *> *serialOpsArray = _FilterArray(serialOps.allObjects, ^BOOL(NSOperation * obj){
return !obj.isFinished;
});
if (!serialOpsArray.count) {
// no serial ops left, clear it
[sOutstandingSerializeOperations removeObjectForKey:key];
serialOps = nil;
serialOpsArray = nil;
}
NSHashTable<NSOperation *> *backoffOps = sOutstandingBackoffOperations[key];
NSArray<NSOperation *> *backoffOpsArray = _FilterArray(backoffOps.allObjects, ^BOOL(NSOperation * obj){
return !obj.isFinished;
});
if (!backoffOpsArray.count) {
if (!serialOps) {
// no backoff ops left, clear it
[sOutstandingBackoffOperations removeObjectForKey:key];
backoffOps = nil;
backoffOpsArray = nil;
} else if (!backoffOps) {
// serial ops but no backoff ops, establish an empty hash-table to populate
backoffOps = [NSHashTable weakObjectsHashTable];
sOutstandingBackoffOperations[key] = backoffOps;
}
}
// No backoff ops or serializing ops to back off with
if (!serialOps && !backoffOps) {
return;
}
TNLAssert(backoffOps != nil);
// make this new operation dependent on prior backoff ops
for (NSOperation *otherOp in backoffOpsArray) {
[op addDependency:otherOp];
}
// add serial delay to slow things down while running serially
if (sSerialDelayDuration > 0 && serialOps.count > 0) {
NSOperation *timeoutOperation = [[TNLTimeoutOperation alloc] initWithTimeoutDuration:sSerialDelayDuration];
for (NSOperation *dep in op.dependencies) {
[timeoutOperation addDependency:dep];
}
[backoffOps addObject:timeoutOperation];
[sURLSessionTaskOperationQueue addOperation:timeoutOperation];
}
// store the op if not a long poll request AND we are still in a serialization mode
if (!isLongPoll && serialOps.count > 0) {
[backoffOps addObject:op];
}
}
- (void)_synchronize_backoffSignalEncounteredForURL:(NSURL *)URL
host:(nullable NSString *)host
headers:(nullable NSDictionary<NSString *, NSString *> *)headers
{
NSString *key = _BackoffKeyFromURL(sBackoffMode, URL, host);
if (!key) {
// no key, no backoff to apply
return;
}
const TNLBackoffBehavior backoffBehavior = [sBackoffBehaviorProvider tnl_backoffBehaviorForURL:URL
responseHeaders:headers];
if (backoffBehavior.backoffDuration > 0) {
NSOperation *timeoutOperation = [[TNLTimeoutOperation alloc] initWithTimeoutDuration:backoffBehavior.backoffDuration];
NSHashTable<NSOperation *> *ops = sOutstandingBackoffOperations[key];
if (!ops) {
ops = [NSHashTable weakObjectsHashTable];
sOutstandingBackoffOperations[key] = ops;
}
// make all outstanding backed off ops depend on this new backoff op
for (NSOperation *op in ops.allObjects) {
[op addDependency:timeoutOperation];
}
[ops addObject:timeoutOperation];
[sURLSessionTaskOperationQueue addOperation:timeoutOperation];
// also add to the outstanding serialization ops
NSHashTable<NSOperation *> *serialOps = sOutstandingSerializeOperations[key];
if (!serialOps) {
serialOps = [NSHashTable weakObjectsHashTable];
sOutstandingSerializeOperations[key] = serialOps;
}
[serialOps addObject:timeoutOperation];
}
if (backoffBehavior.serializeDuration > 0) {
NSOperation *timeoutOperation = [[TNLTimeoutOperation alloc] initWithTimeoutDuration:backoffBehavior.serializeDuration];
NSHashTable<NSOperation *> *ops = sOutstandingSerializeOperations[key];
if (!ops) {
ops = [NSHashTable weakObjectsHashTable];
sOutstandingSerializeOperations[key] = ops;
}
// track the new serialize timer
[ops addObject:timeoutOperation];
[sURLSessionTaskOperationQueue addOperation:timeoutOperation];
}
sSerialDelayDuration = backoffBehavior.serialDelayDuration;
}
@end
@implementation TNLURLSessionManagerV1 (Delegate)
#pragma mark NSURLSessionDelegate
- (void)URLSession:(NSURLSession *)session
didBecomeInvalidWithError:(nullable NSError *)error
{
METHOD_LOG();
// TODO: do we need to propogate this event to operations at all?
_executeOnSynchronizeGCDQueueFromSynchronizeOperationQueue(^{
TNLURLSessionContext *context = [self _synchronize_sessionContextFromURLSession:session];
for (TNLURLSessionTaskOperation *op in context.URLSessionTaskOperations) {
[op URLSession:session didBecomeInvalidWithError:error];
}
});
}
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
METHOD_LOG();
NSMutableArray<id<TNLAuthenticationChallengeHandler>> *handlers = [[[TNLGlobalConfiguration sharedInstance] internalAuthenticationChallengeHandlers] mutableCopy];
[self _private_handleAuthChallenge:challenge
URLSession:session
operation:nil
currentDisposition:nil
handlers:handlers
completion:completionHandler];
}
- (void)_private_notifyAuthChallengeCanceled:(NSURLAuthenticationChallenge *)challenge
URLSession:(NSURLSession *)session
operation:(nullable TNLURLSessionTaskOperation *)operation
handler:(nullable id<TNLAuthenticationChallengeHandler>)handler
context:(nullable id)cancelContext TNL_OBJC_DIRECT
{
if (operation) {
// just the provided operation
if ((id)[NSNull null] != operation) {
[operation handler:handler
didCancelAuthenticationChallenge:challenge
forURLSession:session
context:cancelContext];
}
return;
}
// all the downstream operations
_executeOnSynchronizeGCDQueueFromSynchronizeOperationQueue(^{
TNLURLSessionContext *context = [self _synchronize_sessionContextFromURLSession:session];
for (TNLURLSessionTaskOperation *op in context.URLSessionTaskOperations) {
[op handler:handler
didCancelAuthenticationChallenge:challenge
forURLSession:session
context:cancelContext];
}
});
}
- (void)_private_handleAuthChallenge:(NSURLAuthenticationChallenge *)challenge
URLSession:(NSURLSession *)session
operation:(nullable TNLURLSessionTaskOperation *)operation
currentDisposition:(nullable NSNumber *)currentDisposition
handlers:(NSMutableArray<id<TNLAuthenticationChallengeHandler>> *)handlers
completion:(TNLURLSessionAuthChallengeCompletionBlock)completion TNL_OBJC_DIRECT
{
void (^challengeBlock)(id<TNLAuthenticationChallengeHandler> handler, NSURLSessionAuthChallengeDisposition disposition, id credentialOrContext);
challengeBlock = ^(id<TNLAuthenticationChallengeHandler> handler, NSURLSessionAuthChallengeDisposition disposition, id credentialOrContext) {
NSNumber *newDisposition = currentDisposition;
switch (disposition) {
case NSURLSessionAuthChallengeUseCredential:
{
// There are credentials! Done!
TNLAssert(!credentialOrContext || [credentialOrContext isKindOfClass:[NSURLCredential class]]);
completion(disposition, (NSURLCredential *)credentialOrContext);
return;
}
case NSURLSessionAuthChallengeCancelAuthenticationChallenge:
{
// The challenge is forced to cancel!
// 1) notify downstream request operations
[self _private_notifyAuthChallengeCanceled:challenge
URLSession:session
operation:operation
handler:handler
context:credentialOrContext];
// 2) complete
completion(disposition, nil);
break;
}
case NSURLSessionAuthChallengeRejectProtectionSpace:
{
// Reject the protection space
newDisposition = @(disposition);
break;
}
case NSURLSessionAuthChallengePerformDefaultHandling:
{
// Leave the disposition as-is (`nil` will be default handling)
break;
}
// default: keep the disposition unchanged
}
[self _private_handleAuthChallenge:challenge
URLSession:session
operation:operation
currentDisposition:newDisposition
handlers:handlers
completion:completion];
};
if (currentDisposition) {
TNLAssert(currentDisposition.integerValue != NSURLSessionAuthChallengeUseCredential && currentDisposition.integerValue != NSURLSessionAuthChallengeCancelAuthenticationChallenge);
}
TNLRequestOperation *requestOp = ((id)[NSNull null] != operation) ? operation.requestOperation : nil;
while (handlers.count) {
id<TNLAuthenticationChallengeHandler> handler = handlers.firstObject;
[handlers removeObjectAtIndex:0];
if ([handler respondsToSelector:@selector(tnl_networkLayerDidReceiveAuthChallenge:requestOperation:completion:)]) {
[handler tnl_networkLayerDidReceiveAuthChallenge:challenge
requestOperation:requestOp
completion:^(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential) {
challengeBlock(handler, disposition, credential);
}];
return;
}
}
const NSURLSessionAuthChallengeDisposition finalDisposition = (currentDisposition) ? currentDisposition.integerValue : NSURLSessionAuthChallengePerformDefaultHandling;
completion(finalDisposition, nil);
}
#if TARGET_OS_IPHONE // == IOS + WATCH + TV
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
{
[self URLSessionDidCompleteBackgroundEvents:session];
}
#endif
#pragma mark NSURLSessionTaskDelegate
- (void)URLSession:(NSURLSession *)session
taskIsWaitingForConnectivity:(NSURLSessionTask *)task
{
METHOD_LOG();
_executeOnSynchronizeGCDQueueFromSynchronizeOperationQueue(^{
TNLURLSessionContext *context = [self _synchronize_sessionContextFromURLSession:session];
TNLURLSessionTaskOperation *op = [context operationForTask:task];
if (op) {
if (tnl_available_ios_11) {
[op URLSession:session taskIsWaitingForConnectivity:task];
}
}
});
}
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest *))completionHandler
{
METHOD_LOG();
_executeOnSynchronizeGCDQueueFromSynchronizeOperationQueue(^{
TNLURLSessionContext *context = [self _synchronize_sessionContextFromURLSession:session];
TNLURLSessionTaskOperation *op = [context operationForTask:task];
if (op) {
[op URLSession:session
task:task
willPerformHTTPRedirection:response
newRequest:request
completionHandler:completionHandler];
} else {
completionHandler(request);
}
});
}
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler
{
METHOD_LOG();
__block TNLURLSessionTaskOperation *op = nil;
_executeOnSynchronizeGCDQueueFromSynchronizeOperationQueue(^{
TNLURLSessionContext *context = [self _synchronize_sessionContextFromURLSession:session];
op = [context operationForTask:task];
});
NSMutableArray<id<TNLAuthenticationChallengeHandler>> *handlers = [[[TNLGlobalConfiguration sharedInstance] internalAuthenticationChallengeHandlers] mutableCopy];
if (op) {
id<TNLRequestAuthenticationChallengeHandler> delegate = (id)op.requestOperation.requestDelegate;
if ([delegate respondsToSelector:@selector(tnl_networkLayerDidReceiveAuthChallenge:requestOperation:completion:)]) {
// delegate is also a challenge handler, give it first change at handling challenge
if (!handlers) {
handlers = [[NSMutableArray alloc] init];
}
[handlers insertObject:delegate atIndex:0];
}
}
[self _private_handleAuthChallenge:challenge
URLSession:session
operation:op ?: (id)[NSNull null]
currentDisposition:nil
handlers:handlers
completion:completionHandler];
}
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
needNewBodyStream:(void (^)(NSInputStream * __nullable bodyStream))completionHandler
{
METHOD_LOG();
_executeOnSynchronizeGCDQueueFromSynchronizeOperationQueue(^{
TNLURLSessionContext *context = [self _synchronize_sessionContextFromURLSession:session];
TNLURLSessionTaskOperation *op = [context operationForTask:task];
if (op) {
[op URLSession:session
task:task
needNewBodyStream:completionHandler];
} else {
completionHandler(nil);
}
});
}
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didSendBodyData:(int64_t)bytesSent
totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{
_executeOnSynchronizeGCDQueueFromSynchronizeOperationQueue(^{
TNLURLSessionContext *context = [self _synchronize_sessionContextFromURLSession:session];
TNLURLSessionTaskOperation *op = [context operationForTask:task];
if (op) {
[op URLSession:session
task:task
didSendBodyData:bytesSent
totalBytesSent:totalBytesSent
totalBytesExpectedToSend:totalBytesExpectedToSend];
}
// TODO:[nobrien] - gather heuristics
});
}
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error
{
_executeOnSynchronizeGCDQueueFromSynchronizeOperationQueue(^{
TNLURLSessionContext *context = [self _synchronize_sessionContextFromURLSession:session];
TNLURLSessionTaskOperation *op = [context operationForTask:task];
if (op) {
[op URLSession:session task:task didCompleteWithError:error];
}
// TODO:[nobrien] - gather error info
});
}
// Not implemented due to crash IOS-31427
// See TNLURLSessionManagerV2
//- (void)URLSession:(NSURLSession *)session
// task:(NSURLSessionTask *)task
// didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics;
#pragma mark NSURLSessionDataTaskDelegate
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{
_executeOnSynchronizeGCDQueueFromSynchronizeOperationQueue(^{
TNLURLSessionContext *context = [self _synchronize_sessionContextFromURLSession:session];
TNLURLSessionTaskOperation *op = [context operationForTask:dataTask];
if (op) {
[op URLSession:session
dataTask:dataTask
didReceiveResponse:response
completionHandler:completionHandler];
} else {
completionHandler(NSURLSessionResponseAllow);
}
});
}
// NYI
//- (void)URLSession:(NSURLSession *)session
// dataTask:(NSURLSessionDataTask *)dataTask
// didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
_executeOnSynchronizeGCDQueueFromSynchronizeOperationQueue(^{
TNLURLSessionContext *context = [self _synchronize_sessionContextFromURLSession:session];
TNLURLSessionTaskOperation *op = [context operationForTask:dataTask];
if (op) {
[op URLSession:session dataTask:dataTask didReceiveData:data];
}
});
}
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
willCacheResponse:(NSCachedURLResponse *)proposedResponse
completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler
{
_executeOnSynchronizeGCDQueueFromSynchronizeOperationQueue(^{
TNLURLSessionContext *context = [self _synchronize_sessionContextFromURLSession:session];
TNLURLSessionTaskOperation *op = [context operationForTask:dataTask];
NSCachedURLResponse *flaggedResponse = [proposedResponse tnl_flaggedCachedResponse];
if (op) {
[op URLSession:session
dataTask:dataTask
willCacheResponse:flaggedResponse
completionHandler:completionHandler];
} else {
completionHandler(flaggedResponse);
}
});
}
#pragma mark NSURLSessionDownloadDelegate
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
_executeOnSynchronizeGCDQueueFromSynchronizeOperationQueue(^{
TNLURLSessionContext *context = [self _synchronize_sessionContextFromURLSession:session];
TNLURLSessionTaskOperation *op = [context operationForTask:downloadTask];
if (op) {
[op URLSession:session
downloadTask:downloadTask
didFinishDownloadingToURL:location];
}
});
}
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
_executeOnSynchronizeGCDQueueFromSynchronizeOperationQueue(^{
TNLURLSessionContext *context = [self _synchronize_sessionContextFromURLSession:session];
TNLURLSessionTaskOperation *op = [context operationForTask:downloadTask];
if (op) {
[op URLSession:session
downloadTask:downloadTask
didWriteData:bytesWritten
totalBytesWritten:totalBytesWritten
totalBytesExpectedToWrite:totalBytesExpectedToWrite];
}
});
}
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes
{
_executeOnSynchronizeGCDQueueFromSynchronizeOperationQueue(^{
TNLURLSessionContext *context = [self _synchronize_sessionContextFromURLSession:session];
TNLURLSessionTaskOperation *op = [context operationForTask:downloadTask];
if (op) {
[op URLSession:session
downloadTask:downloadTask
didResumeAtOffset:fileOffset
expectedTotalBytes:expectedTotalBytes];
}
});
}
@end
static volatile atomic_int_fast32_t sSessionContextCount = ATOMIC_VAR_INIT(0);
@implementation TNLURLSessionContext
@synthesize nextLRUEntry = _nextLRUEntry;
@synthesize previousLRUEntry = _previousLRUEntry;
- (NSString *)LRUEntryIdentifier
{
return self.reuseId;
}
- (BOOL)shouldAccessMoveLRUEntryToHead
{
return YES;
}
- (instancetype)initWithURLSession:(NSURLSession *)URLSession
reuseId:(NSString *)reuseId
executionMode:(TNLRequestExecutionMode)mode
{
if (self = [super init]) {
TNLIncrementObjectCount([self class]);
TNLAssert(reuseId != nil);
_reuseId = [reuseId copy];
_URLSession = URLSession;
_URLSessionTaskOperations = [[NSMutableArray alloc] init];
const int32_t previousCount = atomic_fetch_add(&sSessionContextCount, 1);
TNLLogInformation(@"+%@ (%i): %@", NSStringFromClass([self class]), previousCount, reuseId);
// TNLLogDebug(@"Create %@", _URLSession);
if (previousCount > 12-1) {
TNLLogWarning(@"We now have %i %@ instances!", previousCount+1, NSStringFromClass([self class]));
}
[[NSNotificationCenter defaultCenter] postNotificationName:TNLNetworkDidSpinUpSessionNotification
object:nil
userInfo:@{ TNLNetworkSessionIdentifierKey: _reuseId }];
}
return self;
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] postNotificationName:TNLNetworkWillWindDownSessionNotification
object:nil
userInfo:@{ TNLNetworkSessionIdentifierKey: _reuseId }];
const int32_t previousCount = atomic_fetch_sub(&sSessionContextCount, 1);
TNLLogInformation(@"-%@ (%i): %@", NSStringFromClass([self class]), previousCount, _reuseId);
// TNLLogDebug(@"Destroy %@", _URLSession);
TNLURLSessionTaskOperation *op = nil;
while ((op = _URLSessionTaskOperations.firstObject) != nil) {
[self removeOperation:op];
}
[_URLSession finishTasksAndInvalidate];
TNLDecrementObjectCount([self class]);
}
- (NSUInteger)operationCount
{
return _URLSessionTaskOperations.count;
}
- (void)addOperation:(TNLURLSessionTaskOperation *)op
{
TNLAssert(op);
TNLAssert([op isKindOfClass:[TNLURLSessionTaskOperation class]]);
[(NSMutableArray<TNLURLSessionTaskOperation *> *)_URLSessionTaskOperations addObject:op];
_lastOperationRemovedMachTime = 0;
}
- (void)removeOperation:(TNLURLSessionTaskOperation *)op
{
NSUInteger idx = [_URLSessionTaskOperations indexOfObject:op];
if (NSNotFound != idx) {
[(NSMutableArray<TNLURLSessionTaskOperation *> *)_URLSessionTaskOperations removeObjectAtIndex:idx];
if (0 == _URLSessionTaskOperations.count) {
_lastOperationRemovedMachTime = mach_absolute_time();
}
}
}
- (nullable TNLURLSessionTaskOperation *)operationForTask:(NSURLSessionTask *)task
{
TNLAssert(task != nil);
for (TNLURLSessionTaskOperation *operation in _URLSessionTaskOperations) {
if (operation.URLSessionTask == task) {
return operation;
} else {
TNLAssert([operation isKindOfClass:[TNLURLSessionTaskOperation class]]);
#if DEBUG
const NSUInteger taskIdentifier = task.taskIdentifier;
const NSUInteger opTaskIdentifier = operation.URLSessionTask.taskIdentifier; // not thread safe, thus DEBUG only
if (taskIdentifier == opTaskIdentifier) {
if (task != operation.URLSessionTask) {
TNLLogError(@"Two tasks with the same identifier for the same session are not the same...?\n\tTask 1: %@ { taskIdentifier: %tu, request: %@ }\n\tTask 2: %@ { taskIdentifier: %tu, request: %@ }", task, taskIdentifier, task.currentRequest, operation.URLSessionTask, opTaskIdentifier, operation.URLSessionTask.currentRequest);
}
}
#endif
}
}
return nil;
}
- (void)changeOperation:(TNLURLSessionTaskOperation *)op
fromTask:(NSURLSessionTask *)oldTask
toTask:(NSURLSessionTask *)newTask
{
TNLAssert(op != nil);
TNLAssert(oldTask != nil);
TNLAssert(newTask != nil);
TNLAssert(oldTask != newTask);
// TNLAssert(oldTask.taskIdentifier != newTask.taskIdentifier);
}
@end
#pragma mark TNLRequestConfiguration(URLSession)
@implementation TNLRequestConfiguration (URLSession)
+ (TNLRequestConfiguration *)configurationWithSessionConfiguration:(nullable NSURLSessionConfiguration *)sessionConfiguration
{
return [[self alloc] initWithSessionConfiguration:sessionConfiguration];
}
- (instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)config
{
if (!config) {
self = [self init];
} else if ((self = [super init])) {
_retryPolicyProvider = nil;
_URLCredentialStorage = config.URLCredentialStorage;
_URLCache = config.URLCache;
_cookieStorage = config.HTTPCookieStorage;
_sharedContainerIdentifier = [config.sharedContainerIdentifier copy];
memset(&_ivars, 0, sizeof(_ivars)); // prep ivars as all 0
_ivars.executionMode = TNLRequestExecutionModeDefault;
_ivars.redirectPolicy = TNLRequestRedirectPolicyDefault;
_ivars.responseDataConsumptionMode = TNLResponseDataConsumptionModeDefault;
_ivars.protocolOptions = TNLRequestProtocolOptionsDefault;
_ivars.contributeToExecutingNetworkConnectionsCount = YES;
_ivars.skipHostSanitization = NO;
_ivars.responseComputeHashAlgorithm = TNLResponseHashComputeAlgorithmNone;
[self applyDefaultTimeouts];
_ivars.cachePolicy = config.requestCachePolicy;
_ivars.networkServiceType = config.networkServiceType;
_ivars.cookieAcceptPolicy = config.HTTPCookieAcceptPolicy;
_ivars.allowsCellularAccess = (config.allowsCellularAccess != NO);
_ivars.connectivityOptions = TNLRequestConnectivityOptionsNone;
if (tnl_available_ios_11) {
if ([NSURLSessionConfiguration tnl_URLSessionCanUseWaitsForConnectivity]) {
if (config.waitsForConnectivity) {
_ivars.connectivityOptions = TNLRequestConnectivityOptionWaitForConnectivity;
}
} else {
// waitsForConnectivity bug, leave as .None
if (config.waitsForConnectivity) {
TNL_LOG_WAITS_FOR_CONNECTIVITY_WARNING();
}
}
}
_ivars.discretionary = (config.isDiscretionary != NO);
_ivars.shouldSetCookies = (config.HTTPShouldSetCookies != NO);
#if TARGET_OS_IPHONE // == IOS + WATCH + TV
_ivars.shouldLaunchAppForBackgroundEvents = (config.sessionSendsLaunchEvents != NO);
#endif
#if TARGET_OS_IOS
if (tnl_available_ios_11) {
_ivars.multipathServiceType = config.multipathServiceType;
}
#endif
_ivars.shouldUseExtendedBackgroundIdleMode = (config.shouldUseExtendedBackgroundIdleMode != NO);
}
return self;
}
- (NSURLSessionConfiguration *)generateCanonicalSessionConfiguration
{
return [self generateCanonicalSessionConfigurationWithExecutionMode:self.executionMode];
}
- (NSURLSessionConfiguration *)generateCanonicalSessionConfigurationForBackgroundModeWithIdentifier:(nullable NSString *)identifier
{
return [self generateCanonicalSessionConfigurationWithExecutionMode:TNLRequestExecutionModeBackground identifier:identifier];
}
- (NSURLSessionConfiguration *)generateCanonicalSessionConfigurationWithExecutionMode:(TNLRequestExecutionMode)mode
{
return [self generateCanonicalSessionConfigurationWithExecutionMode:mode identifier:nil];
}
- (NSURLSessionConfiguration *)generateCanonicalSessionConfigurationWithExecutionMode:(TNLRequestExecutionMode)mode
identifier:(nullable NSString *)identifier
{
return [self generateCanonicalSessionConfigurationWithExecutionMode:mode
identifier:identifier
canonicalURLCache:nil
canonicalURLCredentialStorage:nil
canonicalCookieStorage:nil];
}
- (NSURLSessionConfiguration *)generateCanonicalSessionConfigurationWithExecutionMode:(TNLRequestExecutionMode)mode
identifier:(nullable NSString *)identifier
canonicalURLCache:(nullable NSURLCache *)canonicalCache
canonicalURLCredentialStorage:(nullable NSURLCredentialStorage *)canonicalCredentialStorage
canonicalCookieStorage:(nullable NSHTTPCookieStorage *)canonicalCookieStorage
{
if (TNLRequestExecutionModeBackground == mode) {
if (!identifier) {
TNLParameterCollection *params = TNLMutableParametersFromRequestConfiguration(self,
canonicalCache,
canonicalCredentialStorage,
canonicalCookieStorage);
identifier = params.stableURLEncodedStringValue;
}
} else {
identifier = nil;
}
// Generate the config (based on execution mode)
NSURLSessionConfiguration *config = (TNLRequestExecutionModeBackground == mode) ?
[NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:identifier] :
[NSURLSessionConfiguration defaultSessionConfiguration];
// Apply settings
[self applySettingsToSessionConfiguration:config];
// Update the containers
// URL Cache
config.URLCache = canonicalCache ?: TNLUnwrappedURLCache(config.URLCache);
// Credential Storage
config.URLCredentialStorage = canonicalCredentialStorage ?: TNLUnwrappedURLCredentialStorage(config.URLCredentialStorage);
// Cookie Storage
config.HTTPCookieStorage = canonicalCookieStorage ?: TNLUnwrappedCookieStorage(config.HTTPCookieStorage);
return config;
}
- (void)applySettingsToSessionConfiguration:(nullable NSURLSessionConfiguration *)config
{
// Transfer configuration
_ConfigureSessionConfigurationWithRequestConfiguration(config, self);
/// Overrides -- keep TNLMutableParametersStrip* functions up to date as things change in this method
// Override the timeouts so that TNL owns the timeouts instead of the NSURLSession
NSTimeInterval dataTimeout = [TNLGlobalConfiguration sharedInstance].timeoutIntervalBetweenDataTransfer;
if (dataTimeout <= 0.0) {
dataTimeout = NSTimeIntervalSince1970;
}
config.timeoutIntervalForRequest = dataTimeout;
if (self.executionMode != TNLRequestExecutionModeBackground) {
config.timeoutIntervalForResource = NSTimeIntervalSince1970;
}
// TNL will control when to fail early if waiting for connectivity
if (tnl_available_ios_11) {
if ([NSURLSessionConfiguration tnl_URLSessionCanUseWaitsForConnectivity]) {
config.waitsForConnectivity = YES;
} else {
config.waitsForConnectivity = NO; // waitsForConnectivity bug
}
}
// TNL will have the NSURLRequest control some configurations,
// so have them be "unrestricted" on the session.
// This will further reduce the number of NSURLSession instances TNL needs to spin up.
// TODO: config.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
config.allowsCellularAccess = YES;
config.networkServiceType = NSURLNetworkServiceTypeDefault;
}
@end
@implementation NSURLSessionConfiguration (TNLRequestConfiguration)
+ (NSURLSessionConfiguration *)sessionConfigurationWithConfiguration:(TNLRequestConfiguration *)requestConfig
{
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
if (requestConfig.executionMode == TNLRequestExecutionModeBackground) {
_ConfigureSessionConfigurationWithRequestConfiguration(sessionConfig, requestConfig);
TNLParameterCollection *params = TNLMutableParametersFromRequestConfiguration(requestConfig, nil, nil, nil);
sessionConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:params.stableURLEncodedStringValue];
}
_ConfigureSessionConfigurationWithRequestConfiguration(sessionConfig, requestConfig);
return sessionConfig;
}
+ (NSURLSessionConfiguration *)tnl_defaultSessionConfigurationWithNilPersistence
{
NSURLSessionConfiguration *config = [[NSURLSessionConfiguration defaultSessionConfiguration] copy];
#if TARGET_OS_IPHONE // == IOS + WATCH + TV
config.sessionSendsLaunchEvents = YES;
#endif
config.URLCache = nil;
config.URLCredentialStorage = nil;
config.HTTPCookieStorage = nil;
config.HTTPCookieAcceptPolicy = NSHTTPCookieAcceptPolicyNever;
config.HTTPShouldSetCookies = NO;
config.shouldUseExtendedBackgroundIdleMode = NO;
return config;
}
@end
@implementation TNLURLSessionManagerV2
+ (NSInteger)version
{
return 2;
}
+ (instancetype)internalSharedInstance
{
static TNLURLSessionManagerV2 *sInstance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sInstance = [[TNLURLSessionManagerV2 alloc] initInternal];
});
return sInstance;
}
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics
{
_executeOnSynchronizeGCDQueueFromSynchronizeOperationQueue(^{
TNLURLSessionContext *context = [self _synchronize_sessionContextFromURLSession:session];
TNLURLSessionTaskOperation *op = [context operationForTask:task];
if (op) {
[op URLSession:session task:task didFinishCollectingMetrics:metrics];
}
});
}
@end
@implementation TNLURLSessionContextLRUCacheDelegate
- (void)tnl_cache:(TNLLRUCache *)cache didEvictEntry:(TNLURLSessionContext *)entry
{
TNLLogInformation(@"Evicted TNLURLSessionContext with identifier: %@", entry.reuseId);
}
@end
#pragma mark Exposed Functions
TNLMutableParameterCollection *TNLMutableParametersFromURLSessionConfiguration(NSURLSessionConfiguration * __nullable config)
{
if (!config) {
return nil;
}
id tempValue;
TNLMutableParameterCollection *params = [[TNLMutableParameterCollection alloc] init];
params[TNLSessionConfigurationPropertyKeyRequestCachePolicy] = @(config.requestCachePolicy);
params[TNLSessionConfigurationPropertyKeyTimeoutIntervalForRequest] = @(round(config.timeoutIntervalForRequest));
params[TNLSessionConfigurationPropertyKeyTimeoutIntervalForResource] = @(round(config.timeoutIntervalForResource));
params[TNLSessionConfigurationPropertyKeyNetworkServiceType] = @(config.networkServiceType);
params[TNLSessionConfigurationPropertyKeyAllowsCellularAccess] = @(config.allowsCellularAccess);
params[TNLSessionConfigurationPropertyKeyDiscretionary] = @(config.isDiscretionary);
if (tnl_available_ios_11) {
params[TNLSessionConfigurationPropertyKeyWaitsForConnectivity] = @(config.waitsForConnectivity);
}
#if TARGET_OS_IPHONE // == IOS + WATCH + TV
params[TNLSessionConfigurationPropertyKeySessionSendsLaunchEvents] = @(config.sessionSendsLaunchEvents);
#endif
params[TNLSessionConfigurationPropertyKeyShouldUseExtendedBackgroundIdleMode] = @(config.shouldUseExtendedBackgroundIdleMode);
tempValue = config.connectionProxyDictionary;
if ([(NSDictionary *)tempValue count] > 0) {
NSString *cpdValue = TNLURLEncodeDictionary((NSDictionary *)tempValue, TNLURLEncodingOptionStableOrder);
TNLAssert(cpdValue.length > 0);
params[TNLSessionConfigurationPropertyKeyConnectionProxyDictionary] = cpdValue;
}
params[TNLSessionConfigurationPropertyKeyTLSMinimumSupportedProtocol] = @(config.TLSMinimumSupportedProtocol);
params[TNLSessionConfigurationPropertyKeyTLSMaximumSupportedProtocol] = @(config.TLSMaximumSupportedProtocol);
params[TNLSessionConfigurationPropertyKeyHTTPShouldUsePipelining] = @(config.HTTPShouldUsePipelining);
params[TNLSessionConfigurationPropertyKeyHTTPShouldSetCookies] = @(config.HTTPShouldSetCookies);
params[TNLSessionConfigurationPropertyKeyHTTPCookieAcceptPolicy] = @(config.HTTPCookieAcceptPolicy);
tempValue = config.HTTPAdditionalHeaders;
if ([(NSDictionary *)tempValue count] > 0) {
NSString *headersValue = TNLURLEncodeDictionary((NSDictionary *)tempValue, TNLURLEncodingOptionStableOrder);
TNLAssert(headersValue);
params[TNLSessionConfigurationPropertyKeyHTTPAdditionalHeaders] = headersValue;
}
params[TNLSessionConfigurationPropertyKeyHTTPMaximumConnectionsPerHost] = @(config.HTTPMaximumConnectionsPerHost);
tempValue = config.HTTPCookieStorage;
if (tempValue) {
params[TNLSessionConfigurationPropertyKeyHTTPCookieStorage] = [NSString stringWithFormat:@"%@_%p", NSStringFromClass([tempValue class]), tempValue];
}
tempValue = config.URLCredentialStorage;
if (tempValue) {
params[TNLSessionConfigurationPropertyKeyURLCredentialStorage] = [NSString stringWithFormat:@"%@_%p", NSStringFromClass([tempValue class]), tempValue];
}
tempValue = config.URLCache;
if (tempValue) {
params[TNLSessionConfigurationPropertyKeyURLCache] = [NSString stringWithFormat:@"%@_%p", NSStringFromClass([tempValue class]), tempValue];
}
NSArray *protocolClasses = config.protocolClasses;
if (protocolClasses.count > 0) {
NSUInteger i = 0;
for (Class class in protocolClasses) {
params[[NSString stringWithFormat:@"%@%tu", TNLSessionConfigurationPropertyKeyProtocolClassPrefix, i]] = NSStringFromClass(class);
i++;
}
}
tempValue = config.sharedContainerIdentifier;
if (tempValue) {
params[TNLSessionConfigurationPropertyKeySharedContainerIdentifier] = tempValue;
}
return params;
}
static void TNLMutableParametersStripNonURLSessionProperties(TNLMutableParameterCollection *params)
{
// Only for TNL layer (not NSURLSession)
params[TNLRequestConfigurationPropertyKeyRedirectPolicy] = nil;
params[TNLRequestConfigurationPropertyKeyResponseDataConsumptionMode] = nil;
params[TNLRequestConfigurationPropertyKeyOperationTimeout] = nil;
params[TNLRequestConfigurationPropertyKeyDeferrableInterval] = nil;
params[TNLRequestConfigurationPropertyKeyConnectivityOptions] = nil;
}
static void TNLMutableParametersStripNonBackgroundURLSessionProperties(TNLMutableParameterCollection *params)
{
// Strip properties background NSURLSession only properties
params[TNLRequestConfigurationPropertyKeyIdleTimeout] = nil;
params[TNLRequestConfigurationPropertyKeyAttemptTimeout] = nil;
params[TNLRequestConfigurationPropertyKeyShouldLaunchAppForBackgroundEvents] = nil;
}
static void TNLMutableParametersStripOverriddenURLSessionProperties(TNLMutableParameterCollection *params)
{
// Strip properties that are overridden in order to coalesce more NSURLSession instances
// TODO: params[TNLRequestConfigurationPropertyKeyCachePolicy] = nil;
params[TNLRequestConfigurationPropertyKeyAllowsCellularAccess] = nil;
params[TNLRequestConfigurationPropertyKeyNetworkServiceType] = nil;
}
static NSArray *_FilterArray(NSArray *source, _FilterBlock filterBlock)
{
NSMutableIndexSet *set = [[NSMutableIndexSet alloc] init];
[source enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (filterBlock(obj)) {
[set addIndex:idx];
}
}];
if (set.count == 0) {
// no matches
return nil;
}
if (((set.lastIndex - set.firstIndex) + 1) == set.count) {
// contiguous!
if (set.firstIndex == 0 && set.lastIndex == (source.count - 1)) {
// same as source
return [source copy];
} else {
return [source subarrayWithRange:NSMakeRange(set.firstIndex, set.count)];
}
}
// non-contiguous
NSMutableArray *destination = [[NSMutableArray alloc] initWithCapacity:set.count];
[source enumerateObjectsAtIndexes:set
options:0
usingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[destination addObject:obj];
}];
return [destination copy];
}
BOOL TNLURLSessionIdentifierIsTaggedForTNL(NSString *identifier)
{
return [identifier hasPrefix:[TNLTwitterNetworkLayerURLScheme stringByAppendingString:@"://"]];
}
#pragma mark Private Functions
static NSString *_GenerateReuseIdentifier(NSString * __nullable operationQueueId,
NSString *URLSessionConfigurationIdentificationString,
TNLRequestExecutionMode executionmode)
{
NSString *identifier = nil;
NSString *modeStr = nil;
switch (executionmode) {
case TNLRequestExecutionModeInApp:
case TNLRequestExecutionModeInAppBackgroundTask:
modeStr = @"InApp";
identifier = kInAppURLSessionContextIdentifier;
break;
case TNLRequestExecutionModeBackground:
modeStr = @"Background";
identifier = operationQueueId /* nil is OK */;
break;
default:
break;
}
TNLAssert(modeStr);
TNLAssert(URLSessionConfigurationIdentificationString);
static NSString *sVersionPath;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sVersionPath = [TNLVersion() stringByReplacingOccurrencesOfString:@"." withString:@"_"];
});
NSString *reuseId = [NSString stringWithFormat:@"%@://%@/%@/%@?%@", TNLTwitterNetworkLayerURLScheme, identifier, sVersionPath, modeStr, URLSessionConfigurationIdentificationString];
return reuseId;
}
static void _ConfigureSessionConfigurationWithRequestConfiguration(NSURLSessionConfiguration * __nullable sessionConfig,
TNLRequestConfiguration *requestConfig)
{
// Transfer
sessionConfig.allowsCellularAccess = requestConfig.allowsCellularAccess;
sessionConfig.discretionary = requestConfig.isDiscretionary;
sessionConfig.networkServiceType = requestConfig.networkServiceType;
sessionConfig.requestCachePolicy = requestConfig.cachePolicy;
if (tnl_available_ios_11) {
const TNLRequestConnectivityOptions connectivityOptions = requestConfig.connectivityOptions;
if (TNL_BITMASK_INTERSECTS_FLAGS(connectivityOptions, TNLRequestConnectivityOptionWaitForConnectivity)) {
sessionConfig.waitsForConnectivity = YES;
} else if (TNL_BITMASK_INTERSECTS_FLAGS(connectivityOptions, TNLRequestConnectivityOptionWaitForConnectivityWhenRetryPolicyProvided) && requestConfig.retryPolicyProvider != nil) {
sessionConfig.waitsForConnectivity = YES;
} else {
sessionConfig.waitsForConnectivity = NO;
}
if (![NSURLSessionConfiguration tnl_URLSessionCanUseWaitsForConnectivity]) {
if (sessionConfig.waitsForConnectivity) {
TNL_LOG_WAITS_FOR_CONNECTIVITY_WARNING();
sessionConfig.waitsForConnectivity = NO;
}
}
}
#if TARGET_OS_IPHONE // == IOS + WATCH + TV
sessionConfig.sessionSendsLaunchEvents = requestConfig.shouldLaunchAppForBackgroundEvents;
#endif
sessionConfig.HTTPCookieAcceptPolicy = requestConfig.cookieAcceptPolicy;
sessionConfig.HTTPShouldSetCookies = requestConfig.shouldSetCookies;
sessionConfig.sharedContainerIdentifier = requestConfig.sharedContainerIdentifier;
#if TARGET_OS_IOS
if (tnl_available_ios_11) {
sessionConfig.multipathServiceType = requestConfig.multipathServiceType;
}
#endif
sessionConfig.shouldUseExtendedBackgroundIdleMode = requestConfig.shouldUseExtendedBackgroundIdleMode;
// Transfer protocols
NSArray<Class> *additionalClasses = TNLProtocolClassesForProtocolOptions(requestConfig.protocolOptions);
[sessionConfig tnl_insertProtocolClasses:additionalClasses];
// Transfer potentially proxied values
sessionConfig.URLCredentialStorage = TNLUnwrappedURLCredentialStorage(requestConfig.URLCredentialStorage);
sessionConfig.URLCache = TNLUnwrappedURLCache(requestConfig.URLCache);
sessionConfig.HTTPCookieStorage = TNLUnwrappedCookieStorage(requestConfig.cookieStorage);
// Best proxy values
sessionConfig.timeoutIntervalForRequest = (requestConfig.idleTimeout < MIN_TIMER_INTERVAL) ? NSTimeIntervalSince1970 : requestConfig.idleTimeout;
sessionConfig.timeoutIntervalForResource = (requestConfig.attemptTimeout < MIN_TIMER_INTERVAL) ? NSTimeIntervalSince1970 : requestConfig.attemptTimeout;
}
static NSString * __nullable _BackoffKeyFromURL(const TNLGlobalConfigurationBackoffMode mode,
NSURL *URL,
NSString * __nullable host)
{
if (TNLGlobalConfigurationBackoffModeDisabled == mode) {
// return early to avoid the lowercase string overhead
return nil;
}
if (host) {
host = host.lowercaseString;
} else {
host = URL.host.lowercaseString;
}
switch (mode) {
case TNLGlobalConfigurationBackoffModeKeyOffHost:
return host;
case TNLGlobalConfigurationBackoffModeKeyOffHostAndPath:
{
NSString *path = [URL.path lowercaseString];
const BOOL pathPrefixedWithSlash = [path hasPrefix:@"/"];
if (!host.length) {
if (!path.length) {
return nil;
}
return (pathPrefixedWithSlash) ? path : [@"/" stringByAppendingString:path];
}
if (!path.length) {
return host;
}
return [NSString stringWithFormat:@"%@%@%@", host, (pathPrefixedWithSlash) ? @"" : @"/", path];
}
case TNLGlobalConfigurationBackoffModeDisabled:
return nil;
}
TNLAssertNever();
return nil;
}
static void _PrepareSessionManagement()
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// Threading
sSynchronizeQueue = dispatch_queue_create("TNLURLSessionManager.synchronize.queue", DISPATCH_QUEUE_SERIAL);
sSynchronizeOperationQueue = [[NSOperationQueue alloc] init];
sSynchronizeOperationQueue.name = @"TNLURLSessionManager.synchronize.operation.queue";
sSynchronizeOperationQueue.maxConcurrentOperationCount = 1;
sSynchronizeOperationQueue.qualityOfService = (NSQualityOfServiceUtility + NSQualityOfServiceUserInitiated / 2);
sSynchronizeOperationQueue.underlyingQueue = sSynchronizeQueue;
sURLSessionTaskOperationQueue = [[NSOperationQueue alloc] init];
sURLSessionTaskOperationQueue.name = @"TNLURLSessionManager.task.operation.queue";
sURLSessionTaskOperationQueue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount;
sURLSessionTaskOperationQueue.qualityOfService = (NSQualityOfServiceUtility + NSQualityOfServiceUserInitiated / 2);
// State
sOutstandingBackoffOperations = [[NSMutableDictionary alloc] init];
sOutstandingSerializeOperations = [[NSMutableDictionary alloc] init];
sSessionContextsDelegate = [[TNLURLSessionContextLRUCacheDelegate alloc] init];
sAppSessionContexts = [[TNLLRUCache alloc] initWithEntries:nil delegate:sSessionContextsDelegate];
sBackgroundSessionContexts = [[TNLLRUCache alloc] initWithEntries:nil delegate:sSessionContextsDelegate];
sActiveURLSessionTaskOperations = [[NSMutableSet alloc] init];
sBackgroundSessionCompletionHandlerDictionary = [[NSMutableDictionary alloc] init];
sBackoffBehaviorProvider = [[TNLSimpleBackoffBehaviorProvider alloc] init];
});
}
NS_ASSUME_NONNULL_END