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