TNLExample/TNLXAppDelegate.m (261 lines of code) (raw):

// // TNLXAppDelegate.m // TNLExample // // Created on 7/24/14. // Copyright © 2020 Twitter. All rights reserved. // #import <TwitterNetworkLayer/TwitterNetworkLayer.h> #import "TAPI.h" #import "TNLXAppDelegate.h" #import "TNLXImageSupport.h" #import "TNLXNetworkHeuristicObserver.h" NSString *TNLXCommunicationStatusUpdatedNotification = @"TNLXCommunicationStatusUpdatedNotification"; @interface TNLXAppDelegate () <TNLNetworkObserver, TNLLogger, TNLCommunicationAgentObserver> @end @implementation TNLXAppDelegate { IBOutlet UITabBarController *_tabBarController; TNLCommunicationAgent *_commAgent; NSString *_communicationStatusDescription; NSString *_SCFlagsString; NSString *_statusString; NSString *_carrierName; NSString *_radioTech; } - (BOOL)tnl_canLogWithLevel:(TNLLogLevel)level context:(id)context { return level <= TNLLogLevelDebug; } - (void)tnl_logWithLevel:(TNLLogLevel)level context:(id)context file:(NSString *)file function:(NSString *)function line:(int)line message:(NSString *)message { NSString *levelString = nil; switch (level) { case TNLLogLevelEmergency: case TNLLogLevelAlert: case TNLLogLevelCritical: case TNLLogLevelError: levelString = @"ERR"; break; case TNLLogLevelWarning: levelString = @"WRN"; break; case TNLLogLevelNotice: case TNLLogLevelInformation: levelString = @"INF"; break; case TNLLogLevelDebug: levelString = @"DBG"; break; } NSLog(@"[%@]: %@", levelString, message); } - (BOOL)tnl_shouldRedactHTTPHeaderField:(nonnull NSString *)headerField { if ([headerField isEqualToString:@"Authorization"]) { return YES; } return NO; } - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [[NSURLCache sharedURLCache] removeAllCachedResponses]; // Set up logging [TNLGlobalConfiguration sharedInstance].logger = self; // Prepare network "business" observer [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkingDidChange:) name:TNLNetworkExecutingNetworkConnectionsDidUpdateNotification object:nil]; // Prepare global settings _commAgent = [[TNLCommunicationAgent alloc] initWithInternetReachabilityHost:@"api.twitter.com"]; (void)[TNLXNetworkHeuristicObserver sharedInstance]; [[TNLGlobalConfiguration sharedInstance] addNetworkObserver:self]; [[TNLGlobalConfiguration sharedInstance] setAssertsEnabled:YES]; [[TNLGlobalConfiguration sharedInstance] setMetricProvidingCommunicationAgent:_commAgent]; [_commAgent addObserver:self]; // Prepare Twitter API TAPIClient *client = [TAPIClient sharedInstance]; NSString *consumerKey = [[NSBundle mainBundle] infoDictionary][@"tnlx_oauth_consumer_key"]; NSString *consumerSecret = [[NSBundle mainBundle] infoDictionary][@"tnlx_oauth_consumer_secret"]; if (consumerKey.length > 0 && consumerSecret.length > 0) { client.oauthConsumerKey = consumerKey; client.oauthConsumerSecret = consumerSecret; } __weak typeof(self) weakSelf = self; client.loginAccessBlock = ^(TAPILoginAccessCompletionBlock completion) { __strong typeof(self) strongSelf = weakSelf; if (!strongSelf) { completion(nil, nil); } else { [strongSelf promptForTwitterAPIAccess:completion]; } }; [self.window makeKeyAndVisible]; return YES; } - (void)application:(nonnull UIApplication *)application handleEventsForBackgroundURLSession:(nonnull NSString *)identifier completionHandler:(nonnull void (^)(void))completionHandler { NSLog(@"%@ %@", NSStringFromSelector(_cmd), identifier); if (![TNLRequestOperationQueue handleBackgroundURLSessionEvents:identifier completionHandler:completionHandler]) { completionHandler(); } } - (void)applicationDidBecomeActive:(UIApplication *)application { #if !TARGET_OS_MACCATALYST if (@available(iOS 13, *)) { } else { application.networkActivityIndicatorVisible = [TNLNetwork hasExecutingNetworkConnections]; } #endif } - (void)tnl_requestOperation:(TNLRequestOperation *)op didCompleteWithResponse:(TNLResponse *)response { TNLAttemptMetrics *lastAttemptMetrics = response.metrics.attemptMetrics.lastObject; TNLAttemptMetaData *metaData = lastAttemptMetrics.metaData; int64_t downloadByteCount = metaData.layer8BodyBytesReceived; NSTimeInterval duration = response.metrics.totalDuration; BOOL isCached = response.info.source == TNLResponseSourceLocalCache; BOOL errorWasEncountered = response.operationError != nil; if (downloadByteCount < 0 || duration <= 0 || isCached) { return; } double bytes = downloadByteCount; double bps = bytes/duration; static NSByteCountFormatter *bpsFormatter; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ bpsFormatter = [[NSByteCountFormatter alloc] init]; bpsFormatter.countStyle = NSByteCountFormatterCountStyleBinary; bpsFormatter.allowedUnits = NSByteCountFormatterUseKB; bpsFormatter.zeroPadsFractionDigits = YES; bpsFormatter.adaptive = YES; }); NSLog(@"Bandwidth - %@ / %.2fs = %@ps%@", [bpsFormatter stringFromByteCount:downloadByteCount], duration, isnan(bps) ?@"NaN B" : [bpsFormatter stringFromByteCount:(long long)bps], errorWasEncountered ? @" DNF!" : @""); if ([response isKindOfClass:[TAPIResponse class]]) { NSError *error = [(TAPIResponse *)response anyError]; if ([error.domain isEqualToString:TNLErrorDomain]) { if (error.code == TNLErrorCodeRequestOperationFailedToAuthorizeRequest) { error = error.userInfo[NSUnderlyingErrorKey]; } } if ([error.domain isEqualToString:TAPIOperationErrorDomain]) { if (error.code == TAPIOperationErrorCodeMissingAccessCredentials) { [self warnThatAccessCredentialsAreMissing]; } else if (error.code == TAPIOperationErrorCodeMissingConsumerCredentials) { [self warnThatConsumerCredentialsAreMissing]; } } } } - (void)networkingDidChange:(NSNotification *)note { assert([NSThread isMainThread]); #if !TARGET_OS_MACCATALYST BOOL on = [note.userInfo[TNLNetworkExecutingNetworkConnectionsExecutingKey] boolValue]; [UIApplication sharedApplication].networkActivityIndicatorVisible = on; #endif } - (void)promptForTwitterAPIAccess:(TAPILoginAccessCompletionBlock)completion { // Just load from bundle - a proper app would have a way for users to log in NSString *token = [[NSBundle mainBundle] infoDictionary][@"tnlx_oauth_access_token"]; NSString *secret = [[NSBundle mainBundle] infoDictionary][@"tnlx_oauth_access_secret"]; if (token && secret) { completion(token, secret); return; } completion(nil, nil); } - (void)warnThatConsumerCredentialsAreMissing { if (![NSThread isMainThread]) { [self performSelectorOnMainThread:_cmd withObject:nil waitUntilDone:NO]; return; } UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Missing Consumer Key & Secret" message:@"Twitter API credentials can be obtained by going to apps.twitter.com.\nPut them in TNLExample-Info.plist under `tnlx_oauth_consumer_key` and `tnlx_oauth_consumer_secret`." preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:NULL]]; [self.window.rootViewController presentViewController:alert animated:YES completion:NULL]; } - (void)warnThatAccessCredentialsAreMissing { if (![NSThread isMainThread]) { [self performSelectorOnMainThread:_cmd withObject:nil waitUntilDone:NO]; return; } UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Enter Access Token & Secret" message:@"Twitter API credentials can be obtained by going to apps.twitter.com.\nPut them in TNLExample-Info.plist under `tnlx_oauth_access_token` and `tnlx_oauth_access_secret`." preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:NULL]]; [self.window.rootViewController presentViewController:alert animated:YES completion:NULL]; } #pragma mark TNLCommunicationAgentObserver - (void)tnl_communicationAgent:(TNLCommunicationAgent *)agent didRegisterObserverWithInitialReachabilityFlags:(TNLNetworkReachabilityFlags)flags status:(TNLNetworkReachabilityStatus)status carrierInfo:(nullable id<TNLCarrierInfo>)info WWANRadioAccessTechnology:(nullable NSString *)radioTech captivePortalStatus:(TNLCaptivePortalStatus)captivePortalStatus { _SCFlagsString = TNLDebugStringFromNetworkReachabilityFlags(flags); _statusString = TNLNetworkReachabilityStatusToString(status) ?: @"<null>"; _carrierName = info.carrierName ?: @"<null>"; _radioTech = [radioTech stringByReplacingOccurrencesOfString:@"CTRadioAccessTechnology" withString:@""] ?: @"<null>"; NSDictionary *logInfo = @{ @"SC_flags" : _SCFlagsString, @"status" : _statusString, @"carrier" : info ?: (id)@"<null>", @"radioTech" : _radioTech, }; NSLog(@"did register: %@", logInfo); [self _updateCommunicationStatusDescription]; } - (void)tnl_communicationAgent:(TNLCommunicationAgent *)agent didUpdateReachabilityFromPreviousFlags:(TNLNetworkReachabilityFlags)oldFlags previousStatus:(TNLNetworkReachabilityStatus)oldStatus toCurrentFlags:(TNLNetworkReachabilityFlags)newFlags currentStatus:(TNLNetworkReachabilityStatus)newStatus { _SCFlagsString = TNLDebugStringFromNetworkReachabilityFlags(newFlags); _statusString = TNLNetworkReachabilityStatusToString(newStatus) ?: @"<null>"; NSDictionary *logInfo = @{ @"SC_flags_old" : TNLDebugStringFromNetworkReachabilityFlags(oldFlags), @"SC_flags_new" : _SCFlagsString, @"status_old" : TNLNetworkReachabilityStatusToString(oldStatus) ?: @"<null>", @"status_new" : _statusString, }; NSLog(@"did update reachability: %@", logInfo); [self _updateCommunicationStatusDescription]; } - (void)tnl_communicationAgent:(TNLCommunicationAgent *)agent didUpdateCarrierFromPreviousInfo:(nullable id<TNLCarrierInfo>)oldInfo toCurrentInfo:(nullable id<TNLCarrierInfo>)newInfo { _carrierName = newInfo.carrierName ?: @"<null>"; NSDictionary *logInfo = @{ @"carrier_old" : oldInfo ?: (id)@"<null>", @"carrier_new" : newInfo ?: (id)@"<null>", }; NSLog(@"did update carrier: %@", logInfo); [self _updateCommunicationStatusDescription]; } - (void)tnl_communicationAgent:(TNLCommunicationAgent *)agent didUpdateWWANRadioAccessTechnologyFromPreviousTech:(nullable NSString *)oldTech toCurrentTech:(nullable NSString *)newTech { _radioTech = [newTech stringByReplacingOccurrencesOfString:@"CTRadioAccessTechnology" withString:@""] ?: @"<null>"; NSDictionary *logInfo = @{ @"radioTech_old" : oldTech ?: @"null", @"radioTech_new" : newTech ?: @"null", }; NSLog(@"did update radio tech: %@", logInfo); [self _updateCommunicationStatusDescription]; } - (void)_updateCommunicationStatusDescription { _communicationStatusDescription = [NSString stringWithFormat:@"%@, %@, %@,\n%@", _radioTech, _carrierName, _statusString, _SCFlagsString]; [[NSNotificationCenter defaultCenter] postNotificationName:TNLXCommunicationStatusUpdatedNotification object:_commAgent userInfo:@{ @"description" : _communicationStatusDescription }]; } - (NSString *)communicationStatusDescription { return _communicationStatusDescription; } @end