diff --git a/AFNetworking/AFHTTPClient.h b/AFNetworking/AFHTTPClient.h index e3d13b1..cdc1666 100644 --- a/AFNetworking/AFHTTPClient.h +++ b/AFNetworking/AFHTTPClient.h @@ -38,7 +38,19 @@ extern NSString * const AFNetworkingReachabilityDidChangeNotification; #endif /** - Method used to encode parameters into request body. + Specifies network reachability of the client to its `baseURL` domain. + */ +#ifdef _SYSTEMCONFIGURATION_H +typedef enum { + AFNetworkReachabilityStatusUnknown = -1, + AFNetworkReachabilityStatusNotReachable = 0, + AFNetworkReachabilityStatusReachableViaWWAN = 1, + AFNetworkReachabilityStatusReachableViaWiFi = 2, +} AFNetworkReachabilityStatus; +#endif + +/** + Specifies the method used to encode parameters into request body. */ typedef enum { AFFormURLParameterEncoding, @@ -141,6 +153,15 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete */ @property (readonly, nonatomic, retain) NSOperationQueue *operationQueue; +/** + The reachability status from the device to the current `baseURL` of the `AFHTTPClient`. + + @warning This property requires the `SystemConfiguration` framework. Add it in the active target's "Link Binary With Library" build phase, and add `#import ` to the header prefix of the project (Prefix.pch). + */ +#ifdef _SYSTEMCONFIGURATION_H +@property (readonly, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus; +#endif + ///--------------------------------------------- /// @name Creating and Initializing HTTP Clients ///--------------------------------------------- @@ -172,12 +193,12 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete /** Sets a callback to be executed when the network availability of the `baseURL` host changes. - @param block A block object to be executed when the network availability of the `baseURL` host changes.. This block has no return value and takes a single argument, which is `YES` if the host is available, otherwise `NO`. + @param block A block object to be executed when the network availability of the `baseURL` host changes.. This block has no return value and takes a single argument which represents the various reachability states from the device to the `baseURL`. @warning This method requires the `SystemConfiguration` framework. Add it in the active target's "Link Binary With Library" build phase, and add `#import ` to the header prefix of the project (Prefix.pch). */ #ifdef _SYSTEMCONFIGURATION_H -- (void)setReachabilityStatusChangeBlock:(void (^)(BOOL isNetworkReachable))block; +- (void)setReachabilityStatusChangeBlock:(void (^)(AFNetworkReachabilityStatus status))block; #endif ///------------------------------- diff --git a/AFNetworking/AFHTTPClient.m b/AFNetworking/AFHTTPClient.m index 919c46f..3b9c751 100644 --- a/AFNetworking/AFHTTPClient.m +++ b/AFNetworking/AFHTTPClient.m @@ -34,6 +34,11 @@ #ifdef _SYSTEMCONFIGURATION_H #import +#import +#import +#import +#import +#import #endif NSString * const AFNetworkingReachabilityDidChangeNotification = @"com.alamofire.networking.reachability.change"; @@ -57,11 +62,11 @@ static NSString * const kAFMultipartFormBoundary = @"Boundary+0xAbCdEfGbOuNdArY" #ifdef _SYSTEMCONFIGURATION_H typedef SCNetworkReachabilityRef AFNetworkReachabilityRef; +typedef void (^AFNetworkReachabilityStatusBlock)(AFNetworkReachabilityStatus status); #else typedef id AFNetworkReachabilityRef; #endif -typedef void (^AFNetworkReachabilityStatusBlock)(BOOL isNetworkReachable); typedef void (^AFCompletionBlock)(void); static NSUInteger const kAFHTTPClientDefaultMaxConcurrentOperationCount = 4; @@ -230,8 +235,11 @@ static NSString * AFPropertyListStringFromParameters(NSDictionary *parameters) { @property (readwrite, nonatomic, retain) NSMutableArray *registeredHTTPOperationClassNames; @property (readwrite, nonatomic, retain) NSMutableDictionary *defaultHeaders; @property (readwrite, nonatomic, retain) NSOperationQueue *operationQueue; +#ifdef _SYSTEMCONFIGURATION_H @property (readwrite, nonatomic, assign) AFNetworkReachabilityRef networkReachability; +@property (readwrite, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus; @property (readwrite, nonatomic, copy) AFNetworkReachabilityStatusBlock networkReachabilityStatusBlock; +#endif #ifdef _SYSTEMCONFIGURATION_H - (void)startMonitoringNetworkReachability; @@ -246,8 +254,11 @@ static NSString * AFPropertyListStringFromParameters(NSDictionary *parameters) { @synthesize registeredHTTPOperationClassNames = _registeredHTTPOperationClassNames; @synthesize defaultHeaders = _defaultHeaders; @synthesize operationQueue = _operationQueue; +#ifdef _SYSTEMCONFIGURATION_H @synthesize networkReachability = _networkReachability; +@synthesize networkReachabilityStatus = _networkReachabilityStatus; @synthesize networkReachabilityStatusBlock = _networkReachabilityStatusBlock; +#endif + (AFHTTPClient *)clientWithBaseURL:(NSURL *)url { return [[[self alloc] initWithBaseURL:url] autorelease]; @@ -283,6 +294,7 @@ static NSString * AFPropertyListStringFromParameters(NSDictionary *parameters) { #endif #ifdef _SYSTEMCONFIGURATION_H + self.networkReachabilityStatus = AFNetworkReachabilityStatusUnknown; [self startMonitoringNetworkReachability]; #endif @@ -295,13 +307,13 @@ static NSString * AFPropertyListStringFromParameters(NSDictionary *parameters) { - (void)dealloc { #ifdef _SYSTEMCONFIGURATION_H [self stopMonitoringNetworkReachability]; + [_networkReachabilityStatusBlock release]; #endif [_baseURL release]; [_registeredHTTPOperationClassNames release]; [_defaultHeaders release]; [_operationQueue release]; - [_networkReachabilityStatusBlock release]; [super dealloc]; } @@ -313,25 +325,78 @@ static NSString * AFPropertyListStringFromParameters(NSDictionary *parameters) { #pragma mark - #ifdef _SYSTEMCONFIGURATION_H -static void AFReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) { +static BOOL AFURLHostIsIPAddress(NSURL *url) { + struct sockaddr_in sa_in; + struct sockaddr_in6 sa_in6; + + return [url host] && (inet_pton(AF_INET, [[url host] UTF8String], &sa_in) == 1 || inet_pton(AF_INET6, [[url host] UTF8String], &sa_in6) == 1); +} + +static AFNetworkReachabilityStatus AFNetworkReachabilityStatusForFlags(SCNetworkReachabilityFlags flags) { BOOL isReachable = ((flags & kSCNetworkReachabilityFlagsReachable) != 0); BOOL needsConnection = ((flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0); BOOL isNetworkReachable = (isReachable && !needsConnection); - + + AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusUnknown; + if(isNetworkReachable == NO){ + status = AFNetworkReachabilityStatusNotReachable; + } +#if TARGET_OS_IPHONE + else if((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0){ + status = AFNetworkReachabilityStatusReachableViaWWAN; + } +#endif + else { + status = AFNetworkReachabilityStatusReachableViaWiFi; + } + + return status; +} + +static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) { + AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags); AFNetworkReachabilityStatusBlock block = (AFNetworkReachabilityStatusBlock)info; if (block) { - block(isNetworkReachable); + block(status); } - [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingReachabilityDidChangeNotification object:[NSNumber numberWithBool:isNetworkReachable]]; + [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingReachabilityDidChangeNotification object:[NSNumber numberWithInt:status]]; +} + +static const void * AFNetworkReachabilityRetainCallback(const void *info) { + return [(AFNetworkReachabilityStatusBlock)info copy]; +} + +static void AFNetworkReachabilityReleaseCallback(const void *info) { + [(AFNetworkReachabilityStatusBlock)info release]; } - (void)startMonitoringNetworkReachability { - [self stopMonitoringNetworkReachability]; + [self stopMonitoringNetworkReachability]; + self.networkReachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [[self.baseURL host] UTF8String]); - SCNetworkReachabilityContext context = {0, self.networkReachabilityStatusBlock, NULL, NULL, NULL}; - SCNetworkReachabilitySetCallback(self.networkReachability, AFReachabilityCallback, &context); + + AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status){ + self.networkReachabilityStatus = status; + if (self.networkReachabilityStatusBlock) { + self.networkReachabilityStatusBlock(status); + } + }; + + SCNetworkReachabilityContext context = {0, callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL}; + SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context); SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), (CFStringRef)NSRunLoopCommonModes); + + /* Network reachability monitoring does not establish a baseline for IP addresses as it does for hostnames, so if the base URL host is an IP address, the initial reachability callback is manually triggered. + */ + if (AFURLHostIsIPAddress(self.baseURL)) { + SCNetworkReachabilityFlags flags; + SCNetworkReachabilityGetFlags(self.networkReachability, &flags); + dispatch_async(dispatch_get_main_queue(), ^{ + AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags); + callback(status); + }); + } } - (void)stopMonitoringNetworkReachability { @@ -341,9 +406,8 @@ static void AFReachabilityCallback(SCNetworkReachabilityRef __unused target, SCN } } -- (void)setReachabilityStatusChangeBlock:(void (^)(BOOL isNetworkReachable))block { +- (void)setReachabilityStatusChangeBlock:(void (^)(AFNetworkReachabilityStatus status))block { self.networkReachabilityStatusBlock = block; - [self startMonitoringNetworkReachability]; } #endif @@ -474,9 +538,9 @@ static void AFReachabilityCallback(SCNetworkReachabilityRef __unused target, SCN if (!operation) { operation = [[[AFHTTPRequestOperation alloc] initWithRequest:urlRequest] autorelease]; } - + [operation setCompletionBlockWithSuccess:success failure:failure]; - + return operation; } diff --git a/AFNetworking/AFHTTPRequestOperation.h b/AFNetworking/AFHTTPRequestOperation.h index 5aa8407..a477b9b 100644 --- a/AFNetworking/AFHTTPRequestOperation.h +++ b/AFNetworking/AFHTTPRequestOperation.h @@ -23,6 +23,13 @@ #import #import "AFURLConnectionOperation.h" +/** + Returns a set of MIME types detected in an HTTP `Accept` or `Content-Type` header. + */ +extern NSSet * AFContentTypesFromHTTPHeader(NSString *string); + +extern NSString * AFCreateIncompleteDownloadDirectoryPath(void); + /** `AFHTTPRequestOperation` is a subclass of `AFURLConnectionOperation` for requests using the HTTP or HTTPS protocols. It encapsulates the concept of acceptable status codes and content types, which determine the success or failure of a request. */ @@ -37,30 +44,39 @@ */ @property (readonly, nonatomic, retain) NSHTTPURLResponse *response; +/** + Set a target file for the response, will stream directly into this destination. + Defaults to nil, which will use a memory stream. Will create a new outputStream on change. + + Note: Changing this while the request is not in ready state will be ignored. + */ +@property (nonatomic, copy) NSString *responseFilePath; + + +/** + Expected total length. This is different than expectedContentLength if the file is resumed. + On regular requests, this is equal to self.response.expectedContentLength unless we resume a request. + + Note: this can also be -1 if the file size is not sent (*) + */ +@property (assign, readonly) long long totalContentLength; + +/** + Indicator for the file offset on partial/resumed downloads. + This is greater than zero if the file download is resumed. + */ +@property (assign, readonly) long long offsetContentLength; + ///---------------------------------------------------------- /// @name Managing And Checking For Acceptable HTTP Responses ///---------------------------------------------------------- -/** - Returns an `NSIndexSet` object containing the ranges of acceptable HTTP status codes. When non-`nil`, the operation will set the `error` property to an error in `AFErrorDomain`. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html - - By default, this is the range 200 to 299, inclusive. - */ -@property (nonatomic, retain) NSIndexSet *acceptableStatusCodes; - /** A Boolean value that corresponds to whether the status code of the response is within the specified set of acceptable status codes. Returns `YES` if `acceptableStatusCodes` is `nil`. */ @property (readonly) BOOL hasAcceptableStatusCode; -/** - Returns an `NSSet` object containing the acceptable MIME types. When non-`nil`, the operation will set the `error` property to an error in `AFErrorDomain`. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17 - - By default, this is `nil`. - */ -@property (nonatomic, retain) NSSet *acceptableContentTypes; - /** A Boolean value that corresponds to whether the MIME type of the response is among the specified set of acceptable content types. Returns `YES` if `acceptableContentTypes` is `nil`. */ @@ -76,6 +92,43 @@ */ @property (nonatomic) dispatch_queue_t failureCallbackQueue; +///------------------------------------------------------------- +/// @name Managing Accceptable HTTP Status Codes & Content Types +///------------------------------------------------------------- + +/** + Returns an `NSIndexSet` object containing the ranges of acceptable HTTP status codes. When non-`nil`, the operation will set the `error` property to an error in `AFErrorDomain`. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html + + By default, this is the range 200 to 299, inclusive. + */ ++ (NSIndexSet *)acceptableStatusCodes; + +/** + Adds status codes to the set of acceptable HTTP status codes returned by `+acceptableStatusCodes` in subsequent calls by this class and its descendents. + + @param statusCodes The status codes to be added to the set of acceptable HTTP status codes + */ ++ (void)addAcceptableStatusCodes:(NSIndexSet *)statusCodes; + +/** + Returns an `NSSet` object containing the acceptable MIME types. When non-`nil`, the operation will set the `error` property to an error in `AFErrorDomain`. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17 + + By default, this is `nil`. + */ ++ (NSSet *)acceptableContentTypes; + +/** + Adds content types to the set of acceptable MIME types returned by `+acceptableContentTypes` in subsequent calls by this class and its descendents. + + @param contentTypes The content types to be added to the set of acceptable MIME types + */ ++ (void)addAcceptableContentTypes:(NSSet *)contentTypes; + + +///----------------------------------------------------- +/// @name Determining Whether A Request Can Be Processed +///----------------------------------------------------- + /** A Boolean value determining whether or not the class can process the specified request. For example, `AFJSONRequestOperation` may check to make sure the content type was `application/json` or the URL path extension was `.json`. diff --git a/AFNetworking/AFHTTPRequestOperation.m b/AFNetworking/AFHTTPRequestOperation.m index c1bff7d..5641aff 100644 --- a/AFNetworking/AFHTTPRequestOperation.m +++ b/AFNetworking/AFHTTPRequestOperation.m @@ -21,6 +21,44 @@ // THE SOFTWARE. #import "AFHTTPRequestOperation.h" +#import + +NSString * const kAFNetworkingIncompleteDownloadDirectoryName = @"Incomplete"; + +NSSet * AFContentTypesFromHTTPHeader(NSString *string) { + static NSCharacterSet *_skippedCharacterSet = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _skippedCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@" ,"]; + }); + + if (!string) { + return nil; + } + + NSScanner *scanner = [NSScanner scannerWithString:string]; + scanner.charactersToBeSkipped = _skippedCharacterSet; + + NSMutableSet *mutableContentTypes = [NSMutableSet set]; + while (![scanner isAtEnd]) { + NSString *contentType = nil; + if ([scanner scanUpToString:@";" intoString:&contentType]) { + [scanner scanUpToString:@"," intoString:nil]; + } + + if (contentType) { + [mutableContentTypes addObject:contentType]; + } + } + + return [NSSet setWithSet:mutableContentTypes]; +} + +static void AFSwizzleClassMethodWithClassAndSelectorUsingBlock(Class klass, SEL selector, void *block) { + Method originalMethod = class_getClassMethod(klass, selector); + IMP implementation = imp_implementationWithBlock(block); + class_replaceMethod(objc_getMetaClass([NSStringFromClass(klass) UTF8String]), selector, implementation, method_getTypeEncoding(originalMethod)); +} static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) { NSMutableString *string = [NSMutableString string]; @@ -52,33 +90,45 @@ static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) { return string; } +NSString * AFCreateIncompleteDownloadDirectoryPath(void) { + static NSString *incompleteDownloadPath; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + NSString *tempDirectory = NSTemporaryDirectory(); + incompleteDownloadPath = [[tempDirectory stringByAppendingPathComponent:kAFNetworkingIncompleteDownloadDirectoryName] retain]; + + NSError *error = nil; + NSFileManager *fileMan = [[NSFileManager alloc] init]; + if(![fileMan createDirectoryAtPath:incompleteDownloadPath withIntermediateDirectories:YES attributes:nil error:&error]) { + NSLog(@"Failed to create incomplete downloads directory at %@", incompleteDownloadPath); + } + [fileMan release]; + }); + + return incompleteDownloadPath; +} + #pragma mark - @interface AFHTTPRequestOperation () +@property (readwrite, nonatomic, retain) NSURLRequest *request; +@property (readwrite, nonatomic, retain) NSHTTPURLResponse *response; @property (readwrite, nonatomic, retain) NSError *HTTPError; +@property (assign) long long totalContentLength; +@property (assign) long long offsetContentLength; @end @implementation AFHTTPRequestOperation -@synthesize acceptableStatusCodes = _acceptableStatusCodes; -@synthesize acceptableContentTypes = _acceptableContentTypes; @synthesize HTTPError = _HTTPError; +@synthesize responseFilePath = _responseFilePath; @synthesize successCallbackQueue = _successCallbackQueue; @synthesize failureCallbackQueue = _failureCallbackQueue; - -- (id)initWithRequest:(NSURLRequest *)request { - self = [super initWithRequest:request]; - if (!self) { - return nil; - } - - self.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)]; - - return self; -} +@synthesize totalContentLength = _totalContentLength; +@synthesize offsetContentLength = _offsetContentLength; +@dynamic request; +@dynamic response; - (void)dealloc { - [_acceptableStatusCodes release]; - [_acceptableContentTypes release]; [_HTTPError release]; if (_successCallbackQueue) { @@ -94,21 +144,17 @@ static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) { [super dealloc]; } -- (NSHTTPURLResponse *)response { - return (NSHTTPURLResponse *)[super response]; -} - - (NSError *)error { if (self.response && !self.HTTPError) { if (![self hasAcceptableStatusCode]) { NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; - [userInfo setValue:[NSString stringWithFormat:NSLocalizedString(@"Expected status code in (%@), got %d", nil), AFStringFromIndexSet(self.acceptableStatusCodes), [self.response statusCode]] forKey:NSLocalizedDescriptionKey]; + [userInfo setValue:[NSString stringWithFormat:NSLocalizedString(@"Expected status code in (%@), got %d", nil), AFStringFromIndexSet([[self class] acceptableStatusCodes]), [self.response statusCode]] forKey:NSLocalizedDescriptionKey]; [userInfo setValue:[self.request URL] forKey:NSURLErrorFailingURLErrorKey]; self.HTTPError = [[[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorBadServerResponse userInfo:userInfo] autorelease]; } else if ([self.responseData length] > 0 && ![self hasAcceptableContentType]) { // Don't invalidate content type if there is no content NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; - [userInfo setValue:[NSString stringWithFormat:NSLocalizedString(@"Expected content type %@, got %@", nil), self.acceptableContentTypes, [self.response MIMEType]] forKey:NSLocalizedDescriptionKey]; + [userInfo setValue:[NSString stringWithFormat:NSLocalizedString(@"Expected content type %@, got %@", nil), [[self class] acceptableContentTypes], [self.response MIMEType]] forKey:NSLocalizedDescriptionKey]; [userInfo setValue:[self.request URL] forKey:NSURLErrorFailingURLErrorKey]; self.HTTPError = [[[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:userInfo] autorelease]; @@ -122,12 +168,30 @@ static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) { } } +- (void)pause { + unsigned long long offset = 0; + if ([self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey]) { + offset = [[self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey] unsignedLongLongValue]; + } else { + offset = [[self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey] length]; + } + + NSMutableURLRequest *mutableURLRequest = [[self.request mutableCopy] autorelease]; + if ([[self.response allHeaderFields] valueForKey:@"ETag"]) { + [mutableURLRequest setValue:[[self.response allHeaderFields] valueForKey:@"ETag"] forHTTPHeaderField:@"If-Range"]; + } + [mutableURLRequest setValue:[NSString stringWithFormat:@"bytes=%llu-", offset] forHTTPHeaderField:@"Range"]; + self.request = mutableURLRequest; + + [super pause]; +} + - (BOOL)hasAcceptableStatusCode { - return !self.acceptableStatusCodes || [self.acceptableStatusCodes containsIndex:[self.response statusCode]]; + return ![[self class] acceptableStatusCodes] || [[[self class] acceptableStatusCodes] containsIndex:[self.response statusCode]]; } - (BOOL)hasAcceptableContentType { - return !self.acceptableContentTypes || [self.acceptableContentTypes containsObject:[self.response MIMEType]]; + return ![[self class] acceptableContentTypes] || [[[self class] acceptableContentTypes] containsObject:[self.response MIMEType]]; } - (void)setSuccessCallbackQueue:(dispatch_queue_t)successCallbackQueue { @@ -136,7 +200,7 @@ static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) { dispatch_release(_successCallbackQueue); _successCallbackQueue = NULL; } - + if (successCallbackQueue) { dispatch_retain(successCallbackQueue); _successCallbackQueue = successCallbackQueue; @@ -182,10 +246,85 @@ static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) { }; } -#pragma mark - AFHTTPClientOperation +- (void)setResponseFilePath:(NSString *)responseFilePath { + if ([self isReady] && responseFilePath != _responseFilePath) { + [_responseFilePath release]; + _responseFilePath = [responseFilePath retain]; + + if (responseFilePath) { + self.outputStream = [NSOutputStream outputStreamToFileAtPath:responseFilePath append:NO]; + }else { + self.outputStream = [NSOutputStream outputStreamToMemory]; + } + } +} + +#pragma mark - AFHTTPRequestOperation + ++ (NSIndexSet *)acceptableStatusCodes { + return [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)]; +} + ++ (void)addAcceptableStatusCodes:(NSIndexSet *)statusCodes { + NSMutableIndexSet *mutableStatusCodes = [[[NSMutableIndexSet alloc] initWithIndexSet:[self acceptableStatusCodes]] autorelease]; + [mutableStatusCodes addIndexes:statusCodes]; + AFSwizzleClassMethodWithClassAndSelectorUsingBlock([self class], @selector(acceptableStatusCodes), ^(id _self) { + return mutableStatusCodes; + }); +} + ++ (NSSet *)acceptableContentTypes { + return nil; +} + ++ (void)addAcceptableContentTypes:(NSSet *)contentTypes { + NSMutableSet *mutableContentTypes = [[[NSMutableSet alloc] initWithSet:[self acceptableContentTypes] copyItems:YES] autorelease]; + [mutableContentTypes unionSet:contentTypes]; + AFSwizzleClassMethodWithClassAndSelectorUsingBlock([self class], @selector(acceptableContentTypes), ^(id _self) { + return mutableContentTypes; + }); +} + (BOOL)canProcessRequest:(NSURLRequest *)request { - return YES; + if (![[self class] isEqual:[AFHTTPRequestOperation class]]) { + return YES; + } + + return [[self acceptableContentTypes] intersectsSet:AFContentTypesFromHTTPHeader([request valueForHTTPHeaderField:@"Accept"])]; +} + +#pragma mark - NSURLConnectionDelegate + +- (void)connection:(NSURLConnection *)connection +didReceiveResponse:(NSURLResponse *)response +{ + self.response = (NSHTTPURLResponse *)response; + + // 206 = Partial Content. + long long totalContentLength = self.response.expectedContentLength; + long long fileOffset = 0; + if ([self.response statusCode] != 206) { + if ([self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey]) { + [self.outputStream setProperty:[NSNumber numberWithInteger:0] forKey:NSStreamFileCurrentOffsetKey]; + } else { + if ([[self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey] length] > 0) { + self.outputStream = [NSOutputStream outputStreamToMemory]; + } + } + }else { + NSString *contentRange = [self.response.allHeaderFields valueForKey:@"Content-Range"]; + if ([contentRange hasPrefix:@"bytes"]) { + NSArray *bytes = [contentRange componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@" -/"]]; + if ([bytes count] == 4) { + fileOffset = [[bytes objectAtIndex:1] longLongValue]; + totalContentLength = [[bytes objectAtIndex:2] longLongValue] ?: -1; // if this is *, it's converted to 0, but -1 is default. + } + } + + } + self.offsetContentLength = MAX(fileOffset, 0); + self.totalContentLength = totalContentLength; + [self.outputStream open]; } @end diff --git a/AFNetworking/AFImageRequestOperation.m b/AFNetworking/AFImageRequestOperation.m index 3ff2fd0..3821046 100644 --- a/AFNetworking/AFImageRequestOperation.m +++ b/AFNetworking/AFImageRequestOperation.m @@ -37,9 +37,6 @@ static dispatch_queue_t image_request_operation_processing_queue() { #elif __MAC_OS_X_VERSION_MIN_REQUIRED @property (readwrite, nonatomic, retain) NSImage *responseImage; #endif - -+ (NSSet *)defaultAcceptableContentTypes; -+ (NSSet *)defaultAcceptablePathExtensions; @end @implementation AFImageRequestOperation @@ -136,22 +133,12 @@ static dispatch_queue_t image_request_operation_processing_queue() { } #endif -+ (NSSet *)defaultAcceptableContentTypes { - return [NSSet setWithObjects:@"image/tiff", @"image/jpeg", @"image/gif", @"image/png", @"image/ico", @"image/x-icon", @"image/bmp", @"image/x-bmp", @"image/x-xbitmap", @"image/x-win-bitmap", nil]; -} - -+ (NSSet *)defaultAcceptablePathExtensions { - return [NSSet setWithObjects:@"tif", @"tiff", @"jpg", @"jpeg", @"gif", @"png", @"ico", @"bmp", @"cur", nil]; -} - - (id)initWithRequest:(NSURLRequest *)urlRequest { self = [super initWithRequest:urlRequest]; if (!self) { return nil; } - - self.acceptableContentTypes = [[self class] defaultAcceptableContentTypes]; - + #if __IPHONE_OS_VERSION_MIN_REQUIRED self.imageScale = [[UIScreen mainScreen] scale]; #endif @@ -200,33 +187,19 @@ static dispatch_queue_t image_request_operation_processing_queue() { #pragma mark - AFHTTPClientOperation -+ (BOOL)canProcessRequest:(NSURLRequest *)request { - return [[self defaultAcceptableContentTypes] containsObject:[request valueForHTTPHeaderField:@"Accept"]] || [[self defaultAcceptablePathExtensions] containsObject:[[request URL] pathExtension]]; ++ (NSSet *)acceptableContentTypes { + return [NSSet setWithObjects:@"image/tiff", @"image/jpeg", @"image/gif", @"image/png", @"image/ico", @"image/x-icon", @"image/bmp", @"image/x-bmp", @"image/x-xbitmap", @"image/x-win-bitmap", nil]; } -#if __IPHONE_OS_VERSION_MIN_REQUIRED -+ (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest - success:(void (^)(id object))success - failure:(void (^)(NSHTTPURLResponse *response, NSError *error))failure -{ - return [self imageRequestOperationWithRequest:urlRequest imageProcessingBlock:nil cacheName:nil success:^(NSURLRequest __unused *request, NSHTTPURLResponse __unused *response, UIImage *image) { - success(image); - } failure:^(NSURLRequest __unused *request, NSHTTPURLResponse *response, NSError *error) { - failure(response, error); - }]; ++ (BOOL)canProcessRequest:(NSURLRequest *)request { + static NSSet * _acceptablePathExtension = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + _acceptablePathExtension = [[NSSet alloc] initWithObjects:@"tif", @"tiff", @"jpg", @"jpeg", @"gif", @"png", @"ico", @"bmp", @"cur", nil]; + }); + + return [_acceptablePathExtension containsObject:[[request URL] pathExtension]] || [super canProcessRequest:request]; } -#elif __MAC_OS_X_VERSION_MIN_REQUIRED -+ (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest - success:(void (^)(id object))success - failure:(void (^)(NSHTTPURLResponse *response, NSError *error))failure -{ - return [self imageRequestOperationWithRequest:urlRequest imageProcessingBlock:nil cacheName:nil success:^(NSURLRequest __unused *request, NSHTTPURLResponse __unused *response, NSImage *image) { - success(image); - } failure:^(NSURLRequest __unused *request, NSHTTPURLResponse *response, NSError *error) { - failure(response, error); - }]; -} -#endif - (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure diff --git a/AFNetworking/AFJSONRequestOperation.m b/AFNetworking/AFJSONRequestOperation.m index 24ed254..9d40368 100644 --- a/AFNetworking/AFJSONRequestOperation.m +++ b/AFNetworking/AFJSONRequestOperation.m @@ -35,9 +35,6 @@ static dispatch_queue_t json_request_operation_processing_queue() { @interface AFJSONRequestOperation () @property (readwrite, nonatomic, retain) id responseJSON; @property (readwrite, nonatomic, retain) NSError *JSONError; - -+ (NSSet *)defaultAcceptableContentTypes; -+ (NSSet *)defaultAcceptablePathExtensions; @end @implementation AFJSONRequestOperation @@ -62,29 +59,6 @@ static dispatch_queue_t json_request_operation_processing_queue() { return requestOperation; } -+ (NSSet *)defaultAcceptableContentTypes { - return [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil]; -} - -+ (NSSet *)defaultAcceptablePathExtensions { - return [NSSet setWithObjects:@"json", nil]; -} - -+ (BOOL)canProcessRequest:(NSURLRequest *)request { - return [[self defaultAcceptableContentTypes] containsObject:[request valueForHTTPHeaderField:@"Accept"]] || [[self defaultAcceptablePathExtensions] containsObject:[[request URL] pathExtension]]; -} - -- (id)initWithRequest:(NSURLRequest *)urlRequest { - self = [super initWithRequest:urlRequest]; - if (!self) { - return nil; - } - - self.acceptableContentTypes = [[self class] defaultAcceptableContentTypes]; - - return self; -} - - (void)dealloc { [_responseJSON release]; [_JSONError release]; @@ -115,6 +89,16 @@ static dispatch_queue_t json_request_operation_processing_queue() { } } +#pragma mark - AFHTTPRequestOperation + ++ (NSSet *)acceptableContentTypes { + return [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil]; +} + ++ (BOOL)canProcessRequest:(NSURLRequest *)request { + return [[[request URL] pathExtension] isEqualToString:@"json"] || [super canProcessRequest:request]; +} + - (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure { diff --git a/AFNetworking/AFPropertyListRequestOperation.m b/AFNetworking/AFPropertyListRequestOperation.m index ea9d216..ecca1af 100644 --- a/AFNetworking/AFPropertyListRequestOperation.m +++ b/AFNetworking/AFPropertyListRequestOperation.m @@ -35,9 +35,6 @@ static dispatch_queue_t property_list_request_operation_processing_queue() { @property (readwrite, nonatomic, retain) id responsePropertyList; @property (readwrite, nonatomic, assign) NSPropertyListFormat propertyListFormat; @property (readwrite, nonatomic, retain) NSError *propertyListError; - -+ (NSSet *)defaultAcceptableContentTypes; -+ (NSSet *)defaultAcceptablePathExtensions; @end @implementation AFPropertyListRequestOperation @@ -64,22 +61,12 @@ static dispatch_queue_t property_list_request_operation_processing_queue() { return requestOperation; } -+ (NSSet *)defaultAcceptableContentTypes { - return [NSSet setWithObjects:@"application/x-plist", nil]; -} - -+ (NSSet *)defaultAcceptablePathExtensions { - return [NSSet setWithObjects:@"plist", nil]; -} - - (id)initWithRequest:(NSURLRequest *)urlRequest { self = [super initWithRequest:urlRequest]; if (!self) { return nil; } - - self.acceptableContentTypes = [[self class] defaultAcceptableContentTypes]; - + self.propertyListReadOptions = NSPropertyListImmutable; return self; @@ -111,8 +98,14 @@ static dispatch_queue_t property_list_request_operation_processing_queue() { } } +#pragma mark - AFHTTPRequestOperation + ++ (NSSet *)acceptableContentTypes { + return [NSSet setWithObjects:@"application/x-plist", nil]; +} + + (BOOL)canProcessRequest:(NSURLRequest *)request { - return [[self defaultAcceptableContentTypes] containsObject:[request valueForHTTPHeaderField:@"Accept"]] || [[self defaultAcceptablePathExtensions] containsObject:[[request URL] pathExtension]]; + return [[[request URL] pathExtension] isEqualToString:@"plist"] || [super canProcessRequest:request]; } - (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success diff --git a/AFNetworking/AFURLConnectionOperation.h b/AFNetworking/AFURLConnectionOperation.h index bef89dc..0266ced 100644 --- a/AFNetworking/AFURLConnectionOperation.h +++ b/AFNetworking/AFURLConnectionOperation.h @@ -135,7 +135,7 @@ extern NSString * const AFNetworkingOperationDidFinishNotification; /** The output stream that is used to write data received until the request is finished. - @discussion By default, data is accumulated into a buffer that is stored into `responseData` upon completion of the request. When `outputStream` is set, the data will not be accumulated into an internal buffer, and as a result, the `responseData` property of the completed request will be `nil`. + @discussion By default, data is accumulated into a buffer that is stored into `responseData` upon completion of the request. When `outputStream` is set, the data will not be accumulated into an internal buffer, and as a result, the `responseData` property of the completed request will be `nil`. The output stream will be scheduled in the network thread runloop upon being set. */ @property (nonatomic, retain) NSOutputStream *outputStream; @@ -152,6 +152,44 @@ extern NSString * const AFNetworkingOperationDidFinishNotification; */ - (id)initWithRequest:(NSURLRequest *)urlRequest; +///---------------------------------- +/// @name Pausing / Resuming Requests +///---------------------------------- + +/** + Pauses the execution of the request operation. + + @discussion A paused operation returns `NO` for `-isReady`, `-isExecuting`, and `-isFinished`. As such, it will remain in an `NSOperationQueue` until it is either cancelled or resumed. Pausing a finished or cancelled operation has no effect. + */ +- (void)pause; + +/** + Whether the request operation is currently paused. + + @return `YES` if the operation is currently paused, otherwise `NO`. + */ +- (BOOL)isPaused; + +/** + Resumes the execution of the paused request operation. + + @discussion Pause/Resume behavior varies depending on the underlying implementation for the operation class. In its base implementation, resuming a paused requests restarts the original request. However, since HTTP defines a specification for how to request a specific content range, `AFHTTPRequestOperation` will resume downloading the request from where it left off, instead of restarting the original request. + */ +- (void)resume; + +///---------------------------------------------- +/// @name Configuring Backgrounding Task Behavior +///---------------------------------------------- + +/** + Specifies that the operation should continue execution after the app has entered the background, and the expiration handler for that background task. + + @param handler A handler to be called shortly before the application’s remaining background time reaches 0. The handler is wrapped in a block that cancels the operation, and cleans up and marks the end of execution, unlike the `handler` parameter in `UIApplication -beginBackgroundTaskWithExpirationHandler:`, which expects this to be done in the handler itself. The handler is called synchronously on the main thread, thus blocking the application’s suspension momentarily while the application is notified. + */ +#if __IPHONE_OS_VERSION_MIN_REQUIRED +- (void)setShouldExecuteAsBackgroundTaskWithExpirationHandler:(void (^)(void))handler; +#endif + ///--------------------------------- /// @name Setting Progress Callbacks ///--------------------------------- @@ -163,7 +201,7 @@ extern NSString * const AFNetworkingOperationDidFinishNotification; @see setDownloadProgressBlock */ -- (void)setUploadProgressBlock:(void (^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))block; +- (void)setUploadProgressBlock:(void (^)(NSInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))block; /** Sets a callback to be called when an undetermined number of bytes have been downloaded from the server. @@ -172,7 +210,7 @@ extern NSString * const AFNetworkingOperationDidFinishNotification; @see setUploadProgressBlock */ -- (void)setDownloadProgressBlock:(void (^)(NSInteger bytesRead, NSInteger totalBytesRead, NSInteger totalBytesExpectedToRead))block; +- (void)setDownloadProgressBlock:(void (^)(NSInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead))block; ///------------------------------------------------- /// @name Setting NSURLConnection Delegate Callbacks diff --git a/AFNetworking/AFURLConnectionOperation.m b/AFNetworking/AFURLConnectionOperation.m index f84674f..5d55ab6 100644 --- a/AFNetworking/AFURLConnectionOperation.m +++ b/AFNetworking/AFURLConnectionOperation.m @@ -23,12 +23,19 @@ #import "AFURLConnectionOperation.h" typedef enum { + AFHTTPOperationPausedState = -1, AFHTTPOperationReadyState = 1, AFHTTPOperationExecutingState = 2, AFHTTPOperationFinishedState = 3, } _AFOperationState; -typedef unsigned short AFOperationState; +typedef signed short AFOperationState; + +#if __IPHONE_OS_VERSION_MIN_REQUIRED +typedef UIBackgroundTaskIdentifier AFBackgroundTaskIdentifier; +#else +typedef id AFBackgroundTaskIdentifier; +#endif static NSUInteger const kAFHTTPMinimumInitialDataCapacity = 1024; static NSUInteger const kAFHTTPMaximumInitialDataCapacity = 1024 * 1024 * 8; @@ -40,7 +47,7 @@ NSString * const AFNetworkingErrorDomain = @"com.alamofire.networking.error"; NSString * const AFNetworkingOperationDidStartNotification = @"com.alamofire.networking.operation.start"; NSString * const AFNetworkingOperationDidFinishNotification = @"com.alamofire.networking.operation.finish"; -typedef void (^AFURLConnectionOperationProgressBlock)(NSInteger bytes, NSInteger totalBytes, NSInteger totalBytesExpected); +typedef void (^AFURLConnectionOperationProgressBlock)(NSInteger bytes, long long totalBytes, long long totalBytesExpected); typedef BOOL (^AFURLConnectionOperationAuthenticationAgainstProtectionSpaceBlock)(NSURLConnection *connection, NSURLProtectionSpace *protectionSpace); typedef void (^AFURLConnectionOperationAuthenticationChallengeBlock)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge); typedef NSCachedURLResponse * (^AFURLConnectionOperationCacheResponseBlock)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse); @@ -53,6 +60,8 @@ static inline NSString * AFKeyPathFromOperationState(AFOperationState state) { return @"isExecuting"; case AFHTTPOperationFinishedState: return @"isFinished"; + case AFHTTPOperationPausedState: + return @"isPaused"; default: return @"state"; } @@ -62,6 +71,7 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat switch (fromState) { case AFHTTPOperationReadyState: switch (toState) { + case AFHTTPOperationPausedState: case AFHTTPOperationExecutingState: return YES; case AFHTTPOperationFinishedState: @@ -71,6 +81,7 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat } case AFHTTPOperationExecutingState: switch (toState) { + case AFHTTPOperationPausedState: case AFHTTPOperationFinishedState: return YES; default: @@ -78,6 +89,8 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat } case AFHTTPOperationFinishedState: return NO; + case AFHTTPOperationPausedState: + return toState == AFHTTPOperationReadyState; default: return YES; } @@ -93,8 +106,8 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat @property (readwrite, nonatomic, retain) NSError *error; @property (readwrite, nonatomic, retain) NSData *responseData; @property (readwrite, nonatomic, copy) NSString *responseString; -@property (readwrite, nonatomic, assign) NSInteger totalBytesRead; -@property (readwrite, nonatomic, retain) NSMutableData *dataAccumulator; +@property (readwrite, nonatomic, assign) long long totalBytesRead; +@property (readwrite, nonatomic, assign) AFBackgroundTaskIdentifier backgroundTaskIdentifier; @property (readwrite, nonatomic, copy) AFURLConnectionOperationProgressBlock uploadProgress; @property (readwrite, nonatomic, copy) AFURLConnectionOperationProgressBlock downloadProgress; @property (readwrite, nonatomic, copy) AFURLConnectionOperationAuthenticationAgainstProtectionSpaceBlock authenticationAgainstProtectionSpace; @@ -116,9 +129,9 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat @synthesize responseData = _responseData; @synthesize responseString = _responseString; @synthesize totalBytesRead = _totalBytesRead; -@synthesize dataAccumulator = _dataAccumulator; @dynamic inputStream; @synthesize outputStream = _outputStream; +@synthesize backgroundTaskIdentifier = _backgroundTaskIdentifier; @synthesize uploadProgress = _uploadProgress; @synthesize downloadProgress = _downloadProgress; @synthesize authenticationAgainstProtectionSpace = _authenticationAgainstProtectionSpace; @@ -158,7 +171,9 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat self.runLoopModes = [NSSet setWithObject:NSRunLoopCommonModes]; self.request = urlRequest; - + + self.outputStream = [NSOutputStream outputStreamToMemory]; + self.state = AFHTTPOperationReadyState; return self; @@ -175,13 +190,19 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat [_responseData release]; [_responseString release]; - [_dataAccumulator release]; if (_outputStream) { [_outputStream close]; [_outputStream release]; _outputStream = nil; } + +#if __IPHONE_OS_VERSION_MIN_REQUIRED + if (_backgroundTaskIdentifier) { + [[UIApplication sharedApplication] endBackgroundTask:_backgroundTaskIdentifier]; + _backgroundTaskIdentifier = UIBackgroundTaskInvalid; + } +#endif [_uploadProgress release]; [_downloadProgress release]; @@ -217,16 +238,51 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat } - (void)setInputStream:(NSInputStream *)inputStream { + [self willChangeValueForKey:@"inputStream"]; NSMutableURLRequest *mutableRequest = [[self.request mutableCopy] autorelease]; mutableRequest.HTTPBodyStream = inputStream; self.request = mutableRequest; + [self didChangeValueForKey:@"inputStream"]; } -- (void)setUploadProgressBlock:(void (^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))block { +- (void)setOutputStream:(NSOutputStream *)outputStream { + [self willChangeValueForKey:@"outputStream"]; + [outputStream retain]; + [_outputStream release]; + _outputStream = outputStream; + [self didChangeValueForKey:@"outputStream"]; + + NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; + for (NSString *runLoopMode in self.runLoopModes) { + [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode]; + } +} + +#if __IPHONE_OS_VERSION_MIN_REQUIRED +- (void)setShouldExecuteAsBackgroundTaskWithExpirationHandler:(void (^)(void))handler { + [self.lock lock]; + if (!self.backgroundTaskIdentifier) { + UIApplication *application = [UIApplication sharedApplication]; + self.backgroundTaskIdentifier = [application beginBackgroundTaskWithExpirationHandler:^{ + if (handler) { + handler(); + } + + [self cancel]; + + [application endBackgroundTask:self.backgroundTaskIdentifier]; + self.backgroundTaskIdentifier = UIBackgroundTaskInvalid; + }]; + } + [self.lock unlock]; +} +#endif + +- (void)setUploadProgressBlock:(void (^)(NSInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))block { self.uploadProgress = block; } -- (void)setDownloadProgressBlock:(void (^)(NSInteger bytesRead, NSInteger totalBytesRead, NSInteger totalBytesExpectedToRead))block { +- (void)setDownloadProgressBlock:(void (^)(NSInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead))block { self.downloadProgress = block; } @@ -283,6 +339,35 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat return _responseString; } +- (void)pause { + if ([self isPaused]) { + return; + } + + [self.lock lock]; + self.state = AFHTTPOperationPausedState; + + [self.connection performSelector:@selector(cancel) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; + [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidFinishNotification object:self]; + [self.lock unlock]; +} + +- (BOOL)isPaused { + return self.state == AFHTTPOperationPausedState; +} + +- (void)resume { + if (![self isPaused]) { + return; + } + + [self.lock lock]; + self.state = AFHTTPOperationReadyState; + + [self start]; + [self.lock unlock]; +} + #pragma mark - NSOperation - (BOOL)isReady { @@ -431,43 +516,30 @@ totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite - (void)connection:(NSURLConnection *)__unused connection didReceiveResponse:(NSURLResponse *)response { - self.response = (NSHTTPURLResponse *)response; + self.response = response; - if (self.outputStream) { - [self.outputStream open]; - } else { - NSUInteger maxCapacity = MAX((NSUInteger)llabs(response.expectedContentLength), kAFHTTPMinimumInitialDataCapacity); - NSUInteger capacity = MIN(maxCapacity, kAFHTTPMaximumInitialDataCapacity); - self.dataAccumulator = [NSMutableData dataWithCapacity:capacity]; - } + [self.outputStream open]; } - (void)connection:(NSURLConnection *)__unused connection - didReceiveData:(NSData *)data + didReceiveData:(NSData *)data { self.totalBytesRead += [data length]; - if (self.outputStream) { - if ([self.outputStream hasSpaceAvailable]) { - const uint8_t *dataBuffer = (uint8_t *) [data bytes]; - [self.outputStream write:&dataBuffer[0] maxLength:[data length]]; - } - } else { - [self.dataAccumulator appendData:data]; + if ([self.outputStream hasSpaceAvailable]) { + const uint8_t *dataBuffer = (uint8_t *) [data bytes]; + [self.outputStream write:&dataBuffer[0] maxLength:[data length]]; } if (self.downloadProgress) { - self.downloadProgress([data length], self.totalBytesRead, (NSInteger)self.response.expectedContentLength); + self.downloadProgress((long long)[data length], self.totalBytesRead, self.response.expectedContentLength); } } -- (void)connectionDidFinishLoading:(NSURLConnection *)__unused connection { - if (self.outputStream) { - [self.outputStream close]; - } else { - self.responseData = [NSData dataWithData:self.dataAccumulator]; - [_dataAccumulator release]; _dataAccumulator = nil; - } +- (void)connectionDidFinishLoading:(NSURLConnection *)__unused connection { + self.responseData = [self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey]; + + [self.outputStream close]; [self finish]; @@ -479,11 +551,7 @@ didReceiveResponse:(NSURLResponse *)response { self.error = error; - if (self.outputStream) { - [self.outputStream close]; - } else { - [_dataAccumulator release]; _dataAccumulator = nil; - } + [self.outputStream close]; [self finish]; diff --git a/AFNetworking/AFXMLRequestOperation.m b/AFNetworking/AFXMLRequestOperation.m index 79c5272..f40cacb 100644 --- a/AFNetworking/AFXMLRequestOperation.m +++ b/AFNetworking/AFXMLRequestOperation.m @@ -39,9 +39,6 @@ static dispatch_queue_t xml_request_operation_processing_queue() { @property (readwrite, nonatomic, retain) NSXMLDocument *responseXMLDocument; #endif @property (readwrite, nonatomic, retain) NSError *XMLError; - -+ (NSSet *)defaultAcceptableContentTypes; -+ (NSSet *)defaultAcceptablePathExtensions; @end @implementation AFXMLRequestOperation @@ -91,25 +88,6 @@ static dispatch_queue_t xml_request_operation_processing_queue() { } #endif -+ (NSSet *)defaultAcceptableContentTypes { - return [NSSet setWithObjects:@"application/xml", @"text/xml", nil]; -} - -+ (NSSet *)defaultAcceptablePathExtensions { - return [NSSet setWithObjects:@"xml", nil]; -} - -- (id)initWithRequest:(NSURLRequest *)urlRequest { - self = [super initWithRequest:urlRequest]; - if (!self) { - return nil; - } - - self.acceptableContentTypes = [[self class] defaultAcceptableContentTypes]; - - return self; -} - - (void)dealloc { [_responseXMLParser release]; @@ -158,8 +136,14 @@ static dispatch_queue_t xml_request_operation_processing_queue() { self.responseXMLParser.delegate = nil; } +#pragma mark - AFHTTPRequestOperation + ++ (NSSet *)acceptableContentTypes { + return [NSSet setWithObjects:@"application/xml", @"text/xml", nil]; +} + + (BOOL)canProcessRequest:(NSURLRequest *)request { - return [[self defaultAcceptableContentTypes] containsObject:[request valueForHTTPHeaderField:@"Accept"]] || [[self defaultAcceptablePathExtensions] containsObject:[[request URL] pathExtension]]; + return [[[request URL] pathExtension] isEqualToString:@"xml"] || [super canProcessRequest:request]; } - (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success diff --git a/Mac Example/AFNetworking Mac Example.xcodeproj/project.pbxproj b/Mac Example/AFNetworking Mac Example.xcodeproj/project.pbxproj index d06a8c5..5d8a7dd 100644 --- a/Mac Example/AFNetworking Mac Example.xcodeproj/project.pbxproj +++ b/Mac Example/AFNetworking Mac Example.xcodeproj/project.pbxproj @@ -25,6 +25,7 @@ F8A27AC9142CFE1300F5E0D6 /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = F8A27AB9142CFE1300F5E0D6 /* Credits.rtf */; }; F8A27ACA142CFE1300F5E0D6 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = F8A27ABB142CFE1300F5E0D6 /* MainMenu.xib */; }; F8A27ACB142CFE1300F5E0D6 /* JSONKit.m in Sources */ = {isa = PBXBuildFile; fileRef = F8A27AC0142CFE1300F5E0D6 /* JSONKit.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc -w -Xanalyzer -analyzer-disable-checker"; }; }; + F8BF7FE4153234BB00885289 /* AFDownloadRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = F8BF7FE3153234BB00885289 /* AFDownloadRequestOperation.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; F8CEEB6F142CEC6E00247B03 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8CEEB6E142CEC6E00247B03 /* Cocoa.framework */; }; /* End PBXBuildFile section */ @@ -63,6 +64,8 @@ F8A27AC0142CFE1300F5E0D6 /* JSONKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSONKit.m; sourceTree = ""; }; F8A27AC4142CFE1300F5E0D6 /* Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Prefix.pch; sourceTree = ""; }; F8A27AC5142CFE1300F5E0D6 /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; + F8BF7FE2153234BB00885289 /* AFDownloadRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AFDownloadRequestOperation.h; sourceTree = ""; }; + F8BF7FE3153234BB00885289 /* AFDownloadRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AFDownloadRequestOperation.m; sourceTree = ""; }; F8CEEB6A142CEC6E00247B03 /* AFNetworking Mac Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "AFNetworking Mac Example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; F8CEEB6E142CEC6E00247B03 /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; F8CEEB71142CEC6E00247B03 /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = System/Library/Frameworks/AppKit.framework; sourceTree = SDKROOT; }; @@ -128,6 +131,8 @@ F87A159E1444926300318955 /* AFURLConnectionOperation.m */, F897DE6B142CFEDC000DDD35 /* AFHTTPRequestOperation.h */, F897DE6C142CFEDC000DDD35 /* AFHTTPRequestOperation.m */, + F8BF7FE2153234BB00885289 /* AFDownloadRequestOperation.h */, + F8BF7FE3153234BB00885289 /* AFDownloadRequestOperation.m */, F897DE71142CFEDC000DDD35 /* AFJSONRequestOperation.h */, F897DE72142CFEDC000DDD35 /* AFJSONRequestOperation.m */, F87A15A01444926900318955 /* AFXMLRequestOperation.h */, @@ -287,6 +292,7 @@ F87A159F1444926300318955 /* AFURLConnectionOperation.m in Sources */, F897DE78142CFEDC000DDD35 /* AFHTTPClient.m in Sources */, F897DE79142CFEDC000DDD35 /* AFHTTPRequestOperation.m in Sources */, + F8BF7FE4153234BB00885289 /* AFDownloadRequestOperation.m in Sources */, F897DE7B142CFEDC000DDD35 /* AFImageRequestOperation.m in Sources */, F897DE7C142CFEDC000DDD35 /* AFJSONRequestOperation.m in Sources */, F87A15A21444926A00318955 /* AFXMLRequestOperation.m in Sources */,