diff --git a/AFNetworking/AFURLConnectionOperation.h b/AFNetworking/AFURLConnectionOperation.h index abd2495..aba0e0b 100644 --- a/AFNetworking/AFURLConnectionOperation.h +++ b/AFNetworking/AFURLConnectionOperation.h @@ -75,6 +75,10 @@ extern NSString * const AFNetworkingOperationDidFinishNotification; */ @interface AFURLConnectionOperation : NSOperation { @private + unsigned short _state; + BOOL _cancelled; + NSRecursiveLock *_lock; + NSSet *_runLoopModes; NSURLConnection *_connection; diff --git a/AFNetworking/AFURLConnectionOperation.m b/AFNetworking/AFURLConnectionOperation.m index 5339da2..c521bba 100644 --- a/AFNetworking/AFURLConnectionOperation.m +++ b/AFNetworking/AFURLConnectionOperation.m @@ -22,14 +22,18 @@ #import "AFURLConnectionOperation.h" -static NSUInteger const kAFHTTPMinimumInitialDataCapacity = 1024; -static NSUInteger const kAFHTTPMaximumInitialDataCapacity = 1024 * 1024 * 8; - typedef enum { AFHTTPOperationReadyState = 1, AFHTTPOperationExecutingState = 2, AFHTTPOperationFinishedState = 3, -} AFOperationState; +} _AFOperationState; + +typedef unsigned short AFOperationState; + +static NSUInteger const kAFHTTPMinimumInitialDataCapacity = 1024; +static NSUInteger const kAFHTTPMaximumInitialDataCapacity = 1024 * 1024 * 8; + +static NSString * const kAFNetworkingLockName = @"com.alamofire.networking.operation.lock"; NSString * const AFNetworkingErrorDomain = @"com.alamofire.networking.error"; @@ -52,9 +56,34 @@ static inline NSString * AFKeyPathFromOperationState(AFOperationState state) { } } +static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperationState toState, BOOL isCancelled) { + switch (fromState) { + case AFHTTPOperationReadyState: + switch (toState) { + case AFHTTPOperationExecutingState: + return YES; + case AFHTTPOperationFinishedState: + return isCancelled; + default: + return NO; + } + case AFHTTPOperationExecutingState: + switch (toState) { + case AFHTTPOperationFinishedState: + return YES; + default: + return NO; + } + case AFHTTPOperationFinishedState: + return NO; + default: + return YES; + } +} + @interface AFURLConnectionOperation () @property (readwrite, nonatomic, assign) AFOperationState state; -@property (readwrite, nonatomic, assign, getter = isCancelled) BOOL cancelled; +@property (readwrite, nonatomic, retain) NSRecursiveLock *lock; @property (readwrite, nonatomic, assign) NSURLConnection *connection; @property (readwrite, nonatomic, retain) NSURLRequest *request; @property (readwrite, nonatomic, retain) NSURLResponse *response; @@ -67,14 +96,12 @@ static inline NSString * AFKeyPathFromOperationState(AFOperationState state) { @property (readwrite, nonatomic, copy) AFURLConnectionOperationProgressBlock downloadProgress; @property (readwrite, nonatomic, copy) AFURLConnectionOperationAuthenticationChallengeBlock authenticationBlock; -- (BOOL)shouldTransitionToState:(AFOperationState)state; - (void)operationDidStart; - (void)finish; @end @implementation AFURLConnectionOperation @synthesize state = _state; -@synthesize cancelled = _cancelled; @synthesize connection = _connection; @synthesize runLoopModes = _runLoopModes; @synthesize request = _request; @@ -89,12 +116,22 @@ static inline NSString * AFKeyPathFromOperationState(AFOperationState state) { @synthesize uploadProgress = _uploadProgress; @synthesize downloadProgress = _downloadProgress; @synthesize authenticationBlock = _authenticationBlock; +@synthesize lock = _lock; + (void)networkRequestThreadEntryPoint:(id)__unused object { do { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - [[NSRunLoop currentRunLoop] run]; - [pool drain]; + NSAutoreleasePool *exceptionPool = [[NSAutoreleasePool alloc] init]; + NSException *caughtException = nil; + @try { + NSAutoreleasePool *runLoopPool = [[NSAutoreleasePool alloc] init]; + [[NSRunLoop currentRunLoop] run]; + [runLoopPool drain]; + } + @catch(NSException *e) { caughtException = e; } + if(caughtException) { + NSLog(NSLocalizedString(@"Unhandled exception on %@ networking thread: %@, userInfo: %@", nil), NSStringFromClass([self class]), caughtException, [caughtException userInfo]); + } + [exceptionPool drain]; } while (YES); } @@ -116,6 +153,9 @@ static inline NSString * AFKeyPathFromOperationState(AFOperationState state) { return nil; } + self.lock = [[[NSRecursiveLock alloc] init] autorelease]; + self.lock.name = kAFNetworkingLockName; + self.runLoopModes = [NSSet setWithObject:NSRunLoopCommonModes]; self.request = urlRequest; @@ -126,6 +166,8 @@ static inline NSString * AFKeyPathFromOperationState(AFOperationState state) { } - (void)dealloc { + [_lock release]; + [_runLoopModes release]; [_request release]; @@ -135,7 +177,12 @@ static inline NSString * AFKeyPathFromOperationState(AFOperationState state) { [_responseData release]; [_responseString release]; [_dataAccumulator release]; - [_outputStream release]; _outputStream = nil; + + if (_outputStream) { + [_outputStream close]; + [_outputStream release]; + _outputStream = nil; + } [_uploadProgress release]; [_downloadProgress release]; @@ -145,6 +192,7 @@ static inline NSString * AFKeyPathFromOperationState(AFOperationState state) { } - (void)setCompletionBlock:(void (^)(void))block { + [self.lock lock]; if (!block) { [super setCompletionBlock:nil]; } else { @@ -154,6 +202,7 @@ static inline NSString * AFKeyPathFromOperationState(AFOperationState state) { [_blockSelf setCompletionBlock:nil]; }]; } + [self.lock unlock]; } - (NSInputStream *)inputStream { @@ -179,65 +228,33 @@ static inline NSString * AFKeyPathFromOperationState(AFOperationState state) { } - (void)setState:(AFOperationState)state { - if (![self shouldTransitionToState:state]) { - return; - } - - NSString *oldStateKey = AFKeyPathFromOperationState(self.state); - NSString *newStateKey = AFKeyPathFromOperationState(state); - - [self willChangeValueForKey:newStateKey]; - [self willChangeValueForKey:oldStateKey]; - _state = state; - [self didChangeValueForKey:oldStateKey]; - [self didChangeValueForKey:newStateKey]; - - switch (state) { - case AFHTTPOperationExecutingState: - [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidStartNotification object:self]; - break; - case AFHTTPOperationFinishedState: - [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidFinishNotification object:self]; - break; - default: - break; - } -} - -- (BOOL)shouldTransitionToState:(AFOperationState)state { - switch (self.state) { - case AFHTTPOperationReadyState: - switch (state) { - case AFHTTPOperationExecutingState: - return YES; - default: - return NO; - } - case AFHTTPOperationExecutingState: - switch (state) { - case AFHTTPOperationFinishedState: - return YES; - default: - return NO; - } - case AFHTTPOperationFinishedState: - return NO; - default: - return YES; - } -} - -- (void)setCancelled:(BOOL)cancelled { - [self willChangeValueForKey:@"isCancelled"]; - _cancelled = cancelled; - [self didChangeValueForKey:@"isCancelled"]; - - if ([self isCancelled]) { - self.state = AFHTTPOperationFinishedState; + [self.lock lock]; + if (AFStateTransitionIsValid(self.state, state, [self isCancelled])) { + NSString *oldStateKey = AFKeyPathFromOperationState(self.state); + NSString *newStateKey = AFKeyPathFromOperationState(state); + + [self willChangeValueForKey:newStateKey]; + [self willChangeValueForKey:oldStateKey]; + _state = state; + [self didChangeValueForKey:oldStateKey]; + [self didChangeValueForKey:newStateKey]; + + switch (state) { + case AFHTTPOperationExecutingState: + [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidStartNotification object:self]; + break; + case AFHTTPOperationFinishedState: + [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidFinishNotification object:self]; + break; + default: + break; + } } + [self.lock unlock]; } - (NSString *)responseString { + [self.lock lock]; if (!_responseString && self.response && self.responseData) { NSStringEncoding textEncoding = NSUTF8StringEncoding; if (self.response.textEncodingName) { @@ -246,6 +263,7 @@ static inline NSString * AFKeyPathFromOperationState(AFOperationState state) { self.responseString = [[[NSString alloc] initWithData:self.responseData encoding:textEncoding] autorelease]; } + [self.lock unlock]; return _responseString; } @@ -264,35 +282,40 @@ static inline NSString * AFKeyPathFromOperationState(AFOperationState state) { return self.state == AFHTTPOperationFinishedState; } +- (BOOL)isCancelled { + return _cancelled; +} + - (BOOL)isConcurrent { return YES; } -- (void)start { - if (![self isReady]) { - return; +- (void)start { + [self.lock lock]; + if ([self isReady]) { + self.state = AFHTTPOperationExecutingState; + + [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; } - - self.state = AFHTTPOperationExecutingState; - - [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:YES modes:[self.runLoopModes allObjects]]; + [self.lock unlock]; } - (void)operationDidStart { + [self.lock lock]; if ([self isCancelled]) { [self finish]; - return; + } else { + self.connection = [[[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO] autorelease]; + + NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; + for (NSString *runLoopMode in self.runLoopModes) { + [self.connection scheduleInRunLoop:runLoop forMode:runLoopMode]; + [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode]; + } + + [self.connection start]; } - - self.connection = [[[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO] autorelease]; - - NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; - for (NSString *runLoopMode in self.runLoopModes) { - [self.connection scheduleInRunLoop:runLoop forMode:runLoopMode]; - [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode]; - } - - [self.connection start]; + [self.lock unlock]; } - (void)finish { @@ -300,15 +323,22 @@ static inline NSString * AFKeyPathFromOperationState(AFOperationState state) { } - (void)cancel { - if ([self isFinished]) { - return; + [self.lock lock]; + if (![self isFinished] && ![self isCancelled]) { + [super cancel]; + + [self willChangeValueForKey:@"isCancelled"]; + _cancelled = YES; + if (self.connection) { + [self.connection cancel]; + + // We must send this delegate protcol message ourselves since the above [self.connection cancel] causes the connection to never send another message to its delegate. + NSDictionary *userInfo = [NSDictionary dictionaryWithObject:[self.request URL] forKey:NSURLErrorFailingURLErrorKey]; + [self performSelector:@selector(connection:didFailWithError:) withObject:self.connection withObject:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:userInfo]]; + } + [self didChangeValueForKey:@"isCancelled"]; } - - [super cancel]; - - self.cancelled = YES; - - [self.connection cancel]; + [self.lock unlock]; } #pragma mark - NSURLConnectionDelegate @@ -400,7 +430,7 @@ didReceiveResponse:(NSURLResponse *)response - (void)connection:(NSURLConnection *)__unused connection didFailWithError:(NSError *)error -{ +{ self.error = error; if (self.outputStream) { diff --git a/AFNetworking/UIImageView+AFNetworking.m b/AFNetworking/UIImageView+AFNetworking.m index 4d60de5..838ab20 100644 --- a/AFNetworking/UIImageView+AFNetworking.m +++ b/AFNetworking/UIImageView+AFNetworking.m @@ -124,8 +124,6 @@ static char kAFImageRequestOperationObjectKey; [requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { if ([[urlRequest URL] isEqual:[[self.af_imageRequestOperation request] URL]]) { self.image = responseObject; - } else { - self.image = placeholderImage; } if (success) {