First implementation of protocol for AFHTTPClient to register classes to automatically determine the appropriate operation to create

Wrapping AFURLConnectionOperation -setCompletionBlock: with blockSelf release pattern to avoid retain cycle
This commit is contained in:
Mattt Thompson 2011-10-05 15:44:51 -05:00
parent 2c478758a2
commit c2be31d4fa
12 changed files with 191 additions and 48 deletions

View File

@ -49,6 +49,7 @@
@private
NSURL *_baseURL;
NSStringEncoding _stringEncoding;
NSMutableArray *_registeredHTTPOperationClassNames;
NSMutableDictionary *_defaultHeaders;
NSOperationQueue *_operationQueue;
}
@ -96,6 +97,12 @@
*/
- (id)initWithBaseURL:(NSURL *)url;
///----------------------------------
/// @name Managing HTTP Operations
///----------------------------------
- (BOOL)registerHTTPOperationClass:(Class)operationClass;
///----------------------------------
/// @name Managing HTTP Header Values
///----------------------------------

View File

@ -77,6 +77,7 @@ static NSString * AFURLEncodedStringFromStringWithEncoding(NSString *string, NSS
@interface AFHTTPClient ()
@property (readwrite, nonatomic, retain) NSURL *baseURL;
@property (readwrite, nonatomic, retain) NSMutableArray *registeredHTTPOperationClassNames;
@property (readwrite, nonatomic, retain) NSMutableDictionary *defaultHeaders;
@property (readwrite, nonatomic, retain) NSOperationQueue *operationQueue;
@end
@ -84,6 +85,7 @@ static NSString * AFURLEncodedStringFromStringWithEncoding(NSString *string, NSS
@implementation AFHTTPClient
@synthesize baseURL = _baseURL;
@synthesize stringEncoding = _stringEncoding;
@synthesize registeredHTTPOperationClassNames = _registeredHTTPOperationClassNames;
@synthesize defaultHeaders = _defaultHeaders;
@synthesize operationQueue = _operationQueue;
@ -101,6 +103,8 @@ static NSString * AFURLEncodedStringFromStringWithEncoding(NSString *string, NSS
self.stringEncoding = NSUTF8StringEncoding;
self.registeredHTTPOperationClassNames = [NSMutableArray array];
self.defaultHeaders = [NSMutableDictionary dictionary];
// Accept HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
@ -124,11 +128,28 @@ static NSString * AFURLEncodedStringFromStringWithEncoding(NSString *string, NSS
- (void)dealloc {
[_baseURL release];
[_registeredHTTPOperationClassNames release];
[_defaultHeaders release];
[_operationQueue release];
[super dealloc];
}
#pragma mark -
- (BOOL)registerHTTPOperationClass:(Class)operationClass {
if (![operationClass conformsToProtocol:@protocol(AFHTTPClientOperation)]) {
return NO;
}
NSString *className = NSStringFromClass(operationClass);
[self.registeredHTTPOperationClassNames removeObject:className];
[self.registeredHTTPOperationClassNames insertObject:className atIndex:0];
return YES;
}
#pragma mark -
- (NSString *)defaultValueForHeader:(NSString *)header {
return [self.defaultHeaders valueForKey:header];
}
@ -228,16 +249,16 @@ static NSString * AFURLEncodedStringFromStringWithEncoding(NSString *string, NSS
success:(void (^)(id object))success
failure:(void (^)(NSHTTPURLResponse *response, NSError *error))failure
{
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:urlRequest success:^(__unused NSURLRequest *request, __unused NSHTTPURLResponse *response, id JSON) {
if (success) {
success(JSON);
AFHTTPRequestOperation *operation = nil;
NSString *className = nil;
NSEnumerator *enumerator = [self.registeredHTTPOperationClassNames reverseObjectEnumerator];
while (!operation && (className = [enumerator nextObject])) {
Class class = NSClassFromString(className);
if (class && [class canProcessRequest:urlRequest]) {
operation = [class HTTPRequestOperationWithRequest:urlRequest success:success failure:failure];
}
} failure:^(__unused NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
if (failure) {
failure(response, error);
}
}];
}
[self.operationQueue addOperation:operation];
}

View File

@ -23,9 +23,12 @@
#import <Foundation/Foundation.h>
#import "AFURLConnectionOperation.h"
@protocol AFHTTPClientRequestOperation
+ (BOOL)canInitWithRequest:(NSURLRequest *)request;
@protocol AFHTTPClientOperation <NSObject>
+ (BOOL)canProcessRequest:(NSURLRequest *)request;
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request;
+ (id)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(id object))success
failure:(void (^)(NSHTTPURLResponse *response, NSError *error))failure;
@end
/**
@ -59,7 +62,7 @@
@see NSOperation
@see NSURLConnection
*/
@interface AFHTTPRequestOperation : AFURLConnectionOperation {
@interface AFHTTPRequestOperation : AFURLConnectionOperation <AFHTTPClientOperation> {
@private
NSIndexSet *_acceptableStatusCodes;
NSSet *_acceptableContentTypes;
@ -80,9 +83,9 @@
/// @name Creating HTTP Request Operations
///---------------------------------------
+ (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSData *data))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure;
//+ (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest
// success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSData *data))success
// failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure;
///-------------------------------
/// @name Validating HTTP Response

View File

@ -31,34 +31,6 @@
@synthesize acceptableContentTypes = _acceptableContentTypes;
@synthesize error = _HTTPError;
+ (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSData *data))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure
{
AFHTTPRequestOperation *operation = [[[self alloc] initWithRequest:urlRequest] autorelease];
operation.completionBlock = ^ {
if ([operation isCancelled]) {
return;
}
if (operation.error) {
if (failure) {
dispatch_async(dispatch_get_main_queue(), ^(void) {
failure(operation.request, operation.response, operation.error);
});
}
} else {
if (success) {
dispatch_async(dispatch_get_main_queue(), ^(void) {
success(operation.request, operation.response, operation.responseData);
});
}
}
};
return operation;
}
- (id)initWithRequest:(NSURLRequest *)request {
self = [super initWithRequest:request];
if (!self) {
@ -108,4 +80,42 @@
return !self.acceptableContentTypes || [self.acceptableContentTypes containsObject:[self.response MIMEType]];
}
#pragma mark - AFHTTPClientOperation
+ (BOOL)canProcessRequest:(NSURLRequest *)request {
return NO;
}
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
return request;
}
+ (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(id object))success
failure:(void (^)(NSHTTPURLResponse *response, NSError *error))failure
{
AFHTTPRequestOperation *operation = [[[self alloc] initWithRequest:urlRequest] autorelease];
operation.completionBlock = ^ {
if ([operation isCancelled]) {
return;
}
if (operation.error) {
if (failure) {
dispatch_async(dispatch_get_main_queue(), ^(void) {
failure(operation.response, operation.error);
});
}
} else {
if (success) {
dispatch_async(dispatch_get_main_queue(), ^(void) {
success(operation.responseData);
});
}
}
};
return operation;
}
@end

View File

@ -122,4 +122,27 @@ static dispatch_queue_t image_request_operation_processing_queue() {
return _responseImage;
}
#pragma mark - AFHTTPClientOperation
+ (BOOL)canProcessRequest:(NSURLRequest *)request {
NSSet *acceptableContentTypes = [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 *acceptablePathExtensions = [NSSet setWithObjects:@"tif", @"tiff", @"jpg", @"jpeg", @"gif", @"png", @"ico", @"bmp", @"cur", nil];
return [acceptableContentTypes containsObject:[request valueForHTTPHeaderField:@"Accept"]] || [acceptablePathExtensions containsObject:[[request URL] pathExtension]];
}
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
return request;
}
+ (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);
}];
}
@end

View File

@ -120,4 +120,26 @@ static dispatch_queue_t json_request_operation_processing_queue() {
return _responseJSON;
}
#pragma mark - AFHTTPClientOperation
+ (BOOL)canProcessRequest:(NSURLRequest *)request {
NSSet *acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"application/x-javascript", @"text/javascript", @"text/x-javascript", @"text/x-json", @"text/json", @"text/plain", nil];
return [acceptableContentTypes containsObject:[request valueForHTTPHeaderField:@"Accept"]] || [[[request URL] pathExtension] isEqualToString:@"json"];
}
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
return request;
}
+ (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(id object))success
failure:(void (^)(NSHTTPURLResponse *response, NSError *error))failure
{
return [self JSONRequestOperationWithRequest:urlRequest success:^(NSURLRequest __unused *request, NSHTTPURLResponse __unused *response, id JSON) {
success(JSON);
} failure:^(NSURLRequest __unused *request, NSHTTPURLResponse *response, NSError *error) {
failure(response, error);
}];
}
@end

View File

@ -113,4 +113,26 @@ static dispatch_queue_t property_list_request_operation_processing_queue() {
return _responsePropertyList;
}
#pragma mark - AFHTTPClientOperation
+ (BOOL)canProcessRequest:(NSURLRequest *)request {
NSSet *acceptableContentTypes = [NSSet setWithObjects:@"application/x-plist", @"application/xml", nil];
return [acceptableContentTypes containsObject:[request valueForHTTPHeaderField:@"Accept"]] || [[[request URL] pathExtension] isEqualToString:@"plist"];
}
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
return request;
}
+ (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(id object))success
failure:(void (^)(NSHTTPURLResponse *response, NSError *error))failure
{
return [self propertyListRequestOperationWithRequest:urlRequest success:^(NSURLRequest __unused *request, NSHTTPURLResponse __unused *response, id propertyList) {
success(propertyList);
} failure:^(NSURLRequest __unused *request, NSHTTPURLResponse *response, NSError *error) {
failure(response, error);
}];
}
@end

View File

@ -142,6 +142,18 @@ static inline NSString * AFKeyPathFromOperationState(AFOperationState state) {
[super dealloc];
}
- (void)setCompletionBlock:(void (^)(void))block {
if (!block) {
[super setCompletionBlock:nil];
}
__block id _blockSelf = [self retain];
[super setCompletionBlock:^ {
block();
[_blockSelf autorelease];
}];
}
- (NSInputStream *)inputStream {
return self.request.HTTPBodyStream;
}
@ -194,8 +206,6 @@ static inline NSString * AFKeyPathFromOperationState(AFOperationState state) {
switch (state) {
case AFHTTPOperationExecutingState:
return YES;
case AFHTTPOperationFinishedState:
return [self isCancelled];
default:
return NO;
}

View File

@ -78,7 +78,7 @@ static NSString * const kAFImageRequestOperationObjectKey = @"_af_imageRequestOp
- (void)setImageWithURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(UIImage *)placeholderImage
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response,UIImage *image))success
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure
{
if (![urlRequest URL] || (![self.af_imageRequestOperation isCancelled] && [[urlRequest URL] isEqual:self.af_imageRequestOperation.request.URL])) {

View File

@ -233,12 +233,12 @@
F874B5C913E0AA6500B28E3E /* AFHTTPRequestOperation.m */,
F874B5D413E0AA6500B28E3E /* AFJSONRequestOperation.h */,
F874B5CC13E0AA6500B28E3E /* AFJSONRequestOperation.m */,
F8F4B16C143CD1410064C9E6 /* AFPropertyListRequestOperation.h */,
F8F4B16D143CD1410064C9E6 /* AFPropertyListRequestOperation.m */,
F8FBFA96142AA237001409DB /* AFHTTPClient.h */,
F8FBFA97142AA238001409DB /* AFHTTPClient.m */,
F874B5D313E0AA6500B28E3E /* AFImageRequestOperation.h */,
F874B5CB13E0AA6500B28E3E /* AFImageRequestOperation.m */,
F8F4B16C143CD1410064C9E6 /* AFPropertyListRequestOperation.h */,
F8F4B16D143CD1410064C9E6 /* AFPropertyListRequestOperation.m */,
F874B5D213E0AA6500B28E3E /* AFImageCache.h */,
F874B5CA13E0AA6500B28E3E /* AFImageCache.m */,
F874B5D513E0AA6500B28E3E /* AFNetworkActivityIndicatorManager.h */,

View File

@ -22,6 +22,8 @@
#import "AFGowallaAPIClient.h"
#import "AFJSONRequestOperation.h"
// Replace this with your own API Key, available at http://api.gowalla.com/api/keys/
NSString * const kAFGowallaClientID = @"e7ccb7d3d2414eb2af4663fc91eb2793";
@ -54,6 +56,8 @@ NSString * const kAFGowallaBaseURLString = @"https://api.gowalla.com/";
// X-UDID HTTP Header
[self setDefaultHeader:@"X-UDID" value:[[UIDevice currentDevice] uniqueIdentifier]];
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
return self;
}

View File

@ -43,6 +43,7 @@
self.detailTextLabel.backgroundColor = self.backgroundColor;
self.imageView.backgroundColor = self.backgroundColor;
self.imageView.contentMode = UIViewContentModeScaleAspectFit;
self.selectionStyle = UITableViewCellSelectionStyleGray;
@ -73,4 +74,24 @@
self.detailTextLabel.text = nil;
}
#pragma mark - UIView
- (void)layoutSubviews {
[super layoutSubviews];
CGRect imageViewFrame = self.imageView.frame;
CGRect textLabelFrame = self.textLabel.frame;
CGRect detailTextLabelFrame = self.detailTextLabel.frame;
imageViewFrame.origin = CGPointMake(10.0f, 10.0f);
imageViewFrame.size = CGSizeMake(50.0f, 50.0f);
textLabelFrame.origin.x = imageViewFrame.size.width + 25.0f;
detailTextLabelFrame.origin.x = textLabelFrame.origin.x;
textLabelFrame.size.width = 240.0f;
detailTextLabelFrame.size.width = textLabelFrame.size.width;
self.textLabel.frame = textLabelFrame;
self.detailTextLabel.frame = detailTextLabelFrame;
self.imageView.frame = imageViewFrame;
}
@end