TNLExample/TNLXPlaygroundViewController.m (557 lines of code) (raw):

// // TNLXThirdViewController.m // TwitterNetworkLayer // // Created on 8/23/14. // Copyright © 2020 Twitter. All rights reserved. // #import <TwitterNetworkLayer/TwitterNetworkLayer.h> #import "TAPI.h" #import "TNLXAppDelegate.h" #import "TNLXMultipartFormData.h" #import "TNLXPlaygroundViewController.h" @import UIKit; #define DOWNLOAD_URL @"http://speedtest.reliableservers.com/100MBtest.bin" @interface Dummy : NSObject - (id)URLSessionTaskOperation; - (id)URLSession; @end #define TWEET_ID 523495208437424128LL // #define TIMEOUT_VALUE (5) typedef void(^TNLXCompletionBlock)(TNLRequestOperation *op, TNLResponse *response); static /*not const*/ BOOL USE_CALLBACK_REDIRECT_POLICIES = YES; typedef NS_ENUM(NSInteger, TNLXRedirectTestPolicy) { TNLXRedirectTestPolicyCallbackNoRedirect, TNLXRedirectTestPolicyCallbackShortendedURLRedirect, TNLXRedirectTestPolicyCallbackAllRedirects, TNLXRedirectTestPolicyAutoNoRedirect, TNLXRedirectTestPolicyAutoRedirect, TNLXRedirectTestPolicyAutoRedirectCancelAfter1, }; @interface TNLXRedirectTestObject : NSObject <TNLRequestDelegate> @property (nonatomic, copy) TNLRequestConfiguration *config; @property (nonatomic) TNLXRedirectTestPolicy redirectPolicy; @property (nonatomic) NSUInteger redirectCount; @property (nonatomic, copy) TNLXCompletionBlock completionBlock; @property (nonatomic) id<TNLRequest> request; - (void)start; @end @interface TNLXPlaygroundViewController () <TNLRequestDelegate, NSURLConnectionDataDelegate> { IBOutlet UIButton *_multiSubmitButton; IBOutlet UIButton *_favButton; IBOutlet UIButton *_unfavButton; IBOutlet UIButton *_loadFileButton; IBOutlet UIButton *_cancelFileButton; IBOutlet UIButton *_httpTestButton; IBOutlet UIButton *_httpsTwitterTestButton; IBOutlet UIButton *_goWithRedirectsButton; IBOutlet UIButton *_goWithoutRedirectsButton; IBOutlet UIButton *_goWithOnlyShortlinkRedirects; IBOutlet UIProgressView *_multiProgressView; IBOutlet UIProgressView *_fileProgressView; IBOutlet UITextField *_httpTestField; IBOutlet UITextField *_customURLField; IBOutlet UILabel *_commsStatusField; TNLRequestOperationQueue *_queue; TNLRequestOperation *_currentOperation; NSString *_fileDestination; TNLRequestOperation *_fileDownloadOp; NSURLConnection *_fileDownloadConnection; NSURLResponse *_fileDownloadConnectionResponse; long long _fileDownloadConnectionLoadedBytes; NSString *_sessionId; NSUInteger _taskId; } @end @implementation TNLXPlaygroundViewController - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) { [self prep]; } return self; } - (instancetype)initWithCoder:(NSCoder *)aDecoder { if (self = [super initWithCoder:aDecoder]) { [self prep]; } return self; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)prep { if (_fileDestination) { return; } _fileDestination = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"DownloadFolder/LargeFileDownload.bin"]; NSFileManager *fm = [NSFileManager defaultManager]; [fm createDirectoryAtPath:[_fileDestination stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:NULL]; _queue = [[TNLRequestOperationQueue alloc] initWithIdentifier:NSStringFromClass([self class]).lowercaseString]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(backgroundRequestDidDownloadNotification:) name:TNLBackgroundRequestOperationDidCompleteNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; } - (void)viewDidLoad { [super viewDidLoad]; _multiSubmitButton.layer.borderWidth = 1; _multiSubmitButton.layer.borderColor = _multiSubmitButton.tintColor.CGColor; _favButton.layer.borderWidth = 1; _favButton.layer.borderColor = _favButton.tintColor.CGColor; _unfavButton.layer.borderWidth = 1; _unfavButton.layer.borderColor = _unfavButton.tintColor.CGColor; _loadFileButton.layer.borderWidth = 1; _loadFileButton.layer.borderColor = _loadFileButton.tintColor.CGColor; _cancelFileButton.layer.borderWidth = 1; _cancelFileButton.layer.borderColor = _cancelFileButton.tintColor.CGColor; _httpTestButton.layer.borderWidth = 1; _httpTestButton.layer.borderColor = _httpTestButton.tintColor.CGColor; _httpsTwitterTestButton.layer.borderWidth = 1; _httpsTwitterTestButton.layer.borderColor = _httpsTwitterTestButton.tintColor.CGColor; _goWithOnlyShortlinkRedirects.layer.borderWidth = 1; _goWithOnlyShortlinkRedirects.layer.borderColor = _goWithOnlyShortlinkRedirects.tintColor.CGColor; _goWithoutRedirectsButton.layer.borderWidth = 1; _goWithoutRedirectsButton.layer.borderColor = _goWithoutRedirectsButton.tintColor.CGColor; _goWithRedirectsButton.layer.borderWidth = 1; _goWithRedirectsButton.layer.borderColor = _goWithRedirectsButton.tintColor.CGColor; _multiProgressView.progress = 0; _fileProgressView.progress = 0; if ([[NSFileManager defaultManager] fileExistsAtPath:_fileDestination]) { _fileProgressView.progress = 1; _fileProgressView.tintColor = [UIColor greenColor]; _loadFileButton.enabled = NO; _cancelFileButton.enabled = YES; } else { _fileProgressView.progress = 0; _fileProgressView.tintColor = [UIColor blueColor]; _loadFileButton.enabled = YES; _cancelFileButton.enabled = NO; } [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_communicationStatusDidUpdate:) name:TNLXCommunicationStatusUpdatedNotification object:nil]; _commsStatusField.text = APP_DELEGATE.communicationStatusDescription; } - (void)_communicationStatusDidUpdate:(NSNotification *)note { NSString *status = note.userInfo[@"description"] ?: APP_DELEGATE.communicationStatusDescription; _commsStatusField.text = status; } - (IBAction)goWithRedirects:(id)sender { [self goWithRedirectPolicy:USE_CALLBACK_REDIRECT_POLICIES ? TNLXRedirectTestPolicyCallbackAllRedirects : TNLXRedirectTestPolicyAutoRedirect]; } - (IBAction)goWithNoRedirects:(id)sender { [self goWithRedirectPolicy:USE_CALLBACK_REDIRECT_POLICIES ? TNLXRedirectTestPolicyCallbackNoRedirect : TNLXRedirectTestPolicyAutoNoRedirect]; } - (IBAction)goWithOnlyShortURLRedirects:(id)sender { [self goWithRedirectPolicy:USE_CALLBACK_REDIRECT_POLICIES ? TNLXRedirectTestPolicyCallbackShortendedURLRedirect : TNLXRedirectTestPolicyAutoRedirectCancelAfter1]; } - (void)goWithRedirectPolicy:(TNLXRedirectTestPolicy)redirectPolicy { _goWithOnlyShortlinkRedirects.enabled = NO; _goWithoutRedirectsButton.enabled = NO; _goWithRedirectsButton.enabled = NO; TNLXRedirectTestObject *testObj = [[TNLXRedirectTestObject alloc] init]; testObj.redirectPolicy = redirectPolicy; testObj.request = [TNLHTTPRequest GETRequestWithURL:[NSURL URLWithString:_customURLField.text] HTTPHeaderFields:nil]; testObj.completionBlock = ^(TNLRequestOperation *operation, TNLResponse *response) { self->_goWithOnlyShortlinkRedirects.enabled = YES; self->_goWithoutRedirectsButton.enabled = YES; self->_goWithRedirectsButton.enabled = YES; NSString *message = [NSString stringWithFormat:@"Redirect Count: %tu\nHeaders: %@\nMetrics: %@", response.metrics.redirectCount, response.info.allHTTPHeaderFields, response.metrics]; UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:@"Response" message:message preferredStyle:UIAlertControllerStyleAlert]; [alertVC addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil]]; [self presentViewController:alertVC animated:YES completion:nil]; }; [testObj start]; } - (void)fileDownloadComplete:(BOOL)success { [_fileProgressView setProgress:1.0 animated:YES]; _fileProgressView.progressTintColor = success ? [UIColor greenColor] : [UIColor redColor]; _loadFileButton.enabled = !success; _cancelFileButton.enabled = success; _fileDownloadOp = nil; } - (void)fileDownloadStarting { [_fileProgressView setProgress:0 animated:NO]; _fileProgressView.progressTintColor = [UIColor blueColor]; _loadFileButton.enabled = NO; _cancelFileButton.enabled = YES; } - (IBAction)fileClear:(id)sender { [[NSFileManager defaultManager] removeItemAtPath:_fileDestination error:NULL]; [_fileProgressView setProgress:0 animated:NO]; _fileProgressView.progressTintColor = [UIColor blueColor]; _loadFileButton.enabled = YES; _cancelFileButton.enabled = NO; [_fileDownloadOp cancelWithSource:@"Clear"]; _fileDownloadOp = nil; [_fileDownloadConnection cancel]; _fileDownloadConnection = nil; _fileDownloadConnectionResponse = nil; _fileDownloadConnectionLoadedBytes = 0; } - (IBAction)fileDownload:(id)sender { assert(!_fileDownloadOp); TNLMutableRequestConfiguration * config = [TNLMutableRequestConfiguration defaultConfiguration]; config.idleTimeout = 10; config.attemptTimeout = NSTimeIntervalSince1970; config.operationTimeout = NSTimeIntervalSince1970; // config.executionMode = TNLRequestExecutionModeBackground; // config.responseDataConsumptionMode = TNLResponseDataConsumptionModeSaveToDisk; // config.allowsCellularAccess = NO; _fileDownloadOp = [TNLRequestOperation operationWithURL:[NSURL URLWithString:DOWNLOAD_URL] configuration:config delegate:self]; [self fileDownloadStarting]; [_queue enqueueRequestOperation:_fileDownloadOp]; } - (IBAction)fileDownload_NSURLConnection_disabled:(id)sender { assert(!_fileDownloadConnection); NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; request.URL = [NSURL URLWithString:DOWNLOAD_URL]; request.timeoutInterval = 10; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" _fileDownloadConnection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]; #pragma clang diagnostic pop [_fileDownloadConnection setDelegateQueue:[NSOperationQueue mainQueue]]; [self fileDownloadStarting]; [_fileDownloadConnection start]; } - (void)didEnterBackground:(NSNotification *)note { // if (_fileDownloadOp) { // NSUInteger *pointer = NULL; // NSUInteger i = *pointer; // i++; // } NSLog(@"Background Timer Remaining: %@s", @([UIApplication sharedApplication].backgroundTimeRemaining)); } - (void)backgroundRequestDidDownloadNotification:(NSNotification *)note { if (![NSThread isMainThread]) { [self performSelectorOnMainThread:_cmd withObject:note waitUntilDone:NO]; return; } NSURLRequest *request = note.userInfo[TNLBackgroundRequestURLRequestKey]; TNLResponse *response = note.userInfo[TNLBackgroundRequestResponseKey]; NSUInteger taskId = [(NSNumber *)note.userInfo[TNLBackgroundRequestURLSessionTaskIdentifierKey] unsignedIntegerValue]; NSString *sessionId = note.userInfo[TNLBackgroundRequestURLSessionConfigurationIdentifierKey]; NSLog(@"[BG_DOWNLOAD] - %@", note); if (taskId == _taskId) { if ([sessionId isEqualToString:_sessionId]) { NSURL *originalURL = _fileDownloadOp.hydratedRequest.URL ?: ([_fileDownloadOp.originalRequest respondsToSelector:@selector(URL)] ? [(id)_fileDownloadOp.originalRequest URL] : nil); if ([request.URL.absoluteString isEqualToString:originalURL.absoluteString]) { NSError *error; BOOL success = response.info.statusCode == 200 && [response.info.temporarySavedFile moveToPath:_fileDestination error:&error]; if (error) { NSLog(@"%@ %@", NSStringFromSelector(_cmd), error); } [self fileDownloadComplete:success]; } } } } - (void)complete:(BOOL)success { [_multiProgressView setProgress:1.0 animated:YES]; _multiProgressView.progressTintColor = success ? [UIColor greenColor] : [UIColor redColor]; _multiSubmitButton.enabled = YES; _favButton.enabled = YES; _unfavButton.enabled = YES; _currentOperation = nil; } - (void)willStart { [_multiProgressView setProgress:0 animated:NO]; _unfavButton.enabled = NO; _favButton.enabled = NO; _multiSubmitButton.enabled = NO; _multiProgressView.progressTintColor = [UIColor blueColor]; } - (IBAction)multiSubmit:(UIButton *)sender { [self willStart]; UIImage *image = [UIImage imageNamed:@"first"]; NSData *jpegData = UIImageJPEGRepresentation(image, .05f); TAPIUploadMediaRequest *request = [[TAPIUploadMediaRequest alloc] initWithImageData:jpegData]; _currentOperation = [[TAPIClient sharedInstance] startRequest:request completion:^(TAPIUploadMediaResponse *response) { [self complete:response.didSucceed]; }]; } - (IBAction)fav:(id)sender { [self willStart]; TAPIClient *client = [TAPIClient sharedInstance]; TAPIFavoriteCreateRequest *favRequest = [[TAPIFavoriteCreateRequest alloc] initWithStatusID:TWEET_ID]; _currentOperation = [client startRequest:favRequest completion:^(TAPIFavoriteResponse *response) { [self complete:response.didSucceed]; }]; } - (IBAction)unfav:(id)sender { [self willStart]; TAPIClient *client = [TAPIClient sharedInstance]; TAPIFavoriteDestroyRequest *unfavRequest = [[TAPIFavoriteDestroyRequest alloc] initWithStatusID:TWEET_ID]; _currentOperation = [client startRequest:unfavRequest completion:^(TAPIFavoriteResponse *response) { [self complete:response.didSucceed]; }]; } - (IBAction)kill:(id)sender { NSUInteger *pointer = NULL; NSUInteger i = *pointer; i++; } - (IBAction)testHTTP:(id)sender { [_httpTestField resignFirstResponder]; _httpTestButton.enabled = NO; NSInteger code = [_httpTestField.text integerValue]; NSString *URLString = [NSString stringWithFormat:@"http://httpbin.org/status/%td", code]; TNLRequestOperation *op = [TNLRequestOperation operationWithURL:[NSURL URLWithString:URLString] completion:^(TNLRequestOperation *innerOp, TNLResponse *response) { self->_httpTestButton.enabled = YES; NSString *message = [NSString stringWithFormat:@"Status Code: %td", response.info.statusCode]; UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:@"HTTP Response" message:message preferredStyle:UIAlertControllerStyleAlert]; [alertVC addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil]]; [self presentViewController:alertVC animated:YES completion:nil]; }]; [[TNLRequestOperationQueue defaultOperationQueue] enqueueRequestOperation:op]; } - (IBAction)testHTTPSTwitter:(id)sender { [self testTwitter]; } - (void)testTwitter { _httpsTwitterTestButton.enabled = NO; TNLMutableRequestConfiguration *config = [TNLMutableRequestConfiguration defaultConfiguration]; // static NSInteger swap = 0; // swap++; // switch (swap % 4) { // case 1: // config.URLCache = [NSURLCache tnl_sharedURLCacheProxy]; // break; // case 2: // config.URLCache = [NSURLCache tnl_impotentURLCache]; // break; // case 3: // config.URLCache = [NSURLCache sharedURLCache]; // break; // case 0: // default: // config.URLCache = nil; // break; // } NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://www.twitter.com"]]; TNLXCompletionBlock block = ^(TNLRequestOperation *innerOp, TNLResponse *response) { assert([NSThread isMainThread]); self->_httpsTwitterTestButton.enabled = YES; NSString *message = [NSString stringWithFormat:@"Metrics: %@", response.metrics]; UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:@"HTTP Response" message:message preferredStyle:UIAlertControllerStyleAlert]; [alertVC addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleCancel handler:nil]]; [self presentViewController:alertVC animated:YES completion:nil]; NSLog(@"(%zi) (CL: %@) %@ %@", response.info.statusCode, [response.info valueForResponseHeaderField:@"Content-Length"], response.operationError ?: @"", response.metrics); // TLSLogDebug(NSStringFromClass([self class]), @"Headers: %@", response.info.allHTTPHeaderFields); }; TNLXRedirectTestObject *testObj = [[TNLXRedirectTestObject alloc] init]; testObj.request = request; testObj.redirectPolicy = 0; // to test without redirects set this to -1 testObj.completionBlock = block; testObj.config = config; [testObj start]; } - (void)tnl_requestOperation:(TNLRequestOperation *)op hydrateRequest:(id<TNLRequest>)request completion:(TNLRequestHydrateCompletionBlock)complete { if (op == _fileDownloadOp) { complete(nil, nil); return; } assert([request isKindOfClass:[TNLXMultipartFormDataRequest class]]); #if TIMEOUT_VALUE sleep(TIMEOUT_VALUE * 2); #endif NSError *error = nil; request = [(TNLXMultipartFormDataRequest *)request generateRequestWithUploadFormat:TNLXMultipartFormDataUploadFormatFile error:&error]; complete(request, error); } - (void)tnl_requestOperation:(TNLRequestOperation *)op didTransitionFromState:(TNLRequestOperationState)oldState toState:(TNLRequestOperationState)newState { if (op == _fileDownloadOp) { // if (newState == TNLRequestOperationStateRunning) { // [(NSURLSession *)[(id)[(id)op URLSessionTaskOperation] URLSession] getTasksWithCompletionHandler:^(NSArray *nt, NSArray *ut, NSArray *dt) { // NSLog(@"\ndata: %@\nupload: %@\ndownload: %@", nt, ut, dt); // }]; // } return; } if (newState == TNLRequestOperationStateRunning) { #if TIMEOUT_VALUE sleep(TIMEOUT_VALUE * 2); #endif } } - (void)tnl_requestOperation:(TNLRequestOperation *)op didStartRequestWithURLSessionTaskIdentifier:(NSUInteger)taskId URLSessionConfigurationIdentifier:(NSString *)configId URLSessionSharedContainerIdentifier:(NSString *)sharedContainerIdentifier isBackgroundRequest:(BOOL)isBackgroundRequest { dispatch_async(dispatch_get_main_queue(), ^{ if (op != self->_fileDownloadOp) { return; } self->_sessionId = configId; self->_taskId = taskId; }); } - (void)tnl_requestOperation:(TNLRequestOperation *)op didCompleteWithResponse:(TNLResponse *)response { assert([NSThread isMainThread]); NSURL *originalURL = [op.originalRequest respondsToSelector:@selector(URL)] ? [(id)op.originalRequest URL] : nil; if ([originalURL.absoluteString isEqualToString:DOWNLOAD_URL]) { NSLog(@"[BG_DOWNLOAD] - %@ %@", op.originalRequest, response); UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:@"BG Download" message:[NSString stringWithFormat:@"%@", response] preferredStyle:UIAlertControllerStyleAlert]; [alertVC addAction:[UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleCancel handler:nil]]; [self presentViewController:alertVC animated:YES completion:nil]; return; } NSLog(@"%@ %@ %@ %@", NSStringFromSelector(_cmd), op, response, response.metrics); [self complete:!response.operationError && 200 == response.info.statusCode]; } - (void)tnl_requestOperation:(TNLRequestOperation *)op didUpdateUploadProgress:(float)uploadProgress { dispatch_async(dispatch_get_main_queue(), ^{ if (op == self->_fileDownloadOp) { return; } [self->_multiProgressView setProgress:uploadProgress animated:YES]; }); } - (void)tnl_requestOperation:(TNLRequestOperation *)op didUpdateDownloadProgress:(float)downloadProgress { dispatch_async(dispatch_get_main_queue(), ^{ if (op != self->_fileDownloadOp) { return; } [self->_fileProgressView setProgress:downloadProgress animated:YES]; }); } #pragma mark Connection - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { _fileDownloadConnectionLoadedBytes += data.length; _fileProgressView.progress = (float)((double)_fileDownloadConnectionLoadedBytes / (double)_fileDownloadConnectionResponse.expectedContentLength); } - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { _fileDownloadConnectionResponse = response; } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { [self _connection:connection didFinishedWithResponse:_fileDownloadConnectionResponse error:nil]; } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { [self _connection:connection didFinishedWithResponse:_fileDownloadConnectionResponse error:error]; } - (void)_connection:(NSURLConnection *)connection didFinishedWithResponse:(NSURLResponse *)response error:(NSError *)error { NSLog(@"[BG_DOWNLOAD] - %@ %@", connection.originalRequest, response); NSMutableDictionary *info = [[NSMutableDictionary alloc] init]; if (response) { info[@"response"] = response; } if (error) { info[@"error"] = error; } info[@"URL"] = connection.originalRequest.URL; UIAlertController *alertVC = [UIAlertController alertControllerWithTitle:@"BG Download" message:[NSString stringWithFormat:@"%@", info] preferredStyle:UIAlertControllerStyleAlert]; [alertVC addAction:[UIAlertAction actionWithTitle:@"Close" style:UIAlertActionStyleCancel handler:nil]]; [self presentViewController:alertVC animated:YES completion:nil]; } @end @implementation TNLXRedirectTestObject - (void)start { TNLMutableRequestConfiguration *config = [self.config mutableCopy] ?: [TNLMutableRequestConfiguration defaultConfiguration]; switch (self.redirectPolicy) { case TNLXRedirectTestPolicyAutoNoRedirect: config.redirectPolicy = TNLRequestRedirectPolicyDontRedirect; break; case TNLXRedirectTestPolicyAutoRedirect: case TNLXRedirectTestPolicyAutoRedirectCancelAfter1: config.redirectPolicy = TNLRequestRedirectPolicyDoRedirect; break; case TNLXRedirectTestPolicyCallbackNoRedirect: case TNLXRedirectTestPolicyCallbackAllRedirects: case TNLXRedirectTestPolicyCallbackShortendedURLRedirect: config.redirectPolicy = TNLRequestRedirectPolicyUseCallback; break; } TNLRequestOperation *op = [TNLRequestOperation operationWithRequest:self.request configuration:config delegate:self]; op.context = self; [[TNLRequestOperationQueue defaultOperationQueue] enqueueRequestOperation:op]; } - (void)tnl_requestOperation:(TNLRequestOperation *)op willRedirectFromRequest:(NSURLRequest<TNLRequest> *)fromRequest withResponse:(NSHTTPURLResponse *)response toRequest:(NSURLRequest<TNLRequest> *)providedRequest completion:(TNLRequestRedirectCompletionBlock)completionBlock { NSURLRequest *toRequest = providedRequest; TNLXRedirectTestPolicy policy = self.redirectPolicy; if (policy == TNLXRedirectTestPolicyCallbackNoRedirect) { toRequest = nil; } else if (policy == TNLXRedirectTestPolicyCallbackShortendedURLRedirect) { if (toRequest.URL.host.length > 8) { toRequest = nil; } else { NSArray *components = toRequest.URL.pathComponents; if (components.count > 2) { toRequest = nil; } else if ([(NSString *)components.lastObject length] > 16) { toRequest = nil; } } } if (toRequest) { NSLog(@"Redirecting to %@", toRequest.URL); } else { NSLog(@"Stopping redirect (was %@)", providedRequest.URL); } completionBlock(toRequest); } - (void)tnl_requestOperation:(TNLRequestOperation *)op didRedirectFromURLRequest:(NSURLRequest *)fromRequest toURLRequest:(NSURLRequest *)toRequest { self.redirectCount++; if (self.redirectCount > 1 && self.redirectPolicy == TNLXRedirectTestPolicyAutoRedirectCancelAfter1) { [op cancelWithSource:@"TNLXRedirectTestPolicyAutoRedirectCancelAfter1"]; } } - (void)tnl_requestOperation:(TNLRequestOperation *)op didCompleteWithResponse:(TNLResponse *)response { TNLXCompletionBlock block = self.completionBlock; self.completionBlock = nil; if (block) { block(op, response); } } @end