Using class method swizzling to dynamically modify class method values for acceptable status codes and content types

This commit is contained in:
Mattt Thompson 2012-03-02 10:42:24 -08:00
parent d2b02c3f17
commit 5b7a2cb05f
6 changed files with 74 additions and 76 deletions

View file

@ -28,8 +28,6 @@
*/ */
@interface AFHTTPRequestOperation : AFURLConnectionOperation { @interface AFHTTPRequestOperation : AFURLConnectionOperation {
@private @private
NSIndexSet *_acceptableStatusCodes;
NSSet *_acceptableContentTypes;
NSError *_HTTPError; NSError *_HTTPError;
dispatch_queue_t _successCallbackQueue; dispatch_queue_t _successCallbackQueue;
dispatch_queue_t _failureCallbackQueue; dispatch_queue_t _failureCallbackQueue;
@ -49,25 +47,11 @@
/// @name Managing And Checking For Acceptable HTTP Responses /// @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`. 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; @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`. 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; @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`. 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`.

View file

@ -22,6 +22,15 @@
#import "AFHTTPRequestOperation.h" #import "AFHTTPRequestOperation.h"
#import <objc/runtime.h>
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) { static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) {
NSMutableString *string = [NSMutableString string]; NSMutableString *string = [NSMutableString string];
@ -59,27 +68,11 @@ static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) {
@end @end
@implementation AFHTTPRequestOperation @implementation AFHTTPRequestOperation
@synthesize acceptableStatusCodes = _acceptableStatusCodes;
@synthesize acceptableContentTypes = _acceptableContentTypes;
@synthesize HTTPError = _HTTPError; @synthesize HTTPError = _HTTPError;
@synthesize successCallbackQueue = _successCallbackQueue; @synthesize successCallbackQueue = _successCallbackQueue;
@synthesize failureCallbackQueue = _failureCallbackQueue; @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 { - (void)dealloc {
[_acceptableStatusCodes release];
[_acceptableContentTypes release];
[_HTTPError release]; [_HTTPError release];
if (_successCallbackQueue) { if (_successCallbackQueue) {
@ -103,13 +96,13 @@ static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) {
if (self.response && !self.HTTPError) { if (self.response && !self.HTTPError) {
if (![self hasAcceptableStatusCode]) { if (![self hasAcceptableStatusCode]) {
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; 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]; [userInfo setValue:[self.request URL] forKey:NSURLErrorFailingURLErrorKey];
self.HTTPError = [[[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorBadServerResponse userInfo:userInfo] autorelease]; 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 } else if ([self.responseData length] > 0 && ![self hasAcceptableContentType]) { // Don't invalidate content type if there is no content
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; 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]; [userInfo setValue:[self.request URL] forKey:NSURLErrorFailingURLErrorKey];
self.HTTPError = [[[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:userInfo] autorelease]; self.HTTPError = [[[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:userInfo] autorelease];
@ -124,11 +117,11 @@ static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) {
} }
- (BOOL)hasAcceptableStatusCode { - (BOOL)hasAcceptableStatusCode {
return !self.acceptableStatusCodes || [self.acceptableStatusCodes containsIndex:[self.response statusCode]]; return ![[self class] acceptableStatusCodes] || [[[self class] acceptableStatusCodes] containsIndex:[self.response statusCode]];
} }
- (BOOL)hasAcceptableContentType { - (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 { - (void)setSuccessCallbackQueue:(dispatch_queue_t)successCallbackQueue {
@ -183,6 +176,30 @@ static NSString * AFStringFromIndexSet(NSIndexSet *indexSet) {
#pragma mark - AFHTTPClientOperation #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 { + (BOOL)canProcessRequest:(NSURLRequest *)request {
return YES; return YES;
} }

View file

@ -38,7 +38,6 @@ static dispatch_queue_t image_request_operation_processing_queue() {
@property (readwrite, nonatomic, retain) NSImage *responseImage; @property (readwrite, nonatomic, retain) NSImage *responseImage;
#endif #endif
+ (NSSet *)defaultAcceptableContentTypes;
+ (NSSet *)defaultAcceptablePathExtensions; + (NSSet *)defaultAcceptablePathExtensions;
@end @end
@ -126,7 +125,7 @@ static dispatch_queue_t image_request_operation_processing_queue() {
} }
#endif #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]; 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];
} }
@ -140,8 +139,6 @@ static dispatch_queue_t image_request_operation_processing_queue() {
return nil; return nil;
} }
self.acceptableContentTypes = [[self class] defaultAcceptableContentTypes];
#if __IPHONE_OS_VERSION_MIN_REQUIRED #if __IPHONE_OS_VERSION_MIN_REQUIRED
self.imageScale = [[UIScreen mainScreen] scale]; self.imageScale = [[UIScreen mainScreen] scale];
#endif #endif
@ -193,7 +190,7 @@ static dispatch_queue_t image_request_operation_processing_queue() {
#pragma mark - AFHTTPClientOperation #pragma mark - AFHTTPClientOperation
+ (BOOL)canProcessRequest:(NSURLRequest *)request { + (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 #if __IPHONE_OS_VERSION_MIN_REQUIRED

View file

@ -36,7 +36,6 @@ static dispatch_queue_t json_request_operation_processing_queue() {
@property (readwrite, nonatomic, retain) id responseJSON; @property (readwrite, nonatomic, retain) id responseJSON;
@property (readwrite, nonatomic, retain) NSError *JSONError; @property (readwrite, nonatomic, retain) NSError *JSONError;
+ (NSSet *)defaultAcceptableContentTypes;
+ (NSSet *)defaultAcceptablePathExtensions; + (NSSet *)defaultAcceptablePathExtensions;
@end @end
@ -62,7 +61,7 @@ static dispatch_queue_t json_request_operation_processing_queue() {
return requestOperation; return requestOperation;
} }
+ (NSSet *)defaultAcceptableContentTypes { + (NSSet *)acceptableContentTypes {
return [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil]; 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 { + (BOOL)canProcessRequest:(NSURLRequest *)request {
return [[self defaultAcceptableContentTypes] containsObject:[request valueForHTTPHeaderField:@"Accept"]] || [[self defaultAcceptablePathExtensions] containsObject:[[request URL] pathExtension]]; return [[self acceptableContentTypes] 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 { - (void)dealloc {

View file

@ -36,7 +36,6 @@ static dispatch_queue_t property_list_request_operation_processing_queue() {
@property (readwrite, nonatomic, assign) NSPropertyListFormat propertyListFormat; @property (readwrite, nonatomic, assign) NSPropertyListFormat propertyListFormat;
@property (readwrite, nonatomic, retain) NSError *propertyListError; @property (readwrite, nonatomic, retain) NSError *propertyListError;
+ (NSSet *)defaultAcceptableContentTypes;
+ (NSSet *)defaultAcceptablePathExtensions; + (NSSet *)defaultAcceptablePathExtensions;
@end @end
@ -64,7 +63,7 @@ static dispatch_queue_t property_list_request_operation_processing_queue() {
return requestOperation; return requestOperation;
} }
+ (NSSet *)defaultAcceptableContentTypes { + (NSSet *)acceptableContentTypes {
return [NSSet setWithObjects:@"application/x-plist", nil]; return [NSSet setWithObjects:@"application/x-plist", nil];
} }
@ -78,8 +77,6 @@ static dispatch_queue_t property_list_request_operation_processing_queue() {
return nil; return nil;
} }
self.acceptableContentTypes = [[self class] defaultAcceptableContentTypes];
self.propertyListReadOptions = NSPropertyListImmutable; self.propertyListReadOptions = NSPropertyListImmutable;
return self; return self;
@ -112,7 +109,7 @@ static dispatch_queue_t property_list_request_operation_processing_queue() {
} }
+ (BOOL)canProcessRequest:(NSURLRequest *)request { + (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 - (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success

View file

@ -40,7 +40,6 @@ static dispatch_queue_t xml_request_operation_processing_queue() {
#endif #endif
@property (readwrite, nonatomic, retain) NSError *XMLError; @property (readwrite, nonatomic, retain) NSError *XMLError;
+ (NSSet *)defaultAcceptableContentTypes;
+ (NSSet *)defaultAcceptablePathExtensions; + (NSSet *)defaultAcceptablePathExtensions;
@end @end
@ -91,7 +90,7 @@ static dispatch_queue_t xml_request_operation_processing_queue() {
} }
#endif #endif
+ (NSSet *)defaultAcceptableContentTypes { + (NSSet *)acceptableContentTypes {
return [NSSet setWithObjects:@"application/xml", @"text/xml", nil]; 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]; 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 { - (void)dealloc {
[_responseXMLParser release]; [_responseXMLParser release];
@ -159,7 +147,7 @@ static dispatch_queue_t xml_request_operation_processing_queue() {
} }
+ (BOOL)canProcessRequest:(NSURLRequest *)request { + (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 - (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success