Source/TNLGlobalConfiguration.m (416 lines of code) (raw):

// // TNLGlobalConfiguration.m // TwitterNetworkLayer // // Created on 11/21/14. // Copyright © 2020 Twitter. All rights reserved. // #import "TNL_Project.h" #import "TNLBackoff.h" #import "TNLGlobalConfiguration_Project.h" #import "TNLLogger.h" #import "TNLRequestOperationQueue_Project.h" NS_ASSUME_NONNULL_BEGIN NSTimeInterval const TNLGlobalConfigurationURLSessionInactivityThresholdDefault = 60.0 * 4.0; // four minutes const TNLBackgroundTaskIdentifier TNLBackgroundTaskInvalid = 0; static const TNLBackgroundTaskIdentifier TNLBackgroundTaskInitial = 1; const NSTimeInterval TNLGlobalConfigurationRequestOperationCallbackTimeoutDefault = 10.0; TNL_OBJC_FINAL TNL_OBJC_DIRECT_MEMBERS @interface TNLBackgroundTaskHandleInternal : NSObject @property (nonatomic, nullable, copy, readonly) void (^expirationHandler)(void); @property (nonatomic, nullable, copy, readonly) NSString *name; @property (nonatomic, readonly) TNLBackgroundTaskIdentifier taskIdentifier; - (instancetype)initWithTaskIdentifier:(TNLBackgroundTaskIdentifier)taskId name:(nullable NSString *)name expirationHandler:(void(^ __nullable)(void))handler; - (instancetype)init NS_UNAVAILABLE; + (instancetype)new NS_UNAVAILABLE; @end @implementation TNLGlobalConfiguration { TNLBackgroundTaskIdentifier _nextBackgroundTaskIdentifier; NSMutableDictionary<NSNumber *, TNLBackgroundTaskHandleInternal *> *_runningBackgroundTasks; dispatch_queue_t _backgroundTaskQueue; NSArray<id<TNLAuthenticationChallengeHandler>> *_authHandlers; id<TNLBackoffSignaler> _backoffSignaler; #if TARGET_OS_IOS || TARGET_OS_TV UIBackgroundTaskIdentifier _sharedUIApplicationBackgroundTaskIdentifier; #endif } + (NSString *)version { return TNLVersion(); } + (instancetype)sharedInstance { static TNLGlobalConfiguration *sConfig; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sConfig = [[TNLGlobalConfiguration alloc] initInternal]; }); return sConfig; } - (instancetype)init { [self doesNotRecognizeSelector:_cmd]; abort(); } - (instancetype)initInternal { if (self = [super init]) { _configurationQueue = dispatch_queue_create("tnl.global.config.queue", DISPATCH_QUEUE_CONCURRENT); _requestOperationCallbackTimeout = TNLGlobalConfigurationRequestOperationCallbackTimeoutDefault; _backgroundTaskQueue = dispatch_queue_create("tnl.global.bg.task.queue", DISPATCH_QUEUE_SERIAL); _nextBackgroundTaskIdentifier = TNLBackgroundTaskInitial; _runningBackgroundTasks = [[NSMutableDictionary alloc] init]; _idleTimeoutMode = TNLGlobalConfigurationIdleTimeoutModeDefault; _timeoutIntervalBetweenDataTransfer = 0.0; _operationAutomaticDependencyPriorityThreshold = (TNLPriority)NSIntegerMax; _internalURLSessionInactivityThreshold = TNLGlobalConfigurationURLSessionInactivityThresholdDefault; _backoffSignaler = [[TNLSimpleBackoffSignaler alloc] init]; #if TARGET_OS_IOS || TARGET_OS_TV _sharedUIApplicationBackgroundTaskIdentifier = 0; const Class UIApplicationClass = TNLDynamicUIApplicationClass(); if (UIApplicationClass != Nil) { _sharedUIApplicationBackgroundTaskIdentifier = UIBackgroundTaskInvalid; NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; [nc addObserver:self selector:@selector(_tnl_applicationDidFinishLaunching:) name:UIApplicationDidFinishLaunchingNotification object:nil]; [nc addObserver:self selector:@selector(_tnl_applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil]; [nc addObserver:self selector:@selector(_tnl_applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil]; [nc addObserver:self selector:@selector(_tnl_applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil]; [nc addObserver:self selector:@selector(_tnl_applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; [nc addObserver:self selector:@selector(_tnl_applicationDidReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil]; UIApplication *sharedUIApplication = TNLDynamicUIApplicationSharedApplication(); if (sharedUIApplication) { if ([NSThread isMainThread]) { _lastApplicationState = sharedUIApplication.applicationState; } else { _lastApplicationState = UIApplicationStateInactive; tnl_dispatch_async_autoreleasing(dispatch_get_main_queue(), ^{ self.lastApplicationState = sharedUIApplication.applicationState; }); } } else { // application can be `nil` if `TNLGlobalConfiguration` is accessed prior to app launch _lastApplicationState = UIApplicationStateBackground; } } #endif // IOS + TV } return self; } #if TARGET_OS_IOS || TARGET_OS_TV - (void)_tnl_applicationDidFinishLaunching:(NSNotification *)note { UIApplication *application = note.object ?: TNLDynamicUIApplicationSharedApplication(); self.lastApplicationState = application.applicationState; } - (void)_tnl_applicationWillResignActive:(NSNotification *)note { self.lastApplicationState = UIApplicationStateInactive; } - (void)_tnl_applicationWillEnterForeground:(NSNotification *)note { // When you adopt UIScene in iOS 13+, the foreground notification is sent // on both cold start and return from background. We only want to update // our application state for the latter if (self.lastApplicationState == UIApplicationStateBackground) { self.lastApplicationState = UIApplicationStateInactive; } } - (void)_tnl_applicationDidBecomeActive:(NSNotification *)note { self.lastApplicationState = UIApplicationStateActive; } - (void)_tnl_applicationDidEnterBackground:(NSNotification *)note { self.lastApplicationState = UIApplicationStateBackground; if (TNL_BITMASK_INTERSECTS_FLAGS(self.internalURLSessionPruneOptions, TNLGlobalConfigurationURLSessionPruneOptionOnApplicationBackground)) { [[TNLURLSessionManager sharedInstance] pruneUnusedURLSessions]; } } - (void)_tnl_applicationDidReceiveMemoryWarning:(NSNotification *)note { if (TNL_BITMASK_INTERSECTS_FLAGS(self.internalURLSessionPruneOptions, TNLGlobalConfigurationURLSessionPruneOptionOnMemoryWarning)) { [[TNLURLSessionManager sharedInstance] pruneUnusedURLSessions]; } } #endif // IOS + TV - (void)addNetworkObserver:(id<TNLNetworkObserver>)observer { if (observer) { [TNLRequestOperationQueue addGlobalNetworkObserver:observer]; } } - (void)removeNetworkObserver:(id<TNLNetworkObserver>)observer { if (observer) { [TNLRequestOperationQueue removeGlobalNetworkObserver:observer]; } } - (NSArray<id<TNLNetworkObserver>> *)allNetworkObservers { return [TNLRequestOperationQueue allGlobalNetworkObservers]; } - (void)addHeaderProvider:(id<TNLHTTPHeaderProvider>)provider { if (provider) { [TNLRequestOperationQueue addGlobalHeaderProvider:provider]; } } - (void)removeHeaderProvider:(id<TNLHTTPHeaderProvider>)provider { if (provider) { [TNLRequestOperationQueue removeGlobalHeaderProvider:provider]; } } - (NSArray<id<TNLHTTPHeaderProvider>> *)allHeaderProviders { return [TNLRequestOperationQueue allGlobalHeaderProviders]; } - (TNLGlobalConfigurationBackoffMode)backoffMode { return [TNLURLSessionManager sharedInstance].backoffMode; } - (void)setBackoffMode:(TNLGlobalConfigurationBackoffMode)mode { [TNLURLSessionManager sharedInstance].backoffMode = mode; } - (id<TNLBackoffBehaviorProvider>)backoffBehaviorProvider { return [TNLURLSessionManager sharedInstance].backoffBehaviorProvider; } - (void)setBackoffBehaviorProvider:(nullable id<TNLBackoffBehaviorProvider>)provider { [TNLURLSessionManager sharedInstance].backoffBehaviorProvider = provider; } - (id<TNLBackoffSignaler>)backoffSignaler { if (dispatch_queue_get_label(tnl_network_queue()) == dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)) { return _backoffSignaler; } __block id<TNLBackoffSignaler> signaler = nil; dispatch_sync(tnl_network_queue(), ^{ signaler = self->_backoffSignaler; }); return signaler; } - (void)setBackoffSignaler:(nullable id<TNLBackoffSignaler>)backoffSignaler { if (!backoffSignaler) { backoffSignaler = [[TNLSimpleBackoffSignaler alloc] init]; } if (dispatch_queue_get_label(tnl_network_queue()) == dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL)) { _backoffSignaler = backoffSignaler; return; } dispatch_async(tnl_network_queue(), ^{ self->_backoffSignaler = backoffSignaler; }); } - (TNLGlobalConfigurationURLSessionPruneOptions)URLSessionPruneOptions { return self.internalURLSessionPruneOptions; } - (void)setURLSessionPruneOptions:(TNLGlobalConfigurationURLSessionPruneOptions)URLSessionPruneOptions { if (TNLGlobalConfigurationURLSessionPruneOptionNow == URLSessionPruneOptions) { [[TNLURLSessionManager sharedInstance] pruneUnusedURLSessions]; return; } self.internalURLSessionPruneOptions = URLSessionPruneOptions; } - (NSTimeInterval)URLSessionInactivityThreshold { return self.internalURLSessionInactivityThreshold; } - (void)setURLSessionInactivityThreshold:(NSTimeInterval)URLSessionInactivityThreshold { if (URLSessionInactivityThreshold < 0.0) { URLSessionInactivityThreshold = TNLGlobalConfigurationURLSessionInactivityThresholdDefault; } self.internalURLSessionInactivityThreshold = URLSessionInactivityThreshold; } - (void)pruneURLSessionMatchingRequestConfiguration:(TNLRequestConfiguration *)config operationQueueId:(nullable NSString *)operationQueueId { [[TNLURLSessionManager sharedInstance] pruneURLSessionMatchingRequestConfiguration:config operationQueueId:operationQueueId]; } - (void)setLogger:(nullable id<TNLLogger>)logger { gTNLLogger = logger; self.internalLogger = logger; } - (nullable id<TNLLogger>)logger { return self.internalLogger; } - (void)setAssertsEnabled:(BOOL)assertsEnabled { gTwitterNetworkLayerAssertEnabled = assertsEnabled; } - (BOOL)areAssertsEnabled { return gTwitterNetworkLayerAssertEnabled; } - (void)addAuthenticationChallengeHandler:(id<TNLAuthenticationChallengeHandler>)handler { dispatch_barrier_async(_configurationQueue, ^{ @autoreleasepool { if (!self->_authHandlers) { self->_authHandlers = @[handler]; } else if (![self->_authHandlers containsObject:handler]) { self->_authHandlers = [self->_authHandlers arrayByAddingObject:handler]; } } }); } - (void)removeAuthenticationChallengeHandler:(id<TNLAuthenticationChallengeHandler>)handler { dispatch_barrier_async(_configurationQueue, ^{ @autoreleasepool { if (self->_authHandlers) { NSMutableArray<id<TNLAuthenticationChallengeHandler>> *handlers = [self->_authHandlers mutableCopy]; [handlers removeObject:handler]; self->_authHandlers = (handlers.count > 0) ? [handlers copy] : nil; } } }); } - (nullable NSArray<id<TNLAuthenticationChallengeHandler>> *)internalAuthenticationChallengeHandlers { __block NSArray<id<TNLAuthenticationChallengeHandler>> *handlers; dispatch_sync(self->_configurationQueue, ^{ handlers = self->_authHandlers; }); return handlers; } #pragma mark Background Tasks - (TNLBackgroundTaskIdentifier)startBackgroundTaskWithName:(nullable NSString *)name expirationHandler:(void(^ __nullable)(void))handler { __block TNLBackgroundTaskIdentifier identifier; dispatch_sync(_backgroundTaskQueue, ^{ identifier = self->_nextBackgroundTaskIdentifier++; if (identifier == TNLBackgroundTaskInvalid) { TNLLogWarning(@"Background Task Identifier pool has been exhausted, restarting."); identifier = TNLBackgroundTaskInitial; self->_nextBackgroundTaskIdentifier = identifier + 1; } }); dispatch_block_t block = ^{ [self _main_ensureSharedBackgroundTask]; TNLBackgroundTaskHandleInternal *handle = [[TNLBackgroundTaskHandleInternal alloc] initWithTaskIdentifier:identifier name:name expirationHandler:handler]; self->_runningBackgroundTasks[@(identifier)] = handle; }; if ([NSThread isMainThread]) { block(); } else { tnl_dispatch_async_autoreleasing(dispatch_get_main_queue(), block); } return identifier; } - (void)endBackgroundTaskWithIdentifier:(TNLBackgroundTaskIdentifier)identifier { SEL cmdSelector = _cmd; tnl_dispatch_async_autoreleasing(dispatch_get_main_queue(), ^{ if (identifier == TNLBackgroundTaskInvalid) { TNLLogWarning(@"Cannot call [%@ %@] with invalid identifier!", NSStringFromClass([self class]), NSStringFromSelector(cmdSelector)); return; } TNLBackgroundTaskHandleInternal *handle = self->_runningBackgroundTasks[@(identifier)]; if (!handle) { TNLLogWarning(@"[%@ %@%tu] Background Task Identifier was not started or already ended!", NSStringFromClass([self class]), NSStringFromSelector(cmdSelector), identifier); return; } [self->_runningBackgroundTasks removeObjectForKey:@(identifier)]; [self _main_cleanUpSharedBackgroundTaskIfNecessary]; }); } - (void)_main_ensureSharedBackgroundTask TNL_OBJC_DIRECT { #if TARGET_OS_IOS || TARGET_OS_TV UIApplication *sharedUIApplication = TNLDynamicUIApplicationSharedApplication(); if (sharedUIApplication) { if (UIBackgroundTaskInvalid == _sharedUIApplicationBackgroundTaskIdentifier) { _sharedUIApplicationBackgroundTaskIdentifier = [sharedUIApplication beginBackgroundTaskWithName:@"tnl.global.shared.bg.task" expirationHandler:^{ [self _handleExpiration]; }]; } } #endif // IOS + TV } - (void)_main_cleanUpSharedBackgroundTaskIfNecessary TNL_OBJC_DIRECT { #if TARGET_OS_IOS || TARGET_OS_TV UIApplication *sharedUIApplication = TNLDynamicUIApplicationSharedApplication(); if (sharedUIApplication) { if (_sharedUIApplicationBackgroundTaskIdentifier != UIBackgroundTaskInvalid && _runningBackgroundTasks.count == 0) { UIBackgroundTaskIdentifier identifier = _sharedUIApplicationBackgroundTaskIdentifier; _sharedUIApplicationBackgroundTaskIdentifier = UIBackgroundTaskInvalid; [sharedUIApplication endBackgroundTask:identifier]; } } #endif // IOS + TV } #if TARGET_OS_IOS || TARGET_OS_TV - (void)_handleExpiration TNL_OBJC_DIRECT { UIApplication *sharedUIApplication = TNLDynamicUIApplicationSharedApplication(); if (sharedUIApplication) { dispatch_block_t block = ^{ for (TNLBackgroundTaskHandleInternal *handle in self->_runningBackgroundTasks.allValues) { TNLLogWarning(@"Background Task Expired! '%@'", handle.name ?: @"???"); if (handle.expirationHandler) { handle.expirationHandler(); } } [self->_runningBackgroundTasks removeAllObjects]; [self _main_cleanUpSharedBackgroundTaskIfNecessary]; }; if ([NSThread isMainThread]) { block(); } else { tnl_dispatch_async_autoreleasing(dispatch_get_main_queue(), block); } } } #endif // IOS + TV @end @implementation TNLGlobalConfiguration (Debugging) - (NSArray<TNLRequestOperation *> *)allRequestOperations { NSArray<NSOperation *> *ops = [TNLRequestOperationQueue globalRequestOperationQueue].operations; NSMutableArray<TNLRequestOperation *> *tnlOps = [[NSMutableArray alloc] init]; for (NSOperation *op in ops) { if ([op isKindOfClass:[TNLRequestOperation class]]) { [tnlOps addObject:(id)op]; } } return [tnlOps copy]; } @end @implementation TNLBackgroundTaskHandleInternal - (instancetype)initWithTaskIdentifier:(TNLBackgroundTaskIdentifier)taskId name:(nullable NSString *)name expirationHandler:(nullable void (^)(void))handler { TNLAssert(TNLBackgroundTaskInvalid != taskId); if (self = [super init]) { _name = [name copy]; _expirationHandler = [handler copy]; } return self; } @end NS_ASSUME_NONNULL_END