Ben Copsey

Ok, now things are getting exciting!

Merge branch 'new-queue-threading-model' into v1.7

Conflicts:
	Classes/ASIHTTPRequest.h
	Classes/ASIHTTPRequest.m
@@ -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.