From 5b7a2cb05f1617a9e7da2f4e5a19eeb7588e8a83 Mon Sep 17 00:00:00 2001 From: Mattt Thompson Date: Fri, 2 Mar 2012 10:42:24 -0800 Subject: [PATCH] Using class method swizzling to dynamically modify class method values for acceptable status codes and content types --- AFNetworking/AFHTTPRequestOperation.h | 43 ++++++++------ AFNetworking/AFHTTPRequestOperation.m | 57 ++++++++++++------- AFNetworking/AFImageRequestOperation.m | 9 +-- AFNetworking/AFJSONRequestOperation.m | 16 +----- AFNetworking/AFPropertyListRequestOperation.m | 9 +-- AFNetworking/AFXMLRequestOperation.m | 16 +----- 6 files changed, 74 insertions(+), 76 deletions(-) diff --git a/AFNetworking/AFHTTPRequestOperation.h b/AFNetworking/AFHTTPRequestOperation.h index 122099e..1c32631 100644 --- a/AFNetworking/AFHTTPRequestOperation.h +++ b/AFNetworking/AFHTTPRequestOperation.h @@ -28,8 +28,6 @@ */ @interface AFHTTPRequestOperation : AFURLConnectionOperation { @private - NSIndexSet *_acceptableStatusCodes; - NSSet *_acceptableContentTypes; NSError *_HTTPError; dispatch_queue_t _successCallbackQueue; dispatch_queue_t _failureCallbackQueue; @@ -49,25 +47,11 @@ /// @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`. */ @@ -83,6 +67,33 @@ */ @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; + +/** + + */ ++ (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; + +/** + + */ ++ (void)addAcceptableContentTypes:(NSSet *)contentTypes; /** 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 732b854..aa9d612 100644 --- a/AFNetworking/AFHTTPRequestOperation.m +++ b/AFNetworking/AFHTTPRequestOperation.m @@ -22,6 +22,15 @@ #import "AFHTTPRequestOperation.h" +#import + +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) cStringUsingEncoding:NSUTF8StringEncoding]), selector, implementation, method_getTypeEncoding(originalMethod)); + +} + static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) { NSMutableString *string = [NSMutableString string]; @@ -59,27 +68,11 @@ static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) { @end @implementation AFHTTPRequestOperation -@synthesize acceptableStatusCodes = _acceptableStatusCodes; -@synthesize acceptableContentTypes = _acceptableContentTypes; @synthesize HTTPError = _HTTPError; @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; -} - - (void)dealloc { - [_acceptableStatusCodes release]; - [_acceptableContentTypes release]; [_HTTPError release]; if (_successCallbackQueue) { @@ -103,13 +96,13 @@ static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) { 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]; @@ -124,11 +117,11 @@ static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) { } - (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 { @@ -183,6 +176,30 @@ static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) { #pragma mark - AFHTTPClientOperation ++ (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; } diff --git a/AFNetworking/AFImageRequestOperation.m b/AFNetworking/AFImageRequestOperation.m index de8237e..52426b1 100644 --- a/AFNetworking/AFImageRequestOperation.m +++ b/AFNetworking/AFImageRequestOperation.m @@ -38,7 +38,6 @@ static dispatch_queue_t image_request_operation_processing_queue() { @property (readwrite, nonatomic, retain) NSImage *responseImage; #endif -+ (NSSet *)defaultAcceptableContentTypes; + (NSSet *)defaultAcceptablePathExtensions; @end @@ -126,7 +125,7 @@ static dispatch_queue_t image_request_operation_processing_queue() { } #endif -+ (NSSet *)defaultAcceptableContentTypes { ++ (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]; } @@ -139,9 +138,7 @@ static dispatch_queue_t image_request_operation_processing_queue() { if (!self) { return nil; } - - self.acceptableContentTypes = [[self class] defaultAcceptableContentTypes]; - + #if __IPHONE_OS_VERSION_MIN_REQUIRED self.imageScale = [[UIScreen mainScreen] scale]; #endif @@ -193,7 +190,7 @@ 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]]; + return [[[self class] acceptableContentTypes] containsObject:[request valueForHTTPHeaderField:@"Accept"]] || [[self defaultAcceptablePathExtensions] containsObject:[[request URL] pathExtension]]; } #if __IPHONE_OS_VERSION_MIN_REQUIRED diff --git a/AFNetworking/AFJSONRequestOperation.m b/AFNetworking/AFJSONRequestOperation.m index 9046dd0..651f2d7 100644 --- a/AFNetworking/AFJSONRequestOperation.m +++ b/AFNetworking/AFJSONRequestOperation.m @@ -36,7 +36,6 @@ static dispatch_queue_t json_request_operation_processing_queue() { @property (readwrite, nonatomic, retain) id responseJSON; @property (readwrite, nonatomic, retain) NSError *JSONError; -+ (NSSet *)defaultAcceptableContentTypes; + (NSSet *)defaultAcceptablePathExtensions; @end @@ -62,7 +61,7 @@ static dispatch_queue_t json_request_operation_processing_queue() { return requestOperation; } -+ (NSSet *)defaultAcceptableContentTypes { ++ (NSSet *)acceptableContentTypes { return [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil]; } @@ -71,18 +70,7 @@ static dispatch_queue_t json_request_operation_processing_queue() { } + (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; + return [[self acceptableContentTypes] containsObject:[request valueForHTTPHeaderField:@"Accept"]] || [[self defaultAcceptablePathExtensions] containsObject:[[request URL] pathExtension]]; } - (void)dealloc { diff --git a/AFNetworking/AFPropertyListRequestOperation.m b/AFNetworking/AFPropertyListRequestOperation.m index ea9d216..b5609b3 100644 --- a/AFNetworking/AFPropertyListRequestOperation.m +++ b/AFNetworking/AFPropertyListRequestOperation.m @@ -36,7 +36,6 @@ static dispatch_queue_t property_list_request_operation_processing_queue() { @property (readwrite, nonatomic, assign) NSPropertyListFormat propertyListFormat; @property (readwrite, nonatomic, retain) NSError *propertyListError; -+ (NSSet *)defaultAcceptableContentTypes; + (NSSet *)defaultAcceptablePathExtensions; @end @@ -64,7 +63,7 @@ static dispatch_queue_t property_list_request_operation_processing_queue() { return requestOperation; } -+ (NSSet *)defaultAcceptableContentTypes { ++ (NSSet *)acceptableContentTypes { return [NSSet setWithObjects:@"application/x-plist", nil]; } @@ -77,9 +76,7 @@ static dispatch_queue_t property_list_request_operation_processing_queue() { if (!self) { return nil; } - - self.acceptableContentTypes = [[self class] defaultAcceptableContentTypes]; - + self.propertyListReadOptions = NSPropertyListImmutable; return self; @@ -112,7 +109,7 @@ static dispatch_queue_t property_list_request_operation_processing_queue() { } + (BOOL)canProcessRequest:(NSURLRequest *)request { - return [[self defaultAcceptableContentTypes] containsObject:[request valueForHTTPHeaderField:@"Accept"]] || [[self defaultAcceptablePathExtensions] containsObject:[[request URL] pathExtension]]; + return [[[self class] acceptableContentTypes] containsObject:[request valueForHTTPHeaderField:@"Accept"]] || [[self defaultAcceptablePathExtensions] containsObject:[[request URL] pathExtension]]; } - (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success diff --git a/AFNetworking/AFXMLRequestOperation.m b/AFNetworking/AFXMLRequestOperation.m index 79c5272..25a3765 100644 --- a/AFNetworking/AFXMLRequestOperation.m +++ b/AFNetworking/AFXMLRequestOperation.m @@ -40,7 +40,6 @@ static dispatch_queue_t xml_request_operation_processing_queue() { #endif @property (readwrite, nonatomic, retain) NSError *XMLError; -+ (NSSet *)defaultAcceptableContentTypes; + (NSSet *)defaultAcceptablePathExtensions; @end @@ -91,7 +90,7 @@ static dispatch_queue_t xml_request_operation_processing_queue() { } #endif -+ (NSSet *)defaultAcceptableContentTypes { ++ (NSSet *)acceptableContentTypes { return [NSSet setWithObjects:@"application/xml", @"text/xml", nil]; } @@ -99,17 +98,6 @@ static dispatch_queue_t xml_request_operation_processing_queue() { 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]; @@ -159,7 +147,7 @@ static dispatch_queue_t xml_request_operation_processing_queue() { } + (BOOL)canProcessRequest:(NSURLRequest *)request { - return [[self defaultAcceptableContentTypes] containsObject:[request valueForHTTPHeaderField:@"Accept"]] || [[self defaultAcceptablePathExtensions] containsObject:[[request URL] pathExtension]]; + return [[[self class] acceptableContentTypes] containsObject:[request valueForHTTPHeaderField:@"Accept"]] || [[self defaultAcceptablePathExtensions] containsObject:[[request URL] pathExtension]]; } - (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success