TNLExample/TNLXLotsOfRequestsViewController.m (335 lines of code) (raw):

// // TNLXSecondViewController.m // TNLExample // // Created on 7/24/14. // Copyright © 2020 Twitter. All rights reserved. // #import <objc/runtime.h> #import <TwitterNetworkLayer/TwitterNetworkLayer.h> #import "TAPI.h" #import "TNLXImageSupport.h" #import "TNLXLotsOfRequestsViewController.h" @import UIKit; //#define REDUNDANCY (2) TODO #define COUNT (50) //#define SKIP (0) TODO #define VARY_CONFIG 1 #define DOWNLOAD_IMAGES 0 #define NON_DOWNLOAD_IMAGES_CONSUMPTION_MODE TNLResponseDataConsumptionModeNone // TNLResponseDataConsumptionModeChunkToDelegateCallback #define BACKGROUND_IMAGES 0 #define DELAY_BETWEEN_IMAGE_SCHEDULING (0.05) #define MAX_REQUESTS (NSIntegerMax) #define USE_CACHE 0 static const BOOL kUseThumbnail = NO; @interface TNLXLotsOfRequestsViewController () <TNLRequestDelegate> @end @implementation TNLXLotsOfRequestsViewController { UIProgressView *_progressView; IBOutlet UILabel *_totalLabel; IBOutlet UILabel *_activeLabel; IBOutlet UILabel *_completeLabel; IBOutlet UILabel *_okLabel; IBOutlet UILabel *_notOkLabel; IBOutlet UILabel *_errorLabel; IBOutlet UILabel *_bytesLabel; IBOutlet UILabel *_durationLabel; CFAbsoluteTime _startTime; TNLRequestOperation *_initialOp; NSArray<id<TAPIImageEntityModel>> *_results; NSMutableArray<TNLRequestOperation *> *_operations; uint64_t _bytesReceived; uint64_t _bytesTotal; NSUInteger _requestsComplete200; NSUInteger _requestsCompleteNot200; NSUInteger _requestsCompleteError; NSUInteger _requestsActive; NSUInteger _requestsSquashed; NSUInteger _cacheHits; TNLResponse *_longestResponse; TNLMutableRequestConfiguration *_imageConfig; } - (void)viewDidLoad { [super viewDidLoad]; if (!_imageConfig) { _imageConfig = [[TNLMutableRequestConfiguration alloc] init]; _imageConfig.contributeToExecutingNetworkConnectionsCount = YES; _imageConfig.discretionary = YES; _imageConfig.networkServiceType = NSURLNetworkServiceTypeBackground; _imageConfig.URLCache = (USE_CACHE) ? [NSURLCache tnl_sharedURLCacheProxy] : nil; _imageConfig.protocolOptions = 0; _imageConfig.operationTimeout = 60; _imageConfig.attemptTimeout = 50; _imageConfig.idleTimeout = 20; #if BACKGROUND_IMAGES _imageConfig.executionMode = TNLRequestExecutionModeBackground; #else _imageConfig.executionMode = TNLRequestExecutionModeInApp; #endif #if DOWNLOAD_IMAGES _imageConfig.responseDataConsumptionMode = TNLResponseDataConsumptionModeSaveToDisk; #else _imageConfig.responseDataConsumptionMode = NON_DOWNLOAD_IMAGES_CONSUMPTION_MODE; #endif } _progressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleBar]; _progressView.frame = CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height); _progressView.center = CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds)); _progressView.transform = CGAffineTransformMakeScale(1.0, self.view.bounds.size.height / _progressView.bounds.size.height); _progressView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin; _progressView.backgroundColor = [UIColor darkTextColor]; [self.view insertSubview:_progressView atIndex:0]; _totalLabel.text = nil; _activeLabel.text = nil; _completeLabel.text = nil; _okLabel.text = nil; _notOkLabel.text = nil; _errorLabel.text = nil; _bytesTotal = NSIntegerMax; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_backgroundDownloadDidComplete:) name:TNLBackgroundRequestOperationDidCompleteNotification object:nil]; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)_backgroundDownloadDidComplete:(NSNotification *)note { NSLog(@"%@", note); } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; NSLog(@"%@", NSStringFromSelector(_cmd)); } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; [self _startInitialLoadIfNeeded]; } - (void)_startInitialLoadIfNeeded { if (!_initialOp && !_results) { _progressView.progress = 0; TAPISearchRequest *request = [[TAPISearchRequest alloc] initWithQuery:@"Xbox"]; _initialOp = [[TAPIClient sharedInstance] startRequest:request delegate:self]; } } - (void)_continueLoadWithNextResultsObject:(id)nextResultsObject { assert(!_initialOp); TAPISearchRequest *request = [[TAPISearchRequest alloc] initWithNextResultsObject:nextResultsObject]; _initialOp = [[TAPIClient sharedInstance] startRequest:request delegate:self]; } - (void)_enqueueRequestWithImageEntity:(id<TAPIImageEntityModel>)image { if (_operations.count >= MAX_REQUESTS) { return; } NSURL *imageURL = TNLXSelectBestImageURL(image, (kUseThumbnail) ? CGSizeMake(1, 1) : CGSizeZero, UIViewContentModeScaleAspectFill); NSURLRequest *urlReq = [NSURLRequest requestWithURL:imageURL]; #if VARY_CONFIG _imageConfig.attemptTimeout = _imageConfig.attemptTimeout+1; #endif TNLRequestOperation *op = [TNLRequestOperation operationWithRequest:urlReq configuration:_imageConfig delegate:self]; op.priority = TNLPriorityLow; [[TNLRequestOperationQueue defaultOperationQueue] enqueueRequestOperation:op]; [_operations addObject:op]; [self _update]; } - (void)_dequeueRunningRequestWithResponse:(TNLResponse *)response { if (response.operationError) { _requestsCompleteError++; } else if (response.info.statusCode == 200) { _requestsComplete200++; } else { _requestsCompleteNot200++; } const NSTimeInterval duration = response.metrics.totalDuration; const NSTimeInterval oldDuration = _longestResponse.metrics.totalDuration; if (oldDuration < duration) { _longestResponse = response; NSLog(@"New Longest Response: %@ %@", _longestResponse, _longestResponse.metrics); } if (response.info.source == TNLResponseSourceLocalCache) { _cacheHits++; } [self _updateComplete]; } #pragma mark - Updates - (void)_update { _totalLabel.text = [NSString stringWithFormat:@"%tu", _operations.count]; [self _updateComplete]; [self _updateActive]; [self _updateProgress]; } - (void)_updateActive { _activeLabel.text = [NSString stringWithFormat:@"%tu", _requestsActive]; if (_requestsActive == 0) { [_progressView setProgress:1.0 animated:YES]; CFAbsoluteTime duration = CFAbsoluteTimeGetCurrent() - _startTime; _durationLabel.text = [NSString stringWithFormat:@"%.3f seconds", duration]; } else { _durationLabel.text = nil; } } - (void)_updateComplete { _completeLabel.text = [NSString stringWithFormat:@"(cache hit %tu) %tu", _cacheHits, (_requestsCompleteError + _requestsComplete200 + _requestsCompleteNot200)]; _errorLabel.text = [NSString stringWithFormat:@"%tu", _requestsCompleteError]; _okLabel.text = [NSString stringWithFormat:@"%tu", _requestsComplete200]; _notOkLabel.text = [NSString stringWithFormat:@"%tu", _requestsCompleteNot200]; } - (void)_updateProgress { double progressSum = 0; const double progressTarget = MAX(_results.count, (NSUInteger)1); for (TNLRequestOperation *op in _operations) { progressSum += op.downloadProgress; } const float progress = (float)(progressSum / progressTarget); if (_requestsActive != 0) { [_progressView setProgress:progress animated:YES]; } _bytesLabel.text = [NSString stringWithFormat:@"%llu / %llu bytes (%.1f%%)", (unsigned long long)_bytesReceived, (unsigned long long)_bytesTotal, 100.0 * progress]; } - (void)_didSquash { _requestsSquashed++; [self _updateComplete]; } #pragma mark - TNLRequestHydrater - (void)tnl_requestOperation:(TNLRequestOperation *)op hydrateRequest:(id<TNLRequest>)request completion:(TNLRequestHydrateCompletionBlock)complete { if ([request isKindOfClass:[TAPIRequest class]]) { [[TAPIClient sharedInstance] tnl_requestOperation:op hydrateRequest:request completion:complete]; return; } complete(nil, nil); } #pragma mark - TNLRequestAuthorizer - (void)tnl_requestOperation:(TNLRequestOperation *)op authorizeURLRequest:(NSURLRequest *)URLRequest completion:(TNLAuthorizeCompletionBlock)completion { if ([op.originalRequest isKindOfClass:[TAPIRequest class]]) { [[TAPIClient sharedInstance] tnl_requestOperation:op authorizeURLRequest:URLRequest completion:completion]; return; } completion(nil, nil); } #pragma mark - TNLRequestEventHandler - (dispatch_queue_t)tnl_delegateQueueForRequestOperation:(TNLRequestOperation *)op { return dispatch_get_main_queue(); } - (void)tnl_requestOperation:(TNLRequestOperation *)op didTransitionFromState:(TNLRequestOperationState)oldState toState:(TNLRequestOperationState)newState { assert([NSThread isMainThread]); if (op != _initialOp) { if (TNLRequestOperationStateIsActive(newState) && TNLRequestOperationStateIdle == oldState) { NSLog(@"TRANS: %@ %@ -> %@", op, TNLRequestOperationStateToString(oldState), TNLRequestOperationStateToString(newState)); _requestsActive++; [self _updateActive]; } else if (TNLRequestOperationStateIsActive(oldState) && TNLRequestOperationStateIsFinal(newState)) { NSLog(@"TRANS: %@ %@ -> %@", op, TNLRequestOperationStateToString(oldState), TNLRequestOperationStateToString(newState)); if (_requestsActive == 0) { // This never should happen NSLog(@"Underflow of _requestsActive!!"); assert(false); return; } _requestsActive--; [self _updateActive]; } } } - (void)tnl_requestOperation:(TNLRequestOperation *)op didReceiveURLResponse:(NSHTTPURLResponse *)response { if (op != _initialOp) { const long long contentLength = [response tnl_expectedResponseBodySize]; if (contentLength > 0) { _bytesTotal += (uint64_t)contentLength; [self _updateProgress]; } } } #if !DOWNLOAD_IMAGES && NON_DOWNLOAD_IMAGES_CONSUMPTION_MODE == TNLResponseDataConsumptionModeChunkToDelegateCallback - (void)tnl_requestOperation:(TNLRequestOperation *)op didReceiveData:(NSData *)data { assert([NSThread isMainThread]); assert(op != _initialOp); if (op != _initialOp) { _bytesReceived += data.length; [self _updateProgress]; } } #endif - (void)tnl_requestOperation:(TNLRequestOperation *)op didUpdateDownloadProgress:(float)downloadProgress { [self _updateProgress]; } - (void)tnl_requestOperation:(TNLRequestOperation *)op didCompleteWithResponse:(TNLResponse *)response { assert([NSThread isMainThread]); if (op == _initialOp) { TAPISearchResponse *searchResponse = (id)response; _bytesTotal = 0; NSArray *results = [searchResponse imagesFromStatuesRemovingSensitiveImages:YES]; _results = _results ? [_results arrayByAddingObjectsFromArray:results] : results; _initialOp = nil; if (searchResponse.nextResultsObject && _results.count < COUNT) { [self _continueLoadWithNextResultsObject:searchResponse.nextResultsObject]; return; } _startTime = CFAbsoluteTimeGetCurrent(); if (_results.count) { _operations = [[NSMutableArray alloc] initWithCapacity:_results.count]; NSUInteger delay = 0; for (id<TAPIImageEntityModel> result in _results) { [self performSelector:@selector(_enqueueRequestWithImageEntity:) withObject:result afterDelay:(NSTimeInterval)delay * DELAY_BETWEEN_IMAGE_SCHEDULING]; delay++; } } else { _progressView.backgroundColor = [UIColor colorWithRed:(CGFloat)0.75 green:(CGFloat)0.1 blue:(CGFloat)0.1 alpha:(CGFloat)0.0]; } } else { NSString *tempPath = [NSTemporaryDirectory() stringByAppendingPathComponent:[[NSUUID UUID] UUIDString]]; NSError *error; if ([response.info.temporarySavedFile moveToPath:tempPath error:&error]) { NSLog(@"Saved: %@", tempPath); [[NSFileManager defaultManager] removeItemAtPath:tempPath error:NULL]; // keep it clean } else if (response.info.temporarySavedFile) { NSLog(@"Error Saving: %@", error ?: response.operationError ?: [NSString stringWithFormat:@"HTTP %zd", response.info.statusCode]); } #if DOWNLOAD_IMAGES || NON_DOWNLOAD_IMAGES_CONSUMPTION_MODE == TNLResponseDataConsumptionModeStoreInMemory if (200 == response.info.statusCode) { const SInt64 contentLength = response.metrics.attemptMetrics.lastObject.metaData.responseContentLength; if (contentLength) { _bytesReceived += (uint64_t)(op.downloadProgress * (double)contentLength); [self _updateProgress]; } } #endif [self _dequeueRunningRequestWithResponse:response]; } } #pragma mark - UIViewController - (UIStatusBarStyle)preferredStatusBarStyle { return UIStatusBarStyleLightContent; } @end