Added support for persistent http connections
More performance tests
Showing
4 changed files
with
187 additions
and
8 deletions
| @@ -331,6 +331,14 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount; | @@ -331,6 +331,14 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount; | ||
| 331 | 331 | ||
| 332 | // The number of times this request has retried (when numberOfTimesToRetryOnTimeout > 0) | 332 | // The number of times this request has retried (when numberOfTimesToRetryOnTimeout > 0) |
| 333 | int retryCount; | 333 | int retryCount; |
| 334 | + | ||
| 335 | + // When YES, requests will keep the connection to the server alive for a while to allow subsequent requests to re-use it for a substatial speed-boost | ||
| 336 | + // Persistent connections only work when the server sends a 'Keep-Alive' header | ||
| 337 | + // Default is YES | ||
| 338 | + BOOL shouldAttemptPersistentConnection; | ||
| 339 | + | ||
| 340 | + // Set to yes when an appropriate keep-alive header is found | ||
| 341 | + BOOL canUsePersistentConnection; | ||
| 334 | } | 342 | } |
| 335 | 343 | ||
| 336 | #pragma mark init / dealloc | 344 | #pragma mark init / dealloc |
| @@ -666,4 +674,5 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount; | @@ -666,4 +674,5 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount; | ||
| 666 | @property (assign) BOOL shouldRunInBackgroundThread; | 674 | @property (assign) BOOL shouldRunInBackgroundThread; |
| 667 | @property (assign) int numberOfTimesToRetryOnTimeout; | 675 | @property (assign) int numberOfTimesToRetryOnTimeout; |
| 668 | @property (assign, readonly) int retryCount; | 676 | @property (assign, readonly) int retryCount; |
| 677 | +@property (assign) BOOL shouldAttemptPersistentConnection; | ||
| 669 | @end | 678 | @end |
| @@ -21,7 +21,7 @@ | @@ -21,7 +21,7 @@ | ||
| 21 | #import "ASIInputStream.h" | 21 | #import "ASIInputStream.h" |
| 22 | 22 | ||
| 23 | // Automatically set on build | 23 | // Automatically set on build |
| 24 | -NSString *ASIHTTPRequestVersion = @"v1.2-52 2009-12-19"; | 24 | +NSString *ASIHTTPRequestVersion = @"v1.2-53 2009-12-19"; |
| 25 | 25 | ||
| 26 | NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain"; | 26 | NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain"; |
| 27 | 27 | ||
| @@ -154,6 +154,7 @@ static BOOL isiPhoneOS2; | @@ -154,6 +154,7 @@ static BOOL isiPhoneOS2; | ||
| 154 | @property (assign) BOOL isSynchronous; | 154 | @property (assign) BOOL isSynchronous; |
| 155 | @property (assign) BOOL inProgress; | 155 | @property (assign) BOOL inProgress; |
| 156 | @property (assign) int retryCount; | 156 | @property (assign) int retryCount; |
| 157 | +@property (assign) BOOL canUsePersistentConnection; | ||
| 157 | @end | 158 | @end |
| 158 | 159 | ||
| 159 | 160 | ||
| @@ -191,6 +192,7 @@ static BOOL isiPhoneOS2; | @@ -191,6 +192,7 @@ static BOOL isiPhoneOS2; | ||
| 191 | self = [super init]; | 192 | self = [super init]; |
| 192 | [self setRequestMethod:@"GET"]; | 193 | [self setRequestMethod:@"GET"]; |
| 193 | 194 | ||
| 195 | + [self setShouldAttemptPersistentConnection:YES]; | ||
| 194 | [self setShouldPresentCredentialsBeforeChallenge:YES]; | 196 | [self setShouldPresentCredentialsBeforeChallenge:YES]; |
| 195 | [self setShouldRedirect:YES]; | 197 | [self setShouldRedirect:YES]; |
| 196 | [self setShowAccurateProgress:YES]; | 198 | [self setShowAccurateProgress:YES]; |
| @@ -808,6 +810,9 @@ static BOOL isiPhoneOS2; | @@ -808,6 +810,9 @@ static BOOL isiPhoneOS2; | ||
| 808 | CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, [NSMutableDictionary dictionaryWithObject:(NSString *)kCFBooleanFalse forKey:(NSString *)kCFStreamSSLValidatesCertificateChain]); | 810 | CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, [NSMutableDictionary dictionaryWithObject:(NSString *)kCFBooleanFalse forKey:(NSString *)kCFStreamSSLValidatesCertificateChain]); |
| 809 | } | 811 | } |
| 810 | 812 | ||
| 813 | + // Use a persistent connection if possible | ||
| 814 | + CFReadStreamSetProperty(readStream, kCFStreamPropertyHTTPAttemptPersistentConnection, kCFBooleanTrue); | ||
| 815 | + | ||
| 811 | 816 | ||
| 812 | // Handle proxy settings | 817 | // Handle proxy settings |
| 813 | 818 | ||
| @@ -930,10 +935,20 @@ static BOOL isiPhoneOS2; | @@ -930,10 +935,20 @@ static BOOL isiPhoneOS2; | ||
| 930 | [self scheduleReadStream]; | 935 | [self scheduleReadStream]; |
| 931 | } | 936 | } |
| 932 | 937 | ||
| 938 | +// if ([NSThread isMainThread]) { | ||
| 939 | +// [NSTimer scheduledTimerWithTimeInterval:0.25 target:self selector:@selector(updateStatus:) userInfo:nil repeats:YES]; | ||
| 940 | +// CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeOutSeconds, NO); | ||
| 941 | +// | ||
| 942 | +// } else if (!uploadProgressDelegate && !downloadProgressDelegate) { | ||
| 943 | +// CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeOutSeconds, NO); | ||
| 944 | +// [self checkRequestStatus]; | ||
| 945 | +// } else { | ||
| 946 | + | ||
| 933 | while (!complete) { | 947 | while (!complete) { |
| 934 | [self checkRequestStatus]; | 948 | [self checkRequestStatus]; |
| 935 | CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.25, NO); | 949 | CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.25, NO); |
| 936 | } | 950 | } |
| 951 | + //} | ||
| 937 | } | 952 | } |
| 938 | 953 | ||
| 939 | // This gets fired every 1/4 of a second in asynchronous requests to update the progress and work out if we need to timeout | 954 | // This gets fired every 1/4 of a second in asynchronous requests to update the progress and work out if we need to timeout |
| @@ -1594,12 +1609,33 @@ static BOOL isiPhoneOS2; | @@ -1594,12 +1609,33 @@ static BOOL isiPhoneOS2; | ||
| 1594 | 1609 | ||
| 1595 | } | 1610 | } |
| 1596 | 1611 | ||
| 1612 | + // Handle connection persistence | ||
| 1613 | + if ([self shouldAttemptPersistentConnection] && [[[self responseHeaders] objectForKey:@"Connection"] isEqualToString:@"Keep-Alive"]) { | ||
| 1614 | + NSString *keepAliveHeader = [[self responseHeaders] objectForKey:@"Keep-Alive"]; | ||
| 1615 | + if (keepAliveHeader) { | ||
| 1616 | + int timeout = 0; | ||
| 1617 | + int max = 0; | ||
| 1618 | + NSScanner *scanner = [NSScanner scannerWithString:keepAliveHeader]; | ||
| 1619 | + [scanner scanString:@"timeout=" intoString:NULL]; | ||
| 1620 | + [scanner scanInt:&timeout]; | ||
| 1621 | + [scanner scanUpToString:@"max=" intoString:NULL]; | ||
| 1622 | + [scanner scanString:@"max=" intoString:NULL]; | ||
| 1623 | + [scanner scanInt:&max]; | ||
| 1624 | + if (max > 5) { | ||
| 1625 | + [self setCanUsePersistentConnection:YES]; | ||
| 1626 | + CFRetain(readStream); | ||
| 1627 | + [NSTimer scheduledTimerWithTimeInterval:max target:[self class] selector:@selector(closePersistentConnection:) userInfo:(id)readStream repeats:NO]; | ||
| 1628 | + } | ||
| 1597 | } | 1629 | } |
| 1630 | + } | ||
| 1631 | + } | ||
| 1632 | + | ||
| 1633 | + | ||
| 1634 | + | ||
| 1598 | CFRelease(headers); | 1635 | CFRelease(headers); |
| 1599 | return isAuthenticationChallenge; | 1636 | return isAuthenticationChallenge; |
| 1600 | } | 1637 | } |
| 1601 | 1638 | ||
| 1602 | - | ||
| 1603 | #pragma mark http authentication | 1639 | #pragma mark http authentication |
| 1604 | 1640 | ||
| 1605 | - (void)saveProxyCredentialsToKeychain:(NSDictionary *)newCredentials | 1641 | - (void)saveProxyCredentialsToKeychain:(NSDictionary *)newCredentials |
| @@ -2222,12 +2258,14 @@ static BOOL isiPhoneOS2; | @@ -2222,12 +2258,14 @@ static BOOL isiPhoneOS2; | ||
| 2222 | if ([self needsRedirect]) { | 2258 | if ([self needsRedirect]) { |
| 2223 | return; | 2259 | return; |
| 2224 | } | 2260 | } |
| 2225 | - long long bufferSize = 2048; | 2261 | +// long long bufferSize = 2048; |
| 2226 | - if (contentLength > 262144) { | 2262 | +// if (contentLength > 262144) { |
| 2227 | - bufferSize = 65536; | 2263 | +// bufferSize = 65536; |
| 2228 | - } else if (contentLength > 65536) { | 2264 | +// } else if (contentLength > 65536) { |
| 2229 | - bufferSize = 16384; | 2265 | +// bufferSize = 16384; |
| 2230 | - } | 2266 | +// } |
| 2267 | + | ||
| 2268 | + long long bufferSize = 262144; | ||
| 2231 | 2269 | ||
| 2232 | // Reduce the buffer size if we're receiving data too quickly when bandwidth throttling is active | 2270 | // Reduce the buffer size if we're receiving data too quickly when bandwidth throttling is active |
| 2233 | // This just augments the throttling done in measureBandwidthUsage to reduce the amount we go over the limit | 2271 | // This just augments the throttling done in measureBandwidthUsage to reduce the amount we go over the limit |
| @@ -2397,7 +2435,9 @@ static BOOL isiPhoneOS2; | @@ -2397,7 +2435,9 @@ static BOOL isiPhoneOS2; | ||
| 2397 | if (readStreamIsScheduled) { | 2435 | if (readStreamIsScheduled) { |
| 2398 | CFReadStreamUnscheduleFromRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); | 2436 | CFReadStreamUnscheduleFromRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); |
| 2399 | } | 2437 | } |
| 2438 | + if (!canUsePersistentConnection) { | ||
| 2400 | CFReadStreamClose(readStream); | 2439 | CFReadStreamClose(readStream); |
| 2440 | + } | ||
| 2401 | CFRelease(readStream); | 2441 | CFRelease(readStream); |
| 2402 | readStream = NULL; | 2442 | readStream = NULL; |
| 2403 | } | 2443 | } |
| @@ -2421,6 +2461,13 @@ static BOOL isiPhoneOS2; | @@ -2421,6 +2461,13 @@ static BOOL isiPhoneOS2; | ||
| 2421 | } | 2461 | } |
| 2422 | } | 2462 | } |
| 2423 | 2463 | ||
| 2464 | ++ (void)closePersistentConnection:(NSTimer *)timer | ||
| 2465 | +{ | ||
| 2466 | + CFReadStreamRef stream = (CFReadStreamRef)[timer userInfo]; | ||
| 2467 | + CFReadStreamClose(stream); | ||
| 2468 | + CFRelease(stream); | ||
| 2469 | +} | ||
| 2470 | + | ||
| 2424 | #pragma mark NSCopying | 2471 | #pragma mark NSCopying |
| 2425 | 2472 | ||
| 2426 | - (id)copyWithZone:(NSZone *)zone | 2473 | - (id)copyWithZone:(NSZone *)zone |
| @@ -3427,6 +3474,8 @@ static BOOL isiPhoneOS2; | @@ -3427,6 +3474,8 @@ static BOOL isiPhoneOS2; | ||
| 3427 | @synthesize shouldRunInBackgroundThread; | 3474 | @synthesize shouldRunInBackgroundThread; |
| 3428 | @synthesize numberOfTimesToRetryOnTimeout; | 3475 | @synthesize numberOfTimesToRetryOnTimeout; |
| 3429 | @synthesize retryCount; | 3476 | @synthesize retryCount; |
| 3477 | +@synthesize shouldAttemptPersistentConnection; | ||
| 3478 | +@synthesize canUsePersistentConnection; | ||
| 3430 | @end | 3479 | @end |
| 3431 | 3480 | ||
| 3432 | 3481 |
| @@ -10,6 +10,8 @@ | @@ -10,6 +10,8 @@ | ||
| 10 | #import "ASITestCase.h" | 10 | #import "ASITestCase.h" |
| 11 | 11 | ||
| 12 | @interface PerformanceTests : ASITestCase { | 12 | @interface PerformanceTests : ASITestCase { |
| 13 | + NSURL *testURL; | ||
| 14 | + | ||
| 13 | NSDate *testStartDate; | 15 | NSDate *testStartDate; |
| 14 | int requestsComplete; | 16 | int requestsComplete; |
| 15 | NSMutableArray *responseData; | 17 | NSMutableArray *responseData; |
| @@ -19,6 +21,7 @@ | @@ -19,6 +21,7 @@ | ||
| 19 | - (void)testASIHTTPRequestAsyncPerformance; | 21 | - (void)testASIHTTPRequestAsyncPerformance; |
| 20 | - (void)testNSURLConnectionAsyncPerformance; | 22 | - (void)testNSURLConnectionAsyncPerformance; |
| 21 | 23 | ||
| 24 | +@property (retain,nonatomic) NSURL *testURL; | ||
| 22 | @property (retain,nonatomic) NSDate *testStartDate; | 25 | @property (retain,nonatomic) NSDate *testStartDate; |
| 23 | @property (assign,nonatomic) int requestsComplete; | 26 | @property (assign,nonatomic) int requestsComplete; |
| 24 | @property (retain,nonatomic) NSMutableArray *responseData; | 27 | @property (retain,nonatomic) NSMutableArray *responseData; |
| @@ -9,6 +9,7 @@ | @@ -9,6 +9,7 @@ | ||
| 9 | #import "PerformanceTests.h" | 9 | #import "PerformanceTests.h" |
| 10 | #import "ASIHTTPRequest.h" | 10 | #import "ASIHTTPRequest.h" |
| 11 | 11 | ||
| 12 | + | ||
| 12 | @interface NSURLConnectionSubclass : NSURLConnection { | 13 | @interface NSURLConnectionSubclass : NSURLConnection { |
| 13 | int tag; | 14 | int tag; |
| 14 | } | 15 | } |
| @@ -21,11 +22,105 @@ | @@ -21,11 +22,105 @@ | ||
| 21 | 22 | ||
| 22 | @implementation PerformanceTests | 23 | @implementation PerformanceTests |
| 23 | 24 | ||
| 25 | +- (void)setUp | ||
| 26 | +{ | ||
| 27 | + [self setTestURL:[NSURL URLWithString:@"http://allseeing-i.com"]]; | ||
| 28 | +} | ||
| 29 | + | ||
| 30 | +- (void)testASIHTTPRequestSynchronousPerformance | ||
| 31 | +{ | ||
| 32 | + [self performSelectorOnMainThread:@selector(runSynchronousASIHTTPRequests) withObject:nil waitUntilDone:YES]; | ||
| 33 | +} | ||
| 34 | + | ||
| 35 | + | ||
| 36 | +- (void)runSynchronousASIHTTPRequests | ||
| 37 | +{ | ||
| 38 | + int runTimes = 10; | ||
| 39 | + NSTimeInterval times[runTimes]; | ||
| 40 | + int i; | ||
| 41 | + for (i=0; i<runTimes; i++) { | ||
| 42 | + NSDate *startTime = [NSDate date]; | ||
| 43 | + ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:testURL]; | ||
| 44 | + //Send the same headers as NSURLRequest | ||
| 45 | + [request addRequestHeader:@"Pragma" value:@"no-cache"]; | ||
| 46 | + [request addRequestHeader:@"Accept" value:@"*/*"]; | ||
| 47 | + [request addRequestHeader:@"Accept-Language" value:@"en/us"]; | ||
| 48 | + [request setUseCookiePersistance:NO]; | ||
| 49 | + [request setUseSessionPersistance:NO]; | ||
| 50 | + //[request setShouldRunInBackgroundThread:YES]; | ||
| 51 | + [request startSynchronous]; | ||
| 52 | + if ([request error]) { | ||
| 53 | + NSLog(@"Request failed - cannot proceed with test"); | ||
| 54 | + return; | ||
| 55 | + } | ||
| 56 | + times[i] = [[NSDate date] timeIntervalSinceDate:startTime]; | ||
| 57 | + } | ||
| 58 | + NSTimeInterval bestTime = 1000; | ||
| 59 | + NSTimeInterval worstTime = 0; | ||
| 60 | + NSTimeInterval totalTime = 0; | ||
| 61 | + for (i=0; i<runTimes; i++) { | ||
| 62 | + if (times[i] < bestTime) { | ||
| 63 | + bestTime = times[i]; | ||
| 64 | + } | ||
| 65 | + if (times[i] > worstTime) { | ||
| 66 | + worstTime = times[i]; | ||
| 67 | + } | ||
| 68 | + totalTime += times[i]; | ||
| 69 | + } | ||
| 70 | + NSLog(@"Ran runTimes requests in %f seconds (average time: %f secs / best time: %f secs / worst time: %f secs)",totalTime,totalTime/runTimes,bestTime,worstTime); | ||
| 71 | +} | ||
| 72 | + | ||
| 73 | + | ||
| 74 | +- (void)testNSURLConnectionSynchronousPerformance | ||
| 75 | +{ | ||
| 76 | + [self performSelectorOnMainThread:@selector(runSynchronousNSURLConnections) withObject:nil waitUntilDone:YES]; | ||
| 77 | +} | ||
| 78 | + | ||
| 79 | + | ||
| 80 | +- (void)runSynchronousNSURLConnections | ||
| 81 | +{ | ||
| 82 | + int runTimes = 10; | ||
| 83 | + NSTimeInterval times[runTimes]; | ||
| 84 | + int i; | ||
| 85 | + for (i=0; i<runTimes; i++) { | ||
| 86 | + NSDate *startTime = [NSDate date]; | ||
| 87 | + | ||
| 88 | + NSURLResponse *response = nil; | ||
| 89 | + NSError *error = nil; | ||
| 90 | + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:testURL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:10]; | ||
| 91 | + [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; | ||
| 92 | + if (error) { | ||
| 93 | + NSLog(@"Request failed - cannot proceed with test"); | ||
| 94 | + return; | ||
| 95 | + } | ||
| 96 | + times[i] = [[NSDate date] timeIntervalSinceDate:startTime]; | ||
| 97 | + } | ||
| 98 | + NSTimeInterval bestTime = 1000; | ||
| 99 | + NSTimeInterval worstTime = 0; | ||
| 100 | + NSTimeInterval totalTime = 0; | ||
| 101 | + for (i=0; i<runTimes; i++) { | ||
| 102 | + if (times[i] < bestTime) { | ||
| 103 | + bestTime = times[i]; | ||
| 104 | + } | ||
| 105 | + if (times[i] > worstTime) { | ||
| 106 | + worstTime = times[i]; | ||
| 107 | + } | ||
| 108 | + totalTime += times[i]; | ||
| 109 | + } | ||
| 110 | + NSLog(@"Ran runTimes requests in %f seconds (average time: %f secs / best time: %f secs / worst time: %f secs)",totalTime,totalTime/runTimes,bestTime,worstTime); | ||
| 111 | +} | ||
| 112 | + | ||
| 113 | + | ||
| 24 | - (void)testASIHTTPRequestAsyncPerformance | 114 | - (void)testASIHTTPRequestAsyncPerformance |
| 25 | { | 115 | { |
| 26 | [self performSelectorOnMainThread:@selector(startASIHTTPRequests) withObject:nil waitUntilDone:NO]; | 116 | [self performSelectorOnMainThread:@selector(startASIHTTPRequests) withObject:nil waitUntilDone:NO]; |
| 27 | } | 117 | } |
| 28 | 118 | ||
| 119 | +- (void)testASIHTTPRequestAsyncPerformanceWithQueue | ||
| 120 | +{ | ||
| 121 | + [self performSelectorOnMainThread:@selector(startASIHTTPRequestsWithQueue) withObject:nil waitUntilDone:NO]; | ||
| 122 | +} | ||
| 123 | + | ||
| 29 | 124 | ||
| 30 | - (void)startASIHTTPRequests | 125 | - (void)startASIHTTPRequests |
| 31 | { | 126 | { |
| @@ -35,11 +130,33 @@ | @@ -35,11 +130,33 @@ | ||
| 35 | int i; | 130 | int i; |
| 36 | for (i=0; i<10; i++) { | 131 | for (i=0; i<10; i++) { |
| 37 | ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/the_great_american_novel_(abridged).txt"]]; | 132 | ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/the_great_american_novel_(abridged).txt"]]; |
| 133 | + //Send the same headers as NSURLRequest | ||
| 134 | + [request addRequestHeader:@"Pragma" value:@"no-cache"]; | ||
| 135 | + [request addRequestHeader:@"Accept" value:@"*/*"]; | ||
| 136 | + [request addRequestHeader:@"Accept-Language" value:@"en/us"]; | ||
| 38 | [request setDelegate:self]; | 137 | [request setDelegate:self]; |
| 39 | [request start]; | 138 | [request start]; |
| 40 | } | 139 | } |
| 41 | } | 140 | } |
| 42 | 141 | ||
| 142 | +- (void)startASIHTTPRequestsWithQueue | ||
| 143 | +{ | ||
| 144 | + bytesDownloaded = 0; | ||
| 145 | + [self setRequestsComplete:0]; | ||
| 146 | + [self setTestStartDate:[NSDate date]]; | ||
| 147 | + int i; | ||
| 148 | + NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease]; | ||
| 149 | + for (i=0; i<10; i++) { | ||
| 150 | + ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/the_great_american_novel_(abridged).txt"]]; | ||
| 151 | + //Send the same headers as NSURLRequest | ||
| 152 | + [request addRequestHeader:@"Pragma" value:@"no-cache"]; | ||
| 153 | + [request addRequestHeader:@"Accept" value:@"*/*"]; | ||
| 154 | + [request addRequestHeader:@"Accept-Language" value:@"en/us"]; | ||
| 155 | + [request setDelegate:self]; | ||
| 156 | + [queue addOperation:request]; | ||
| 157 | + } | ||
| 158 | +} | ||
| 159 | + | ||
| 43 | - (void)requestFailed:(ASIHTTPRequest *)request | 160 | - (void)requestFailed:(ASIHTTPRequest *)request |
| 44 | { | 161 | { |
| 45 | GHFail(@"Cannot proceed with ASIHTTPRequest test - a request failed"); | 162 | GHFail(@"Cannot proceed with ASIHTTPRequest test - a request failed"); |
| @@ -99,6 +216,7 @@ | @@ -99,6 +216,7 @@ | ||
| 99 | } | 216 | } |
| 100 | } | 217 | } |
| 101 | 218 | ||
| 219 | +@synthesize testURL; | ||
| 102 | @synthesize requestsComplete; | 220 | @synthesize requestsComplete; |
| 103 | @synthesize testStartDate; | 221 | @synthesize testStartDate; |
| 104 | @synthesize responseData; | 222 | @synthesize responseData; |
-
Please register or login to post a comment