Ok, now things are getting exciting!
Merge branch 'new-queue-threading-model' into v1.7 Conflicts: Classes/ASIHTTPRequest.h Classes/ASIHTTPRequest.m
Showing
4 changed files
with
111 additions
and
89 deletions
| @@ -343,9 +343,6 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount; | @@ -343,9 +343,6 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount; | ||
| 343 | // Default is YES | 343 | // Default is YES |
| 344 | BOOL shouldPresentCredentialsBeforeChallenge; | 344 | BOOL shouldPresentCredentialsBeforeChallenge; |
| 345 | 345 | ||
| 346 | - // YES when the request is run with runSynchronous, NO otherwise. READ-ONLY | ||
| 347 | - BOOL isSynchronous; | ||
| 348 | - | ||
| 349 | // YES when the request hasn't finished yet. Will still be YES even if the request isn't doing anything (eg it's waiting for delegate authentication). READ-ONLY | 346 | // YES when the request hasn't finished yet. Will still be YES even if the request isn't doing anything (eg it's waiting for delegate authentication). READ-ONLY |
| 350 | BOOL inProgress; | 347 | BOOL inProgress; |
| 351 | 348 | ||
| @@ -384,9 +381,6 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount; | @@ -384,9 +381,6 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount; | ||
| 384 | // The stream will be closed + released either when another request comes to use the connection, or when the timer fires to tell the connection to expire | 381 | // The stream will be closed + released either when another request comes to use the connection, or when the timer fires to tell the connection to expire |
| 385 | NSMutableDictionary *connectionInfo; | 382 | NSMutableDictionary *connectionInfo; |
| 386 | 383 | ||
| 387 | - // This timer checks up on the request every 0.25 seconds, and updates progress | ||
| 388 | - NSTimer *statusTimer; | ||
| 389 | - | ||
| 390 | // When set to YES, 301 and 302 automatic redirects will use the original method and and body, according to the HTTP 1.1 standard | 384 | // When set to YES, 301 and 302 automatic redirects will use the original method and and body, according to the HTTP 1.1 standard |
| 391 | // Default is NO (to follow the behaviour of most browsers) | 385 | // Default is NO (to follow the behaviour of most browsers) |
| 392 | BOOL shouldUseRFC2616RedirectBehaviour; | 386 | BOOL shouldUseRFC2616RedirectBehaviour; |
| @@ -400,6 +394,10 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount; | @@ -400,6 +394,10 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount; | ||
| 400 | // Will be ASIHTTPRequestRunLoopMode for synchronous requests, NSDefaultRunLoopMode for all other requests | 394 | // Will be ASIHTTPRequestRunLoopMode for synchronous requests, NSDefaultRunLoopMode for all other requests |
| 401 | NSString *runLoopMode; | 395 | NSString *runLoopMode; |
| 402 | 396 | ||
| 397 | + // This timer checks up on the request every 0.25 seconds, and updates progress | ||
| 398 | + NSTimer *statusTimer; | ||
| 399 | + | ||
| 400 | + | ||
| 403 | // The download cache that will be used for this request (use [ASIHTTPRequest setDefaultCache:cache] to configure a default cache | 401 | // The download cache that will be used for this request (use [ASIHTTPRequest setDefaultCache:cache] to configure a default cache |
| 404 | id <ASICacheDelegate> downloadCache; | 402 | id <ASICacheDelegate> downloadCache; |
| 405 | 403 | ||
| @@ -414,7 +412,6 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount; | @@ -414,7 +412,6 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount; | ||
| 414 | 412 | ||
| 415 | // Set secondsToCache to use a custom time interval for expiring the response when it is stored in a cache | 413 | // Set secondsToCache to use a custom time interval for expiring the response when it is stored in a cache |
| 416 | NSTimeInterval secondsToCache; | 414 | NSTimeInterval secondsToCache; |
| 417 | - | ||
| 418 | } | 415 | } |
| 419 | 416 | ||
| 420 | #pragma mark init / dealloc | 417 | #pragma mark init / dealloc |
| @@ -717,6 +714,18 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount; | @@ -717,6 +714,18 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount; | ||
| 717 | // Returns a date from a string in RFC1123 format | 714 | // Returns a date from a string in RFC1123 format |
| 718 | + (NSDate *)dateFromRFC1123String:(NSString *)string; | 715 | + (NSDate *)dateFromRFC1123String:(NSString *)string; |
| 719 | 716 | ||
| 717 | +#pragma mark threading behaviour | ||
| 718 | + | ||
| 719 | +// In the default implementation, all requests run in a single background thread | ||
| 720 | +// Advanced users only: Override this method in a subclass for a different threading behaviour | ||
| 721 | +// Eg: return [NSThread mainThread] to run all requests in the main thread | ||
| 722 | +// Alternatively, you can create a thread on demand, or manage a pool of threads | ||
| 723 | +// Threads returned by this method will need to run the runloop in default mode (eg CFRunLoopRun()) | ||
| 724 | +// Requests will stop the runloop when they complete | ||
| 725 | +// If you have multiple requests sharing the thread you'll need to restart the runloop when this happens | ||
| 726 | ++ (NSThread *)threadForRequest:(ASIHTTPRequest *)request; | ||
| 727 | + | ||
| 728 | + | ||
| 720 | #pragma mark === | 729 | #pragma mark === |
| 721 | 730 | ||
| 722 | @property (retain) NSString *username; | 731 | @property (retain) NSString *username; |
| @@ -794,7 +803,6 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount; | @@ -794,7 +803,6 @@ extern unsigned long const ASIWWANBandwidthThrottleAmount; | ||
| 794 | @property (assign, readonly) int proxyAuthenticationRetryCount; | 803 | @property (assign, readonly) int proxyAuthenticationRetryCount; |
| 795 | @property (assign) BOOL haveBuiltRequestHeaders; | 804 | @property (assign) BOOL haveBuiltRequestHeaders; |
| 796 | @property (assign, nonatomic) BOOL haveBuiltPostBody; | 805 | @property (assign, nonatomic) BOOL haveBuiltPostBody; |
| 797 | -@property (assign, readonly) BOOL isSynchronous; | ||
| 798 | @property (assign, readonly) BOOL inProgress; | 806 | @property (assign, readonly) BOOL inProgress; |
| 799 | @property (assign) int numberOfTimesToRetryOnTimeout; | 807 | @property (assign) int numberOfTimesToRetryOnTimeout; |
| 800 | @property (assign, readonly) int retryCount; | 808 | @property (assign, readonly) int retryCount; |
| @@ -24,7 +24,7 @@ | @@ -24,7 +24,7 @@ | ||
| 24 | 24 | ||
| 25 | // Automatically set on build | 25 | // Automatically set on build |
| 26 | 26 | ||
| 27 | -NSString *ASIHTTPRequestVersion = @"v1.6.2-37 2010-06-23"; | 27 | +NSString *ASIHTTPRequestVersion = @"v1.6.2-58 2010-06-23"; |
| 28 | 28 | ||
| 29 | NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain"; | 29 | NSString* const NetworkRequestErrorDomain = @"ASIHTTPRequestErrorDomain"; |
| 30 | 30 | ||
| @@ -124,10 +124,17 @@ static BOOL isiPhoneOS2; | @@ -124,10 +124,17 @@ static BOOL isiPhoneOS2; | ||
| 124 | 124 | ||
| 125 | static id <ASICacheDelegate> defaultCache = nil; | 125 | static id <ASICacheDelegate> defaultCache = nil; |
| 126 | 126 | ||
| 127 | +//**Queue stuff**/ | ||
| 128 | + | ||
| 129 | +// The thread all requests will run on | ||
| 130 | +// Hangs around forever, but will be blocked unless there are requests underway | ||
| 131 | +static NSThread *networkThread = nil; | ||
| 132 | + | ||
| 133 | +static NSOperationQueue *sharedQueue = nil; | ||
| 134 | + | ||
| 127 | // Private stuff | 135 | // Private stuff |
| 128 | @interface ASIHTTPRequest () | 136 | @interface ASIHTTPRequest () |
| 129 | 137 | ||
| 130 | -- (void)checkRequestStatus; | ||
| 131 | - (void)cancelLoad; | 138 | - (void)cancelLoad; |
| 132 | 139 | ||
| 133 | - (void)destroyReadStream; | 140 | - (void)destroyReadStream; |
| @@ -139,12 +146,13 @@ static id <ASICacheDelegate> defaultCache = nil; | @@ -139,12 +146,13 @@ static id <ASICacheDelegate> defaultCache = nil; | ||
| 139 | + (void)measureBandwidthUsage; | 146 | + (void)measureBandwidthUsage; |
| 140 | + (void)recordBandwidthUsage; | 147 | + (void)recordBandwidthUsage; |
| 141 | - (void)startRequest; | 148 | - (void)startRequest; |
| 149 | +- (void)updateStatus:(NSTimer *)timer; | ||
| 150 | +- (void)checkRequestStatus; | ||
| 142 | 151 | ||
| 143 | - (void)markAsFinished; | 152 | - (void)markAsFinished; |
| 144 | - (void)performRedirect; | 153 | - (void)performRedirect; |
| 145 | - (BOOL)shouldTimeOut; | 154 | - (BOOL)shouldTimeOut; |
| 146 | 155 | ||
| 147 | -- (void)updateStatus:(NSTimer*)timer; | ||
| 148 | 156 | ||
| 149 | - (BOOL)useDataFromCache; | 157 | - (BOOL)useDataFromCache; |
| 150 | 158 | ||
| @@ -153,6 +161,7 @@ static id <ASICacheDelegate> defaultCache = nil; | @@ -153,6 +161,7 @@ static id <ASICacheDelegate> defaultCache = nil; | ||
| 153 | + (void)unsubscribeFromNetworkReachabilityNotifications; | 161 | + (void)unsubscribeFromNetworkReachabilityNotifications; |
| 154 | // Called when the status of the network changes | 162 | // Called when the status of the network changes |
| 155 | + (void)reachabilityChanged:(NSNotification *)note; | 163 | + (void)reachabilityChanged:(NSNotification *)note; |
| 164 | + | ||
| 156 | #endif | 165 | #endif |
| 157 | 166 | ||
| 158 | @property (assign) BOOL complete; | 167 | @property (assign) BOOL complete; |
| @@ -181,7 +190,6 @@ static id <ASICacheDelegate> defaultCache = nil; | @@ -181,7 +190,6 @@ static id <ASICacheDelegate> defaultCache = nil; | ||
| 181 | @property (retain) NSString *authenticationRealm; | 190 | @property (retain) NSString *authenticationRealm; |
| 182 | @property (retain) NSString *proxyAuthenticationRealm; | 191 | @property (retain) NSString *proxyAuthenticationRealm; |
| 183 | @property (retain) NSString *responseStatusMessage; | 192 | @property (retain) NSString *responseStatusMessage; |
| 184 | -@property (assign) BOOL isSynchronous; | ||
| 185 | @property (assign) BOOL inProgress; | 193 | @property (assign) BOOL inProgress; |
| 186 | @property (assign) int retryCount; | 194 | @property (assign) int retryCount; |
| 187 | @property (assign) BOOL connectionCanBeReused; | 195 | @property (assign) BOOL connectionCanBeReused; |
| @@ -189,10 +197,10 @@ static id <ASICacheDelegate> defaultCache = nil; | @@ -189,10 +197,10 @@ static id <ASICacheDelegate> defaultCache = nil; | ||
| 189 | @property (retain, nonatomic) NSInputStream *readStream; | 197 | @property (retain, nonatomic) NSInputStream *readStream; |
| 190 | @property (assign) ASIAuthenticationState authenticationNeeded; | 198 | @property (assign) ASIAuthenticationState authenticationNeeded; |
| 191 | @property (assign, nonatomic) BOOL readStreamIsScheduled; | 199 | @property (assign, nonatomic) BOOL readStreamIsScheduled; |
| 192 | -@property (retain, nonatomic) NSTimer *statusTimer; | ||
| 193 | @property (assign, nonatomic) BOOL downloadComplete; | 200 | @property (assign, nonatomic) BOOL downloadComplete; |
| 194 | @property (retain) NSNumber *requestID; | 201 | @property (retain) NSNumber *requestID; |
| 195 | @property (assign, nonatomic) NSString *runLoopMode; | 202 | @property (assign, nonatomic) NSString *runLoopMode; |
| 203 | +@property (retain, nonatomic) NSTimer *statusTimer; | ||
| 196 | @property (assign) BOOL didUseCachedResponse; | 204 | @property (assign) BOOL didUseCachedResponse; |
| 197 | @end | 205 | @end |
| 198 | 206 | ||
| @@ -223,6 +231,9 @@ static id <ASICacheDelegate> defaultCache = nil; | @@ -223,6 +231,9 @@ static id <ASICacheDelegate> defaultCache = nil; | ||
| 223 | #else | 231 | #else |
| 224 | isiPhoneOS2 = NO; | 232 | isiPhoneOS2 = NO; |
| 225 | #endif | 233 | #endif |
| 234 | + sharedQueue = [[NSOperationQueue alloc] init]; | ||
| 235 | + [sharedQueue setMaxConcurrentOperationCount:4]; | ||
| 236 | + | ||
| 226 | } | 237 | } |
| 227 | } | 238 | } |
| 228 | 239 | ||
| @@ -280,8 +291,6 @@ static id <ASICacheDelegate> defaultCache = nil; | @@ -280,8 +291,6 @@ static id <ASICacheDelegate> defaultCache = nil; | ||
| 280 | 291 | ||
| 281 | - (void)dealloc | 292 | - (void)dealloc |
| 282 | { | 293 | { |
| 283 | - [statusTimer invalidate]; | ||
| 284 | - [statusTimer release]; | ||
| 285 | [self setAuthenticationNeeded:ASINoAuthenticationNeededYet]; | 294 | [self setAuthenticationNeeded:ASINoAuthenticationNeededYet]; |
| 286 | if (requestAuthentication) { | 295 | if (requestAuthentication) { |
| 287 | CFRelease(requestAuthentication); | 296 | CFRelease(requestAuthentication); |
| @@ -523,39 +532,21 @@ static id <ASICacheDelegate> defaultCache = nil; | @@ -523,39 +532,21 @@ static id <ASICacheDelegate> defaultCache = nil; | ||
| 523 | #endif | 532 | #endif |
| 524 | [self setRunLoopMode:ASIHTTPRequestRunLoopMode]; | 533 | [self setRunLoopMode:ASIHTTPRequestRunLoopMode]; |
| 525 | [self setInProgress:YES]; | 534 | [self setInProgress:YES]; |
| 526 | - @try { | 535 | + |
| 527 | if (![self isCancelled] && ![self complete]) { | 536 | if (![self isCancelled] && ![self complete]) { |
| 528 | - [self setIsSynchronous:YES]; | ||
| 529 | [self main]; | 537 | [self main]; |
| 538 | + while (!complete) { | ||
| 539 | + [[NSRunLoop currentRunLoop] runMode:[self runLoopMode] beforeDate:[NSDate distantFuture]]; | ||
| 530 | } | 540 | } |
| 531 | - | ||
| 532 | - } @catch (NSException *exception) { | ||
| 533 | - NSError *underlyingError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIUnhandledExceptionError userInfo:[exception userInfo]]; | ||
| 534 | - [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIUnhandledExceptionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[exception name],NSLocalizedDescriptionKey,[exception reason],NSLocalizedFailureReasonErrorKey,underlyingError,NSUnderlyingErrorKey,nil]]]; | ||
| 535 | } | 541 | } |
| 542 | + | ||
| 536 | [self setInProgress:NO]; | 543 | [self setInProgress:NO]; |
| 537 | } | 544 | } |
| 538 | 545 | ||
| 539 | - (void)start | 546 | - (void)start |
| 540 | { | 547 | { |
| 541 | -#if TARGET_OS_IPHONE | 548 | + [self setInProgress:YES]; |
| 542 | - [self performSelectorInBackground:@selector(startAsynchronous) withObject:nil]; | 549 | + [self performSelector:@selector(main) onThread:[[self class] threadForRequest:self] withObject:nil waitUntilDone:NO]; |
| 543 | -#else | ||
| 544 | - | ||
| 545 | - SInt32 versionMajor; | ||
| 546 | - OSErr err = Gestalt(gestaltSystemVersionMajor, &versionMajor); | ||
| 547 | - if (err != noErr) { | ||
| 548 | - [NSException raise:@"FailedToDetectOSVersion" format:@"Unable to determine OS version, must give up"]; | ||
| 549 | - } | ||
| 550 | - // GCD will run the operation in its thread pool on Snow Leopard | ||
| 551 | - if (versionMajor >= 6) { | ||
| 552 | - [self startAsynchronous]; | ||
| 553 | - | ||
| 554 | - // On Leopard, we'll create the thread ourselves | ||
| 555 | - } else { | ||
| 556 | - [self performSelectorInBackground:@selector(startAsynchronous) withObject:nil]; | ||
| 557 | - } | ||
| 558 | -#endif | ||
| 559 | } | 550 | } |
| 560 | 551 | ||
| 561 | - (void)startAsynchronous | 552 | - (void)startAsynchronous |
| @@ -563,27 +554,7 @@ static id <ASICacheDelegate> defaultCache = nil; | @@ -563,27 +554,7 @@ static id <ASICacheDelegate> defaultCache = nil; | ||
| 563 | #if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING | 554 | #if DEBUG_REQUEST_STATUS || DEBUG_THROTTLING |
| 564 | NSLog(@"Starting asynchronous request %@",self); | 555 | NSLog(@"Starting asynchronous request %@",self); |
| 565 | #endif | 556 | #endif |
| 566 | - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; | 557 | + [sharedQueue addOperation:self]; |
| 567 | - | ||
| 568 | - [self setInProgress:YES]; | ||
| 569 | - @try { | ||
| 570 | - if ([self isCancelled] || [self complete]) | ||
| 571 | - { | ||
| 572 | - [self willChangeValueForKey:@"isFinished"]; | ||
| 573 | - [self didChangeValueForKey:@"isFinished"]; | ||
| 574 | - } else { | ||
| 575 | - [self willChangeValueForKey:@"isExecuting"]; | ||
| 576 | - [self didChangeValueForKey:@"isExecuting"]; | ||
| 577 | - | ||
| 578 | - [self main]; | ||
| 579 | - | ||
| 580 | - } | ||
| 581 | - | ||
| 582 | - } @catch (NSException *exception) { | ||
| 583 | - NSError *underlyingError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIUnhandledExceptionError userInfo:[exception userInfo]]; | ||
| 584 | - [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIUnhandledExceptionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[exception name],NSLocalizedDescriptionKey,[exception reason],NSLocalizedFailureReasonErrorKey,underlyingError,NSUnderlyingErrorKey,nil]]]; | ||
| 585 | - } | ||
| 586 | - [pool release]; | ||
| 587 | } | 558 | } |
| 588 | 559 | ||
| 589 | #pragma mark concurrency | 560 | #pragma mark concurrency |
| @@ -607,6 +578,8 @@ static id <ASICacheDelegate> defaultCache = nil; | @@ -607,6 +578,8 @@ static id <ASICacheDelegate> defaultCache = nil; | ||
| 607 | // Create the request | 578 | // Create the request |
| 608 | - (void)main | 579 | - (void)main |
| 609 | { | 580 | { |
| 581 | + @try { | ||
| 582 | + | ||
| 610 | [self setComplete:NO]; | 583 | [self setComplete:NO]; |
| 611 | 584 | ||
| 612 | // A HEAD request generated by an ASINetworkQueue may have set the error already. If so, we should not proceed. | 585 | // A HEAD request generated by an ASINetworkQueue may have set the error already. If so, we should not proceed. |
| @@ -629,6 +602,7 @@ static id <ASICacheDelegate> defaultCache = nil; | @@ -629,6 +602,7 @@ static id <ASICacheDelegate> defaultCache = nil; | ||
| 629 | [self setDownloadCache:nil]; | 602 | [self setDownloadCache:nil]; |
| 630 | } | 603 | } |
| 631 | 604 | ||
| 605 | + | ||
| 632 | // If we're redirecting, we'll already have a CFHTTPMessageRef | 606 | // If we're redirecting, we'll already have a CFHTTPMessageRef |
| 633 | if (request) { | 607 | if (request) { |
| 634 | CFRelease(request); | 608 | CFRelease(request); |
| @@ -686,6 +660,10 @@ static id <ASICacheDelegate> defaultCache = nil; | @@ -686,6 +660,10 @@ static id <ASICacheDelegate> defaultCache = nil; | ||
| 686 | 660 | ||
| 687 | [self startRequest]; | 661 | [self startRequest]; |
| 688 | 662 | ||
| 663 | + } @catch (NSException *exception) { | ||
| 664 | + NSError *underlyingError = [NSError errorWithDomain:NetworkRequestErrorDomain code:ASIUnhandledExceptionError userInfo:[exception userInfo]]; | ||
| 665 | + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIUnhandledExceptionError userInfo:[NSDictionary dictionaryWithObjectsAndKeys:[exception name],NSLocalizedDescriptionKey,[exception reason],NSLocalizedFailureReasonErrorKey,underlyingError,NSUnderlyingErrorKey,nil]]]; | ||
| 666 | + } | ||
| 689 | } | 667 | } |
| 690 | 668 | ||
| 691 | - (void)applyAuthorizationHeader | 669 | - (void)applyAuthorizationHeader |
| @@ -1100,17 +1078,8 @@ static id <ASICacheDelegate> defaultCache = nil; | @@ -1100,17 +1078,8 @@ static id <ASICacheDelegate> defaultCache = nil; | ||
| 1100 | 1078 | ||
| 1101 | // Record when the request started, so we can timeout if nothing happens | 1079 | // Record when the request started, so we can timeout if nothing happens |
| 1102 | [self setLastActivityTime:[NSDate date]]; | 1080 | [self setLastActivityTime:[NSDate date]]; |
| 1103 | - | ||
| 1104 | - | ||
| 1105 | [self setStatusTimer:[NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(updateStatus:) userInfo:nil repeats:YES]]; | 1081 | [self setStatusTimer:[NSTimer timerWithTimeInterval:0.25 target:self selector:@selector(updateStatus:) userInfo:nil repeats:YES]]; |
| 1106 | [[NSRunLoop currentRunLoop] addTimer:[self statusTimer] forMode:[self runLoopMode]]; | 1082 | [[NSRunLoop currentRunLoop] addTimer:[self statusTimer] forMode:[self runLoopMode]]; |
| 1107 | - | ||
| 1108 | - // If we're running asynchronously on the main thread, the runloop will already be running and we can return control | ||
| 1109 | - if (![NSThread isMainThread] || [self isSynchronous] || ![[self runLoopMode] isEqualToString:NSDefaultRunLoopMode]) { | ||
| 1110 | - while (!complete) { | ||
| 1111 | - [[NSRunLoop currentRunLoop] runMode:[self runLoopMode] beforeDate:[NSDate distantFuture]]; | ||
| 1112 | - } | ||
| 1113 | - } | ||
| 1114 | } | 1083 | } |
| 1115 | 1084 | ||
| 1116 | - (void)setStatusTimer:(NSTimer *)timer | 1085 | - (void)setStatusTimer:(NSTimer *)timer |
| @@ -1119,15 +1088,22 @@ static id <ASICacheDelegate> defaultCache = nil; | @@ -1119,15 +1088,22 @@ static id <ASICacheDelegate> defaultCache = nil; | ||
| 1119 | // We must invalidate the old timer here, not before we've created and scheduled a new timer | 1088 | // We must invalidate the old timer here, not before we've created and scheduled a new timer |
| 1120 | // This is because the timer may be the only thing retaining an asynchronous request | 1089 | // This is because the timer may be the only thing retaining an asynchronous request |
| 1121 | if (statusTimer && timer != statusTimer) { | 1090 | if (statusTimer && timer != statusTimer) { |
| 1122 | - | ||
| 1123 | [statusTimer invalidate]; | 1091 | [statusTimer invalidate]; |
| 1124 | [statusTimer release]; | 1092 | [statusTimer release]; |
| 1125 | - | ||
| 1126 | } | 1093 | } |
| 1127 | statusTimer = [timer retain]; | 1094 | statusTimer = [timer retain]; |
| 1128 | [self release]; | 1095 | [self release]; |
| 1129 | } | 1096 | } |
| 1130 | 1097 | ||
| 1098 | +// This gets fired every 1/4 of a second to update the progress and work out if we need to timeout | ||
| 1099 | +- (void)updateStatus:(NSTimer*)timer | ||
| 1100 | +{ | ||
| 1101 | + [self checkRequestStatus]; | ||
| 1102 | + if (![self inProgress]) { | ||
| 1103 | + [self setStatusTimer:nil]; | ||
| 1104 | + } | ||
| 1105 | +} | ||
| 1106 | + | ||
| 1131 | - (void)performRedirect | 1107 | - (void)performRedirect |
| 1132 | { | 1108 | { |
| 1133 | [[self cancelledLock] lock]; | 1109 | [[self cancelledLock] lock]; |
| @@ -1149,16 +1125,6 @@ static id <ASICacheDelegate> defaultCache = nil; | @@ -1149,16 +1125,6 @@ static id <ASICacheDelegate> defaultCache = nil; | ||
| 1149 | } | 1125 | } |
| 1150 | } | 1126 | } |
| 1151 | 1127 | ||
| 1152 | -// This gets fired every 1/4 of a second to update the progress and work out if we need to timeout | ||
| 1153 | -- (void)updateStatus:(NSTimer*)timer | ||
| 1154 | -{ | ||
| 1155 | - [self checkRequestStatus]; | ||
| 1156 | - if (![self inProgress]) { | ||
| 1157 | - [self setStatusTimer:nil]; | ||
| 1158 | - CFRunLoopStop(CFRunLoopGetCurrent()); | ||
| 1159 | - } | ||
| 1160 | -} | ||
| 1161 | - | ||
| 1162 | - (BOOL)shouldTimeOut | 1128 | - (BOOL)shouldTimeOut |
| 1163 | { | 1129 | { |
| 1164 | NSTimeInterval secondsSinceLastActivity = [[NSDate date] timeIntervalSinceDate:lastActivityTime]; | 1130 | NSTimeInterval secondsSinceLastActivity = [[NSDate date] timeIntervalSinceDate:lastActivityTime]; |
| @@ -1650,10 +1616,13 @@ static id <ASICacheDelegate> defaultCache = nil; | @@ -1650,10 +1616,13 @@ static id <ASICacheDelegate> defaultCache = nil; | ||
| 1650 | [[failedRequest queue] performSelectorOnMainThread:@selector(requestFailed:) withObject:failedRequest waitUntilDone:[NSThread isMainThread]]; | 1616 | [[failedRequest queue] performSelectorOnMainThread:@selector(requestFailed:) withObject:failedRequest waitUntilDone:[NSThread isMainThread]]; |
| 1651 | } | 1617 | } |
| 1652 | 1618 | ||
| 1619 | + // markAsFinished may well cause this object to be dealloced | ||
| 1620 | + [self retain]; | ||
| 1653 | [self markAsFinished]; | 1621 | [self markAsFinished]; |
| 1654 | if ([self mainRequest]) { | 1622 | if ([self mainRequest]) { |
| 1655 | [[self mainRequest] markAsFinished]; | 1623 | [[self mainRequest] markAsFinished]; |
| 1656 | } | 1624 | } |
| 1625 | + [self release]; | ||
| 1657 | } | 1626 | } |
| 1658 | 1627 | ||
| 1659 | #pragma mark parsing HTTP response headers | 1628 | #pragma mark parsing HTTP response headers |
| @@ -2495,15 +2464,11 @@ static id <ASICacheDelegate> defaultCache = nil; | @@ -2495,15 +2464,11 @@ static id <ASICacheDelegate> defaultCache = nil; | ||
| 2495 | [[self cancelledLock] unlock]; | 2464 | [[self cancelledLock] unlock]; |
| 2496 | 2465 | ||
| 2497 | if ([self downloadComplete] && [self needsRedirect]) { | 2466 | if ([self downloadComplete] && [self needsRedirect]) { |
| 2498 | - CFRunLoopStop(CFRunLoopGetCurrent()); | ||
| 2499 | [self performRedirect]; | 2467 | [self performRedirect]; |
| 2500 | return; | 2468 | return; |
| 2501 | } else if ([self downloadComplete] && [self authenticationNeeded]) { | 2469 | } else if ([self downloadComplete] && [self authenticationNeeded]) { |
| 2502 | - CFRunLoopStop(CFRunLoopGetCurrent()); | ||
| 2503 | [self attemptToApplyCredentialsAndResume]; | 2470 | [self attemptToApplyCredentialsAndResume]; |
| 2504 | return; | 2471 | return; |
| 2505 | - } else if (![self inProgress]) { | ||
| 2506 | - [self setStatusTimer:nil]; | ||
| 2507 | } | 2472 | } |
| 2508 | 2473 | ||
| 2509 | } | 2474 | } |
| @@ -2712,12 +2677,19 @@ static id <ASICacheDelegate> defaultCache = nil; | @@ -2712,12 +2677,19 @@ static id <ASICacheDelegate> defaultCache = nil; | ||
| 2712 | } | 2677 | } |
| 2713 | 2678 | ||
| 2714 | [self markAsFinished]; | 2679 | [self markAsFinished]; |
| 2680 | + | ||
| 2681 | + // If request has asked delegate or ASIAuthenticationDialog for credentials | ||
| 2682 | + } else if ([self authenticationNeeded]) { | ||
| 2683 | + [self setStatusTimer:nil]; | ||
| 2684 | + CFRunLoopStop(CFRunLoopGetCurrent()); | ||
| 2715 | } | 2685 | } |
| 2716 | } | 2686 | } |
| 2717 | 2687 | ||
| 2718 | - (void)markAsFinished | 2688 | - (void)markAsFinished |
| 2719 | { | 2689 | { |
| 2720 | - [[self retain] autorelease]; | 2690 | + // Autoreleased requests may well be dealloced here otherwise |
| 2691 | + // We use manual retain release rather than [[self retain] autorelease] because the pool is only released when all requests are finished | ||
| 2692 | + [self retain]; | ||
| 2721 | 2693 | ||
| 2722 | // dealloc won't be called when running with GC, so we'll clean these up now | 2694 | // dealloc won't be called when running with GC, so we'll clean these up now |
| 2723 | if (request) { | 2695 | if (request) { |
| @@ -2731,8 +2703,14 @@ static id <ASICacheDelegate> defaultCache = nil; | @@ -2731,8 +2703,14 @@ static id <ASICacheDelegate> defaultCache = nil; | ||
| 2731 | } | 2703 | } |
| 2732 | [self willChangeValueForKey:@"isFinished"]; | 2704 | [self willChangeValueForKey:@"isFinished"]; |
| 2733 | [self setInProgress:NO]; | 2705 | [self setInProgress:NO]; |
| 2706 | + | ||
| 2707 | + [self setStatusTimer:nil]; | ||
| 2708 | + | ||
| 2734 | [self didChangeValueForKey:@"isFinished"]; | 2709 | [self didChangeValueForKey:@"isFinished"]; |
| 2710 | + | ||
| 2735 | CFRunLoopStop(CFRunLoopGetCurrent()); | 2711 | CFRunLoopStop(CFRunLoopGetCurrent()); |
| 2712 | + | ||
| 2713 | + [self release]; | ||
| 2736 | } | 2714 | } |
| 2737 | 2715 | ||
| 2738 | - (BOOL)useDataFromCache | 2716 | - (BOOL)useDataFromCache |
| @@ -3779,6 +3757,43 @@ static id <ASICacheDelegate> defaultCache = nil; | @@ -3779,6 +3757,43 @@ static id <ASICacheDelegate> defaultCache = nil; | ||
| 3779 | } | 3757 | } |
| 3780 | #endif | 3758 | #endif |
| 3781 | 3759 | ||
| 3760 | +#pragma mark threading behaviour | ||
| 3761 | + | ||
| 3762 | +// In the default implementation, all requests run in a single background thread | ||
| 3763 | +// Advanced users only: Override this method in a subclass for a different threading behaviour | ||
| 3764 | +// Eg: return [NSThread mainThread] to run all requests in the main thread | ||
| 3765 | +// Alternatively, you can create a thread on demand, or manage a pool of threads | ||
| 3766 | +// Threads returned by this method will need to run the runloop in default mode (eg CFRunLoopRun()) | ||
| 3767 | +// Requests will stop the runloop when they complete | ||
| 3768 | +// If you have multiple requests sharing the thread or you want to re-use the thread, you'll need to restart the runloop | ||
| 3769 | ++ (NSThread *)threadForRequest:(ASIHTTPRequest *)request | ||
| 3770 | +{ | ||
| 3771 | + if (!networkThread) { | ||
| 3772 | + networkThread = [[NSThread alloc] initWithTarget:self selector:@selector(runRequests) object:nil]; | ||
| 3773 | + [networkThread start]; | ||
| 3774 | + } | ||
| 3775 | + return networkThread; | ||
| 3776 | +} | ||
| 3777 | + | ||
| 3778 | ++ (void)runRequests | ||
| 3779 | +{ | ||
| 3780 | + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; | ||
| 3781 | + | ||
| 3782 | + // Create a timer that fires extremely infrequently and runs forever to keep the runloop from exiting | ||
| 3783 | + NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:DBL_MAX target:self selector:@selector(doNothing:) userInfo:nil repeats:YES]; | ||
| 3784 | + timer = nil; | ||
| 3785 | + | ||
| 3786 | + while (1) { | ||
| 3787 | + CFRunLoopRun(); | ||
| 3788 | + } | ||
| 3789 | + | ||
| 3790 | + [pool release]; | ||
| 3791 | +} | ||
| 3792 | + | ||
| 3793 | ++ (void)doNothing:(NSTimer *)timer | ||
| 3794 | +{ | ||
| 3795 | +} | ||
| 3796 | + | ||
| 3782 | 3797 | ||
| 3783 | + (void)setDefaultCache:(id <ASICacheDelegate>)cache | 3798 | + (void)setDefaultCache:(id <ASICacheDelegate>)cache |
| 3784 | { | 3799 | { |
| @@ -3940,7 +3955,6 @@ static id <ASICacheDelegate> defaultCache = nil; | @@ -3940,7 +3955,6 @@ static id <ASICacheDelegate> defaultCache = nil; | ||
| 3940 | @synthesize responseStatusMessage; | 3955 | @synthesize responseStatusMessage; |
| 3941 | @synthesize shouldPresentCredentialsBeforeChallenge; | 3956 | @synthesize shouldPresentCredentialsBeforeChallenge; |
| 3942 | @synthesize haveBuiltRequestHeaders; | 3957 | @synthesize haveBuiltRequestHeaders; |
| 3943 | -@synthesize isSynchronous; | ||
| 3944 | @synthesize inProgress; | 3958 | @synthesize inProgress; |
| 3945 | @synthesize numberOfTimesToRetryOnTimeout; | 3959 | @synthesize numberOfTimesToRetryOnTimeout; |
| 3946 | @synthesize retryCount; | 3960 | @synthesize retryCount; |
| @@ -3950,11 +3964,11 @@ static id <ASICacheDelegate> defaultCache = nil; | @@ -3950,11 +3964,11 @@ static id <ASICacheDelegate> defaultCache = nil; | ||
| 3950 | @synthesize connectionInfo; | 3964 | @synthesize connectionInfo; |
| 3951 | @synthesize readStream; | 3965 | @synthesize readStream; |
| 3952 | @synthesize readStreamIsScheduled; | 3966 | @synthesize readStreamIsScheduled; |
| 3953 | -@synthesize statusTimer; | ||
| 3954 | @synthesize shouldUseRFC2616RedirectBehaviour; | 3967 | @synthesize shouldUseRFC2616RedirectBehaviour; |
| 3955 | @synthesize downloadComplete; | 3968 | @synthesize downloadComplete; |
| 3956 | @synthesize requestID; | 3969 | @synthesize requestID; |
| 3957 | @synthesize runLoopMode; | 3970 | @synthesize runLoopMode; |
| 3971 | +@synthesize statusTimer; | ||
| 3958 | @synthesize downloadCache; | 3972 | @synthesize downloadCache; |
| 3959 | @synthesize cachePolicy; | 3973 | @synthesize cachePolicy; |
| 3960 | @synthesize cacheStoragePolicy; | 3974 | @synthesize cacheStoragePolicy; |
| @@ -96,7 +96,7 @@ | @@ -96,7 +96,7 @@ | ||
| 96 | // Stop any other requests | 96 | // Stop any other requests |
| 97 | [networkQueue reset]; | 97 | [networkQueue reset]; |
| 98 | 98 | ||
| 99 | - [self setBigFetchRequest:[[[ASIHTTPRequest alloc] initWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/redirect_resume"]] autorelease]]; | 99 | + [self setBigFetchRequest:[ASIHTTPRequest requestWithURL:[NSURL URLWithString:@"http://allseeing-i.com/ASIHTTPRequest/tests/redirect_resume"]]]; |
| 100 | [[self bigFetchRequest] setDownloadDestinationPath:[[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"The Great American Novel.txt"]]; | 100 | [[self bigFetchRequest] setDownloadDestinationPath:[[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"The Great American Novel.txt"]]; |
| 101 | [[self bigFetchRequest] setTemporaryFileDownloadPath:[[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"The Great American Novel.txt.download"]]; | 101 | [[self bigFetchRequest] setTemporaryFileDownloadPath:[[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByAppendingPathComponent:@"The Great American Novel.txt.download"]]; |
| 102 | [[self bigFetchRequest] setAllowResumeForFileDownloads:YES]; | 102 | [[self bigFetchRequest] setAllowResumeForFileDownloads:YES]; |
This diff was suppressed by a .gitattributes entry.
-
Please register or login to post a comment