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