// AFURLConnectionOperation.m // Copyright (c) 2011–2015 Alamofire Software Foundation (http://alamofire.org/) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import "AFURLConnectionOperation.h" #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) #import #endif #if !__has_feature(objc_arc) #error AFNetworking must be built with ARC. // You can turn on ARC for only AFNetworking files by adding -fobjc-arc to the build phase for each of its files. #endif typedef NS_ENUM(NSInteger, AFOperationState) { AFOperationPausedState = -1, AFOperationReadyState = 1, AFOperationExecutingState = 2, AFOperationFinishedState = 3, }; static dispatch_group_t url_request_operation_completion_group() { static dispatch_group_t af_url_request_operation_completion_group; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ af_url_request_operation_completion_group = dispatch_group_create(); }); return af_url_request_operation_completion_group; } static dispatch_queue_t url_request_operation_completion_queue() { static dispatch_queue_t af_url_request_operation_completion_queue; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ af_url_request_operation_completion_queue = dispatch_queue_create("com.alamofire.networking.operation.queue", DISPATCH_QUEUE_CONCURRENT ); }); return af_url_request_operation_completion_queue; } static NSString * const kAFNetworkingLockName = @"com.alamofire.networking.operation.lock"; NSString * const AFNetworkingOperationDidStartNotification = @"com.alamofire.networking.operation.start"; NSString * const AFNetworkingOperationDidFinishNotification = @"com.alamofire.networking.operation.finish"; typedef void (^AFURLConnectionOperationProgressBlock)(NSUInteger bytes, long long totalBytes, long long totalBytesExpected); typedef void (^AFURLConnectionOperationAuthenticationChallengeBlock)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge); typedef NSCachedURLResponse * (^AFURLConnectionOperationCacheResponseBlock)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse); typedef NSURLRequest * (^AFURLConnectionOperationRedirectResponseBlock)(NSURLConnection *connection, NSURLRequest *request, NSURLResponse *redirectResponse); typedef void (^AFURLConnectionOperationBackgroundTaskCleanupBlock)(); static inline NSString * AFKeyPathFromOperationState(AFOperationState state) { switch (state) { case AFOperationReadyState: return @"isReady"; case AFOperationExecutingState: return @"isExecuting"; case AFOperationFinishedState: return @"isFinished"; case AFOperationPausedState: return @"isPaused"; default: { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunreachable-code" return @"state"; #pragma clang diagnostic pop } } } static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperationState toState, BOOL isCancelled) { switch (fromState) { case AFOperationReadyState: switch (toState) { case AFOperationPausedState: case AFOperationExecutingState: return YES; case AFOperationFinishedState: return isCancelled; default: return NO; } case AFOperationExecutingState: switch (toState) { case AFOperationPausedState: case AFOperationFinishedState: return YES; default: return NO; } case AFOperationFinishedState: return NO; case AFOperationPausedState: return toState == AFOperationReadyState; default: { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunreachable-code" switch (toState) { case AFOperationPausedState: case AFOperationReadyState: case AFOperationExecutingState: case AFOperationFinishedState: return YES; default: return NO; } } #pragma clang diagnostic pop } } @interface AFURLConnectionOperation () @property (readwrite, nonatomic, assign) AFOperationState state; @property (readwrite, nonatomic, strong) NSRecursiveLock *lock; @property (readwrite, nonatomic, strong) NSURLConnection *connection; @property (readwrite, nonatomic, strong) NSURLRequest *request; @property (readwrite, nonatomic, strong) NSURLResponse *response; @property (readwrite, nonatomic, strong) NSError *error; @property (readwrite, nonatomic, strong) NSData *responseData; @property (readwrite, nonatomic, copy) NSString *responseString; @property (readwrite, nonatomic, assign) NSStringEncoding responseStringEncoding; @property (readwrite, nonatomic, assign) long long totalBytesRead; @property (readwrite, nonatomic, copy) AFURLConnectionOperationBackgroundTaskCleanupBlock backgroundTaskCleanup; @property (readwrite, nonatomic, copy) AFURLConnectionOperationProgressBlock uploadProgress; @property (readwrite, nonatomic, copy) AFURLConnectionOperationProgressBlock downloadProgress; @property (readwrite, nonatomic, copy) AFURLConnectionOperationAuthenticationChallengeBlock authenticationChallenge; @property (readwrite, nonatomic, copy) AFURLConnectionOperationCacheResponseBlock cacheResponse; @property (readwrite, nonatomic, copy) AFURLConnectionOperationRedirectResponseBlock redirectResponse; - (void)operationDidStart; - (void)finish; - (void)cancelConnection; @end @implementation AFURLConnectionOperation @synthesize outputStream = _outputStream; + (void)networkRequestThreadEntryPoint:(id)__unused object { @autoreleasepool { [[NSThread currentThread] setName:@"AFNetworking"]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; [runLoop run]; } } + (NSThread *)networkRequestThread { static NSThread *_networkRequestThread = nil; static dispatch_once_t oncePredicate; dispatch_once(&oncePredicate, ^{ _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil]; [_networkRequestThread start]; }); return _networkRequestThread; } - (instancetype)initWithRequest:(NSURLRequest *)urlRequest { NSParameterAssert(urlRequest); self = [super init]; if (!self) { return nil; } _state = AFOperationReadyState; self.lock = [[NSRecursiveLock alloc] init]; self.lock.name = kAFNetworkingLockName; self.runLoopModes = [NSSet setWithObject:NSRunLoopCommonModes]; self.request = urlRequest; self.shouldUseCredentialStorage = YES; self.securityPolicy = [AFSecurityPolicy defaultPolicy]; return self; } - (instancetype)init NS_UNAVAILABLE { return nil; } - (void)dealloc { if (_outputStream) { [_outputStream close]; _outputStream = nil; } if (_backgroundTaskCleanup) { _backgroundTaskCleanup(); } } #pragma mark - - (void)setResponseData:(NSData *)responseData { [self.lock lock]; if (!responseData) { _responseData = nil; } else { _responseData = [NSData dataWithBytes:responseData.bytes length:responseData.length]; } [self.lock unlock]; } - (NSString *)responseString { [self.lock lock]; if (!_responseString && self.response && self.responseData) { self.responseString = [[NSString alloc] initWithData:self.responseData encoding:self.responseStringEncoding]; } [self.lock unlock]; return _responseString; } - (NSStringEncoding)responseStringEncoding { [self.lock lock]; if (!_responseStringEncoding && self.response) { NSStringEncoding stringEncoding = NSUTF8StringEncoding; if (self.response.textEncodingName) { CFStringEncoding IANAEncoding = CFStringConvertIANACharSetNameToEncoding((__bridge CFStringRef)self.response.textEncodingName); if (IANAEncoding != kCFStringEncodingInvalidId) { stringEncoding = CFStringConvertEncodingToNSStringEncoding(IANAEncoding); } } self.responseStringEncoding = stringEncoding; } [self.lock unlock]; return _responseStringEncoding; } - (NSInputStream *)inputStream { return self.request.HTTPBodyStream; } - (void)setInputStream:(NSInputStream *)inputStream { NSMutableURLRequest *mutableRequest = [self.request mutableCopy]; mutableRequest.HTTPBodyStream = inputStream; self.request = mutableRequest; } - (NSOutputStream *)outputStream { if (!_outputStream) { self.outputStream = [NSOutputStream outputStreamToMemory]; } return _outputStream; } - (void)setOutputStream:(NSOutputStream *)outputStream { [self.lock lock]; if (outputStream != _outputStream) { if (_outputStream) { [_outputStream close]; } _outputStream = outputStream; } [self.lock unlock]; } #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED) - (void)setShouldExecuteAsBackgroundTaskWithExpirationHandler:(void (^)(void))handler { [self.lock lock]; if (!self.backgroundTaskCleanup) { UIApplication *application = [UIApplication sharedApplication]; UIBackgroundTaskIdentifier __block backgroundTaskIdentifier = UIBackgroundTaskInvalid; __weak __typeof(self)weakSelf = self; self.backgroundTaskCleanup = ^(){ if (backgroundTaskIdentifier != UIBackgroundTaskInvalid) { [[UIApplication sharedApplication] endBackgroundTask:backgroundTaskIdentifier]; backgroundTaskIdentifier = UIBackgroundTaskInvalid; } }; backgroundTaskIdentifier = [application beginBackgroundTaskWithExpirationHandler:^{ __strong __typeof(weakSelf)strongSelf = weakSelf; if (handler) { handler(); } if (strongSelf) { [strongSelf cancel]; strongSelf.backgroundTaskCleanup(); } }]; } [self.lock unlock]; } #endif #pragma mark - - (void)setState:(AFOperationState)state { if (!AFStateTransitionIsValid(self.state, state, [self isCancelled])) { return; } [self.lock lock]; NSString *oldStateKey = AFKeyPathFromOperationState(self.state); NSString *newStateKey = AFKeyPathFromOperationState(state); [self willChangeValueForKey:newStateKey]; [self willChangeValueForKey:oldStateKey]; _state = state; [self didChangeValueForKey:oldStateKey]; [self didChangeValueForKey:newStateKey]; [self.lock unlock]; } - (void)pause { if ([self isPaused] || [self isFinished] || [self isCancelled]) { return; } [self.lock lock]; if ([self isExecuting]) { [self performSelector:@selector(operationDidPause) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; dispatch_async(dispatch_get_main_queue(), ^{ NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter postNotificationName:AFNetworkingOperationDidFinishNotification object:self]; }); } self.state = AFOperationPausedState; [self.lock unlock]; } - (void)operationDidPause { [self.lock lock]; [self.connection cancel]; [self.lock unlock]; } - (BOOL)isPaused { return self.state == AFOperationPausedState; } - (void)resume { if (![self isPaused]) { return; } [self.lock lock]; self.state = AFOperationReadyState; [self start]; [self.lock unlock]; } #pragma mark - - (void)setUploadProgressBlock:(void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))block { self.uploadProgress = block; } - (void)setDownloadProgressBlock:(void (^)(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead))block { self.downloadProgress = block; } - (void)setWillSendRequestForAuthenticationChallengeBlock:(void (^)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge))block { self.authenticationChallenge = block; } - (void)setCacheResponseBlock:(NSCachedURLResponse * (^)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse))block { self.cacheResponse = block; } - (void)setRedirectResponseBlock:(NSURLRequest * (^)(NSURLConnection *connection, NSURLRequest *request, NSURLResponse *redirectResponse))block { self.redirectResponse = block; } #pragma mark - NSOperation - (void)setCompletionBlock:(void (^)(void))block { [self.lock lock]; if (!block) { [super setCompletionBlock:nil]; } else { __weak __typeof(self)weakSelf = self; [super setCompletionBlock:^ { __strong __typeof(weakSelf)strongSelf = weakSelf; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" dispatch_group_t group = strongSelf.completionGroup ?: url_request_operation_completion_group(); dispatch_queue_t queue = strongSelf.completionQueue ?: dispatch_get_main_queue(); #pragma clang diagnostic pop dispatch_group_async(group, queue, ^{ block(); }); dispatch_group_notify(group, url_request_operation_completion_queue(), ^{ [strongSelf setCompletionBlock:nil]; }); }]; } [self.lock unlock]; } - (BOOL)isReady { return self.state == AFOperationReadyState && [super isReady]; } - (BOOL)isExecuting { return self.state == AFOperationExecutingState; } - (BOOL)isFinished { return self.state == AFOperationFinishedState; } - (BOOL)isConcurrent { return YES; } - (void)start { [self.lock lock]; if ([self isCancelled]) { [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; } else if ([self isReady]) { self.state = AFOperationExecutingState; [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; } [self.lock unlock]; } - (void)operationDidStart { [self.lock lock]; if (![self isCancelled]) { self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; for (NSString *runLoopMode in self.runLoopModes) { [self.connection scheduleInRunLoop:runLoop forMode:runLoopMode]; [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode]; } [self.outputStream open]; [self.connection start]; } [self.lock unlock]; dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidStartNotification object:self]; }); } - (void)finish { [self.lock lock]; self.state = AFOperationFinishedState; [self.lock unlock]; dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidFinishNotification object:self]; }); } - (void)cancel { [self.lock lock]; if (![self isFinished] && ![self isCancelled]) { [super cancel]; if ([self isExecuting]) { [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; } } [self.lock unlock]; } - (void)cancelConnection { NSDictionary *userInfo = nil; if ([self.request URL]) { userInfo = @{NSURLErrorFailingURLErrorKey : [self.request URL]}; } NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:userInfo]; if (![self isFinished]) { if (self.connection) { [self.connection cancel]; [self performSelector:@selector(connection:didFailWithError:) withObject:self.connection withObject:error]; } else { // Accommodate race condition where `self.connection` has not yet been set before cancellation self.error = error; [self finish]; } } } #pragma mark - + (NSArray *)batchOfRequestOperations:(NSArray *)operations progressBlock:(void (^)(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations))progressBlock completionBlock:(void (^)(NSArray *operations))completionBlock { if (!operations || [operations count] == 0) { return @[[NSBlockOperation blockOperationWithBlock:^{ dispatch_async(dispatch_get_main_queue(), ^{ if (completionBlock) { completionBlock(@[]); } }); }]]; } __block dispatch_group_t group = dispatch_group_create(); NSBlockOperation *batchedOperation = [NSBlockOperation blockOperationWithBlock:^{ dispatch_group_notify(group, dispatch_get_main_queue(), ^{ if (completionBlock) { completionBlock(operations); } }); }]; for (AFURLConnectionOperation *operation in operations) { operation.completionGroup = group; void (^originalCompletionBlock)(void) = [operation.completionBlock copy]; __weak __typeof(operation)weakOperation = operation; operation.completionBlock = ^{ __strong __typeof(weakOperation)strongOperation = weakOperation; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" dispatch_queue_t queue = strongOperation.completionQueue ?: dispatch_get_main_queue(); #pragma clang diagnostic pop dispatch_group_async(group, queue, ^{ if (originalCompletionBlock) { originalCompletionBlock(); } NSUInteger numberOfFinishedOperations = [[operations indexesOfObjectsPassingTest:^BOOL(id op, NSUInteger __unused idx, BOOL __unused *stop) { return [op isFinished]; }] count]; if (progressBlock) { progressBlock(numberOfFinishedOperations, [operations count]); } dispatch_group_leave(group); }); }; dispatch_group_enter(group); [batchedOperation addDependency:operation]; } return [operations arrayByAddingObject:batchedOperation]; } #pragma mark - NSObject - (NSString *)description { [self.lock lock]; NSString *description = [NSString stringWithFormat:@"<%@: %p, state: %@, cancelled: %@ request: %@, response: %@>", NSStringFromClass([self class]), self, AFKeyPathFromOperationState(self.state), ([self isCancelled] ? @"YES" : @"NO"), self.request, self.response]; [self.lock unlock]; return description; } #pragma mark - NSURLConnectionDelegate - (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { if (self.authenticationChallenge) { self.authenticationChallenge(connection, challenge); return; } if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) { NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge]; } else { [[challenge sender] cancelAuthenticationChallenge:challenge]; } } else { if ([challenge previousFailureCount] == 0) { if (self.credential) { [[challenge sender] useCredential:self.credential forAuthenticationChallenge:challenge]; } else { [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge]; } } else { [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge]; } } } - (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection __unused *)connection { return self.shouldUseCredentialStorage; } - (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse { if (self.redirectResponse) { return self.redirectResponse(connection, request, redirectResponse); } else { return request; } } - (void)connection:(NSURLConnection __unused *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite { dispatch_async(dispatch_get_main_queue(), ^{ if (self.uploadProgress) { self.uploadProgress((NSUInteger)bytesWritten, totalBytesWritten, totalBytesExpectedToWrite); } }); } - (void)connection:(NSURLConnection __unused *)connection didReceiveResponse:(NSURLResponse *)response { self.response = response; } - (void)connection:(NSURLConnection __unused *)connection didReceiveData:(NSData *)data { NSUInteger length = [data length]; while (YES) { NSInteger totalNumberOfBytesWritten = 0; if ([self.outputStream hasSpaceAvailable]) { const uint8_t *dataBuffer = (uint8_t *)[data bytes]; NSInteger numberOfBytesWritten = 0; while (totalNumberOfBytesWritten < (NSInteger)length) { numberOfBytesWritten = [self.outputStream write:&dataBuffer[(NSUInteger)totalNumberOfBytesWritten] maxLength:(length - (NSUInteger)totalNumberOfBytesWritten)]; if (numberOfBytesWritten == -1) { break; } totalNumberOfBytesWritten += numberOfBytesWritten; } break; } else { [self.connection cancel]; if (self.outputStream.streamError) { [self performSelector:@selector(connection:didFailWithError:) withObject:self.connection withObject:self.outputStream.streamError]; } return; } } dispatch_async(dispatch_get_main_queue(), ^{ self.totalBytesRead += (long long)length; if (self.downloadProgress) { self.downloadProgress(length, self.totalBytesRead, self.response.expectedContentLength); } }); } - (void)connectionDidFinishLoading:(NSURLConnection __unused *)connection { self.responseData = [self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey]; [self.outputStream close]; if (self.responseData) { self.outputStream = nil; } self.connection = nil; [self finish]; } - (void)connection:(NSURLConnection __unused *)connection didFailWithError:(NSError *)error { self.error = error; [self.outputStream close]; if (self.responseData) { self.outputStream = nil; } self.connection = nil; [self finish]; } - (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse { if (self.cacheResponse) { return self.cacheResponse(connection, cachedResponse); } else { if ([self isCancelled]) { return nil; } return cachedResponse; } } #pragma mark - NSSecureCoding + (BOOL)supportsSecureCoding { return YES; } - (id)initWithCoder:(NSCoder *)decoder { NSURLRequest *request = [decoder decodeObjectOfClass:[NSURLRequest class] forKey:NSStringFromSelector(@selector(request))]; self = [self initWithRequest:request]; if (!self) { return nil; } self.state = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(state))] integerValue]; self.response = [decoder decodeObjectOfClass:[NSHTTPURLResponse class] forKey:NSStringFromSelector(@selector(response))]; self.error = [decoder decodeObjectOfClass:[NSError class] forKey:NSStringFromSelector(@selector(error))]; self.responseData = [decoder decodeObjectOfClass:[NSData class] forKey:NSStringFromSelector(@selector(responseData))]; self.totalBytesRead = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(totalBytesRead))] longLongValue]; return self; } - (void)encodeWithCoder:(NSCoder *)coder { [self pause]; [coder encodeObject:self.request forKey:NSStringFromSelector(@selector(request))]; switch (self.state) { case AFOperationExecutingState: case AFOperationPausedState: [coder encodeInteger:AFOperationReadyState forKey:NSStringFromSelector(@selector(state))]; break; default: [coder encodeInteger:self.state forKey:NSStringFromSelector(@selector(state))]; break; } [coder encodeObject:self.response forKey:NSStringFromSelector(@selector(response))]; [coder encodeObject:self.error forKey:NSStringFromSelector(@selector(error))]; [coder encodeObject:self.responseData forKey:NSStringFromSelector(@selector(responseData))]; [coder encodeInt64:self.totalBytesRead forKey:NSStringFromSelector(@selector(totalBytesRead))]; } #pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone { AFURLConnectionOperation *operation = [(AFURLConnectionOperation *)[[self class] allocWithZone:zone] initWithRequest:self.request]; operation.uploadProgress = self.uploadProgress; operation.downloadProgress = self.downloadProgress; operation.authenticationChallenge = self.authenticationChallenge; operation.cacheResponse = self.cacheResponse; operation.redirectResponse = self.redirectResponse; operation.completionQueue = self.completionQueue; operation.completionGroup = self.completionGroup; return operation; } @end