Merge branch 'experimental-0.6'

Conflicts:
	AFNetworking/NSData+AFNetworking.m
This commit is contained in:
Mattt Thompson 2011-09-22 12:06:23 -05:00
commit 432dd0a61e
32 changed files with 1141 additions and 995 deletions

281
AFNetworking/AFHTTPClient.h Normal file
View file

@ -0,0 +1,281 @@
// AFHTTPClient.h
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
#import "AFHTTPRequestOperation.h"
@protocol AFMultipartFormDataProxy;
/**
`AFHTTPClient` objects encapsulates the common patterns of communicating with an application, webservice, or API. It encapsulates persistent information, like base URL, authorization credentials, and HTTP headers, and uses them to construct and manage the execution of HTTP request operations.
*/
@interface AFHTTPClient : NSObject {
@private
NSURL *_baseURL;
NSStringEncoding _stringEncoding;
NSMutableDictionary *_defaultHeaders;
NSOperationQueue *_operationQueue;
}
/**
The url used as the base for paths specified in methods such as `getPath:parameteres:success:failure`
*/
@property (readonly, nonatomic, retain) NSURL *baseURL;
/**
The string encoding used in constructing url requests. This is `NSUTF8StringEncoding` by default.
*/
@property (nonatomic, assign) NSStringEncoding stringEncoding;
/**
The operation queue which manages operations enqueued by the HTTP client.
*/
@property (readonly, nonatomic, retain) NSOperationQueue *operationQueue;;
///---------------------------------------------
/// @name Creating and Initializing HTTP Clients
///---------------------------------------------
/**
Creates and initializes an `AFHTTPClient` object with the specified base URL.
@param url The base URL for the HTTP client. This argument must not be nil.
@return The newly-initialized HTTP client
*/
+ (AFHTTPClient *)clientWithBaseURL:(NSURL *)url;
/**
Initializes an `AFHTTPClient` object with the specified base URL.
@param url The base URL for the HTTP client. This argument must not be nil.
@discussion This is the designated initializer for `AFHTTPClient`
@return The newly-initialized HTTP client
*/
- (id)initWithBaseURL:(NSURL *)url;
///----------------------------------
/// @name Managing HTTP Header Values
///----------------------------------
/**
Returns the value for the HTTP headers set in request objects created by the HTTP client
@param header The HTTP header to return the default value for
@return The default value for the HTTP header, or `nil` if unspecified
*/
- (NSString *)defaultValueForHeader:(NSString *)header;
/**
Sets the value for the HTTP headers set in request objects made by the HTTP client. If `nil`, removes the existing value for that header.
@param header The HTTP header to set a default value for
@param value The value set as default for the specified header, or `nil
*/
- (void)setDefaultHeader:(NSString *)header value:(NSString *)value;
/**
Sets the "Authorization" HTTP header set in request objects made by the HTTP client to a basic authentication value with Base64-encoded username and password. This overwrites any existing value for this header.
@param username The HTTP basic auth username
@param password The HTTP basic auth password
*/
- (void)setAuthorizationHeaderWithUsername:(NSString *)username password:(NSString *)password;
/**
Sets the "Authorization" HTTP header set in request objects made by the HTTP client to a token-based authentication value, such as an OAuth access token. This overwrites any existing value for this header.
@param token The authentication token
*/
- (void)setAuthorizationHeaderWithToken:(NSString *)token;
/**
Clears any existing value for the "Authorization" HTTP header.
*/
- (void)clearAuthorizationHeader;
///-------------------------------
/// @name Creating Request Objects
///-------------------------------
/**
Creates an `NSMutableURLRequest` object with the specified HTTP method and path. If the HTTP method is `GET`, the parameters will be used to construct a url-encoded query string that is appended to the request's URL. If `POST`, `PUT`, or `DELETE`, the parameters will be encoded into a `application/x-www-form-urlencoded` HTTP body.
@param method The HTTP method for the request, such as `GET`, `POST`, `PUT`, or `DELETE`.
@param path The path to be appended to the HTTP client's base URL and used as the request URL.
@param parameters The parameters to be either set as a query string for `GET` requests, or the request HTTP body.
@return An `NSMutableURLRequest` object
*/
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
path:(NSString *)path parameters:(NSDictionary *)parameters;
/**
Creates an `NSMutableURLRequest` object with the specified HTTP method and path, and constructs a `multipart/form-data` HTTP body, using the specified parameters and multipart form data block.
@param method The HTTP method for the request. Must be either `POST`, `PUT`, or `DELETE`.
@param path The path to be appended to the HTTP client's base URL and used as the request URL.
@param parameters The parameters to be encoded and set in the request HTTP body.
@param block A block that takes a single argument and appends data to the HTTP body. The block argument is an object adopting the `AFMultipartFormDataProxy` protocol. This can be used to upload files, encode HTTP body as JSON or XML, or specify multiple values for the same parameter, as one might for array values.
@see AFMultipartFormDataProxy
@return An `NSMutableURLRequest` object
*/
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
path:(NSString *)path
parameters:(NSDictionary *)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormDataProxy>formData))block;
///--------------------------------
/// @name Enqueuing HTTP Operations
///--------------------------------
/**
Creates and enqueues an `AFHTTPRequestOperation` to the HTTP client's operation queue.
@param request The request object to be loaded asynchronously during execution of the operation.
@param success A block object to be executed when the request operation finishes successfully, with a status code in the 2xx range, and with an acceptable content type (e.g. `application/json`). This block has no return value and takes a single argument, which is the response object created from the response data of request.
@param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the resonse data as JSON. This block has no return value and takes a single argument, which is the `NSError` object describing the network or parsing error that occurred.
*/
- (void)enqueueHTTPOperationWithRequest:(NSURLRequest *)request
success:(void (^)(id response))success
failure:(void (^)(NSError *error))failure;
///---------------------------------
/// @name Cancelling HTTP Operations
///---------------------------------
/**
Cancels all operations in the HTTP client's operation queue that match the specified HTTP method and URL.
@param method The HTTP method to match for the cancelled requests, such as `GET`, `POST`, `PUT`, or `DELETE`.
@param url The URL to match for the cancelled requests.
*/
- (void)cancelHTTPOperationsWithMethod:(NSString *)method andURL:(NSURL *)url;
///---------------------------
/// @name Making HTTP Requests
///---------------------------
/**
Creates an `AFHTTPRequestOperation` with a `GET` request, and enqueues it to the HTTP client's operation queue.
@param path The path to be appended to the HTTP client's base URL and used as the request URL.
@param parameters The parameters to be encoded and appended as the query string for the request URL.
@param success A block object to be executed when the request operation finishes successfully, with a status code in the 2xx range, and with an acceptable content type (e.g. `application/json`). This block has no return value and takes a single argument, which is the response object created from the response data of request.
@param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the resonse data as JSON. This block has no return value and takes a single argument, which is the `NSError` object describing the network or parsing error that occurred.
*/
- (void)getPath:(NSString *)path
parameters:(NSDictionary *)parameters
success:(void (^)(id response))success
failure:(void (^)(NSError *error))failure;
/**
Creates an `AFHTTPRequestOperation` with a `POST` request, and enqueues it to the HTTP client's operation queue.
@param path The path to be appended to the HTTP client's base URL and used as the request URL.
@param parameters The parameters to be encoded and set in the request HTTP body.
@param success A block object to be executed when the request operation finishes successfully, with a status code in the 2xx range, and with an acceptable content type (e.g. `application/json`). This block has no return value and takes a single argument, which is the response object created from the response data of request.
@param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the resonse data as JSON. This block has no return value and takes a single argument, which is the `NSError` object describing the network or parsing error that occurred.
*/
- (void)postPath:(NSString *)path
parameters:(NSDictionary *)parameters
success:(void (^)(id response))success
failure:(void (^)(NSError *error))failure;
/**
Creates an `AFHTTPRequestOperation` with a `PUT` request, and enqueues it to the HTTP client's operation queue.
@param path The path to be appended to the HTTP client's base URL and used as the request URL.
@param parameters The parameters to be encoded and set in the request HTTP body.
@param success A block object to be executed when the request operation finishes successfully, with a status code in the 2xx range, and with an acceptable content type (e.g. `application/json`). This block has no return value and takes a single argument, which is the response object created from the response data of request.
@param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the resonse data as JSON. This block has no return value and takes a single argument, which is the `NSError` object describing the network or parsing error that occurred.
*/
- (void)putPath:(NSString *)path
parameters:(NSDictionary *)parameters
success:(void (^)(id response))success
failure:(void (^)(NSError *error))failure;
/**
Creates an `AFHTTPRequestOperation` with a `DELETE` request, and enqueues it to the HTTP client's operation queue.
@param path The path to be appended to the HTTP client's base URL and used as the request URL.
@param parameters The parameters to be encoded and set in the request HTTP body.
@param success A block object to be executed when the request operation finishes successfully, with a status code in the 2xx range, and with an acceptable content type (e.g. `application/json`). This block has no return value and takes a single argument, which is the response object created from the response data of request.
@param failure A block object to be executed when the request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the resonse data as JSON. This block has no return value and takes a single argument, which is the `NSError` object describing the network or parsing error that occurred.
*/
- (void)deletePath:(NSString *)path
parameters:(NSDictionary *)parameters
success:(void (^)(id response))success
failure:(void (^)(NSError *error))failure;
@end
#pragma mark -
/**
The `AFMultipartFormDataProxy` protocol defines the methods supported by the parameter in the block argument of `multipartFormRequestWithMethod:path:parameters:constructingBodyWithBlock:`.
*/
@protocol AFMultipartFormDataProxy <NSObject>
/**
Appends HTTP headers, followed by the encoded data and the multipart form boundary.
@param headers The HTTP headers to be appended to the form data.
@param body The data to be encoded and appended to the form data.
*/
- (void)appendPartWithHeaders:(NSDictionary *)headers body:(NSData *)body;
/**
Appends the HTTP header `Content-Disposition: form-data; name=#{name}"`, followed by the encoded data and the multipart form boundary.
@param data The data to be encoded and appended to the form data.
@param name The name to be associated with the specified data.
*/
- (void)appendPartWithFormData:(NSData *)data name:(NSString *)name;
/**
Appends the HTTP header `Content-Disposition: file; filename=#{filename}"`, followed by the encoded file data and the multipart form boundary.
@param fileURL The URL for the local file to have its contents appended to the form data.
@param body The filename to be associated with the file contents. If `nil`, the last path component followed by its file extension will be used instead.
*/
- (void)appendPartWithFile:(NSURL *)fileURL fileName:(NSString *)fileNameOrNil;
/**
Appends encoded data to the form data.
@param data The data to be encoded and appended to the form data.
*/
- (void)appendData:(NSData *)data;
/**
Appends a string to the form data.
@param string The string to be encoded and appended to the form data.
*/
- (void)appendString:(NSString *)string;
@end

351
AFNetworking/AFHTTPClient.m Normal file
View file

@ -0,0 +1,351 @@
// AFHTTPClient.m
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "AFHTTPClient.h"
#import "AFJSONRequestOperation.h"
static NSString * const kAFMultipartFormLineDelimiter = @"\r\n"; // CRLF
static NSString * const kAFMultipartFormBoundary = @"Boundary+0xAbCdEfGbOuNdArY";
static NSString * AFMultipartFormEncapsulationBoundary() {
return [NSString stringWithFormat:@"--%@", kAFMultipartFormBoundary];
}
static NSString * AFMultipartFormFinalBoundary() {
return [NSString stringWithFormat:@"--%@--", kAFMultipartFormBoundary];
}
@interface AFMutableMultipartFormData : NSObject <AFMultipartFormDataProxy> {
@private
NSStringEncoding _stringEncoding;
NSMutableArray *_mutableLines;
}
- (id)initWithStringEncoding:(NSStringEncoding)encoding;
- (NSData *)data;
@end
#pragma mark -
static NSString * AFBase64EncodedStringFromString(NSString *string) {
NSData *data = [NSData dataWithBytes:[string UTF8String] length:[string length]];
NSUInteger length = [data length];
NSMutableData *mutableData = [NSMutableData dataWithLength:((length + 2) / 3) * 4];
uint8_t *input = (uint8_t *)[data bytes];
uint8_t *output = (uint8_t *)[mutableData mutableBytes];
for (NSUInteger i = 0; i < length; i += 3) {
NSUInteger value = 0;
for (NSUInteger j = i; j < (i + 3); j++) {
value <<= 8;
if (j < length) {
value |= (0xFF & input[j]);
}
}
static char const kAFBase64EncodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
NSInteger idx = (i / 3) * 4;
output[idx + 0] = kAFBase64EncodingTable[(value >> 18) & 0x3F];
output[idx + 1] = kAFBase64EncodingTable[(value >> 12) & 0x3F];
output[idx + 2] = (i + 1) < length ? kAFBase64EncodingTable[(value >> 6) & 0x3F] : '=';
output[idx + 3] = (i + 2) < length ? kAFBase64EncodingTable[(value >> 0) & 0x3F] : '=';
}
return [[[NSString alloc] initWithData:mutableData encoding:NSASCIIStringEncoding] autorelease];
}
static NSString * AFURLEncodedStringFromStringWithEncoding(NSString *string, NSStringEncoding encoding) {
static NSString * const kAFLegalCharactersToBeEscaped = @"?!@#$^&%*+,:;='\"`<>()[]{}/\\|~ ";
return [(NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, (CFStringRef)kAFLegalCharactersToBeEscaped, CFStringConvertNSStringEncodingToEncoding(encoding)) autorelease];
}
@interface AFHTTPClient ()
@property (readwrite, nonatomic, retain) NSURL *baseURL;
@property (readwrite, nonatomic, retain) NSMutableDictionary *defaultHeaders;
@property (readwrite, nonatomic, retain) NSOperationQueue *operationQueue;
@end
@implementation AFHTTPClient
@synthesize baseURL = _baseURL;
@synthesize stringEncoding = _stringEncoding;
@synthesize defaultHeaders = _defaultHeaders;
@synthesize operationQueue = _operationQueue;
+ (AFHTTPClient *)clientWithBaseURL:(NSURL *)url {
return [[[self alloc] initWithBaseURL:url] autorelease];
}
- (id)initWithBaseURL:(NSURL *)url {
self = [super init];
if (!self) {
return nil;
}
self.baseURL = url;
self.stringEncoding = NSUTF8StringEncoding;
self.defaultHeaders = [NSMutableDictionary dictionary];
// Accept HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
[self setDefaultHeader:@"Accept" value:@"application/json"];
// Accept-Encoding HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
[self setDefaultHeader:@"Accept-Encoding" value:@"gzip"];
// Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
NSString *preferredLanguageCodes = [[NSLocale preferredLanguages] componentsJoinedByString:@", "];
[self setDefaultHeader:@"Accept-Language" value:[NSString stringWithFormat:@"%@, en-us;q=0.8", preferredLanguageCodes]];
// User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
[self setDefaultHeader:@"User-Agent" value:[NSString stringWithFormat:@"%@/%@ (%@, %@ %@, %@, Scale/%f)", [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleIdentifierKey], [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleVersionKey], @"unknown", [[UIDevice currentDevice] systemName], [[UIDevice currentDevice] systemVersion], [[UIDevice currentDevice] model], ([[UIScreen mainScreen] respondsToSelector:@selector(scale)] ? [[UIScreen mainScreen] scale] : 1.0)]];
self.operationQueue = [[[NSOperationQueue alloc] init] autorelease];
[self.operationQueue setMaxConcurrentOperationCount:2];
return self;
}
- (void)dealloc {
[_baseURL release];
[_defaultHeaders release];
[_operationQueue release];
[super dealloc];
}
- (NSString *)defaultValueForHeader:(NSString *)header {
return [self.defaultHeaders valueForKey:header];
}
- (void)setDefaultHeader:(NSString *)header value:(NSString *)value {
[self.defaultHeaders setObject:value forKey:header];
}
- (void)setAuthorizationHeaderWithUsername:(NSString *)username password:(NSString *)password {
NSString *basicAuthCredentials = [NSString stringWithFormat:@"%@:%@", username, password];
[self setDefaultHeader:@"Authorization" value:[NSString stringWithFormat:@"Basic %@", AFBase64EncodedStringFromString(basicAuthCredentials)]];
}
- (void)setAuthorizationHeaderWithToken:(NSString *)token {
[self setDefaultHeader:@"Authorization" value:[NSString stringWithFormat:@"Token token=\"%@\"", token]];
}
- (void)clearAuthorizationHeader {
[self.defaultHeaders removeObjectForKey:@"Authorization"];
}
#pragma mark -
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters {
NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] init] autorelease];
NSMutableDictionary *headers = [NSMutableDictionary dictionaryWithDictionary:self.defaultHeaders];
NSURL *url = [NSURL URLWithString:path relativeToURL:self.baseURL];
if (parameters) {
NSMutableArray *mutableParameterComponents = [NSMutableArray array];
for (id key in [parameters allKeys]) {
NSString *component = [NSString stringWithFormat:@"%@=%@", AFURLEncodedStringFromStringWithEncoding([key description], self.stringEncoding), AFURLEncodedStringFromStringWithEncoding([[parameters valueForKey:key] description], self.stringEncoding)];
[mutableParameterComponents addObject:component];
}
NSString *queryString = [mutableParameterComponents componentsJoinedByString:@"&"];
if ([method isEqualToString:@"GET"]) {
url = [NSURL URLWithString:[[url absoluteString] stringByAppendingFormat:[path rangeOfString:@"?"].location == NSNotFound ? @"?%@" : @"&%@", queryString]];
} else {
NSString *charset = (NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding(self.stringEncoding));
[headers setObject:[NSString stringWithFormat:@"application/x-www-form-urlencoded; charset=%@", charset] forKey:@"Content-Type"];
[request setHTTPBody:[queryString dataUsingEncoding:self.stringEncoding]];
}
}
[request setURL:url];
[request setHTTPMethod:method];
[request setHTTPShouldHandleCookies:NO];
[request setAllHTTPHeaderFields:headers];
return request;
}
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
path:(NSString *)path
parameters:(NSDictionary *)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormDataProxy>formData))block
{
if (!([method isEqualToString:@"POST"] || [method isEqualToString:@"PUT"] || [method isEqualToString:@"DELETE"])) {
[NSException raise:@"Invalid HTTP Method" format:@"%@ is not supported for multipart form requests; must be either POST, PUT, or DELETE", method];
return nil;
}
NSMutableURLRequest *request = [self requestWithMethod:method path:path parameters:nil];
__block AFMutableMultipartFormData *formData = [[AFMutableMultipartFormData alloc] initWithStringEncoding:self.stringEncoding];
id key = nil;
NSEnumerator *enumerator = [parameters keyEnumerator];
while ((key = [enumerator nextObject])) {
id value = [parameters valueForKey:key];
if (![value isKindOfClass:[NSData class]]) {
value = [value description];
}
[formData appendPartWithFormData:[value dataUsingEncoding:self.stringEncoding] name:[key description]];
}
if (block) {
block(formData);
}
[request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", kAFMultipartFormBoundary] forHTTPHeaderField:@"Content-Type"];
[request setHTTPBody:[formData data]];
[formData autorelease];
return request;
}
- (void)enqueueHTTPOperationWithRequest:(NSURLRequest *)request success:(void (^)(id response))success failure:(void (^)(NSError *error))failure {
if ([request URL] == nil || [[request URL] isEqual:[NSNull null]]) {
return;
}
AFHTTPRequestOperation *operation = [AFJSONRequestOperation operationWithRequest:request success:success failure:failure];
[self.operationQueue addOperation:operation];
}
- (void)cancelHTTPOperationsWithMethod:(NSString *)method andURL:(NSURL *)url {
for (AFHTTPRequestOperation *operation in [self.operationQueue operations]) {
if ([[[operation request] HTTPMethod] isEqualToString:method] && [[[operation request] URL] isEqual:url]) {
[operation cancel];
}
}
}
#pragma mark -
- (void)getPath:(NSString *)path parameters:(NSDictionary *)parameters success:(void (^)(id response))success failure:(void (^)(NSError *error))failure {
NSURLRequest *request = [self requestWithMethod:@"GET" path:path parameters:parameters];
[self enqueueHTTPOperationWithRequest:request success:success failure:failure];
}
- (void)postPath:(NSString *)path parameters:(NSDictionary *)parameters success:(void (^)(id response))success failure:(void (^)(NSError *error))failure {
NSURLRequest *request = [self requestWithMethod:@"POST" path:path parameters:parameters];
[self enqueueHTTPOperationWithRequest:request success:success failure:failure];
}
- (void)putPath:(NSString *)path parameters:(NSDictionary *)parameters success:(void (^)(id response))success failure:(void (^)(NSError *error))failure {
NSURLRequest *request = [self requestWithMethod:@"PUT" path:path parameters:parameters];
[self enqueueHTTPOperationWithRequest:request success:success failure:failure];
}
- (void)deletePath:(NSString *)path parameters:(NSDictionary *)parameters success:(void (^)(id response))success failure:(void (^)(NSError *error))failure {
NSURLRequest *request = [self requestWithMethod:@"DELETE" path:path parameters:parameters];
[self enqueueHTTPOperationWithRequest:request success:success failure:failure];
}
@end
#pragma mark -
// multipart/form-data; see http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.2
@interface AFMutableMultipartFormData ()
@property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding;
@property (readwrite, nonatomic, retain) NSMutableArray *mutableLines;
- (void)appendBlankLine;
@end
@implementation AFMutableMultipartFormData
@synthesize stringEncoding = _stringEncoding;
@synthesize mutableLines = _mutableLines;
- (id)initWithStringEncoding:(NSStringEncoding)encoding {
self = [super init];
if (!self) {
return nil;
}
self.stringEncoding = encoding;
self.mutableLines = [NSMutableArray array];
return self;
}
- (void)dealloc {
[_mutableLines release];
[super dealloc];
}
- (NSData *)data {
if ([self.mutableLines count] == 0) {
return nil;
}
return [[[[self.mutableLines componentsJoinedByString:kAFMultipartFormLineDelimiter] stringByAppendingString:kAFMultipartFormLineDelimiter] stringByAppendingString:AFMultipartFormFinalBoundary()] dataUsingEncoding:self.stringEncoding];
}
#pragma mark - AFMultipartFormDataProxy
- (void)appendPartWithHeaders:(NSDictionary *)headers body:(NSData *)body {
if ([self.mutableLines count] > 0) {
[self appendString:AFMultipartFormEncapsulationBoundary()];
}
for (NSString *field in [headers allKeys]) {
[self appendString:[NSString stringWithFormat:@"%@: %@", field, [headers valueForKey:field]]];
}
[self appendBlankLine];
[self appendData:body];
}
- (void)appendPartWithFormData:(NSData *)data name:(NSString *)name {
[self appendPartWithHeaders:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"form-data; name=\"%@\"", name] forKey:@"Content-Disposition"] body:data];
}
- (void)appendPartWithFile:(NSURL *)fileURL fileName:(NSString *)fileNameOrNil {
if (![fileURL isFileURL]) {
[NSException raise:@"Invalid fileURL value" format:@"%@ must be a valid file URL", fileURL];
return;
}
NSData *data = [NSData dataWithContentsOfFile:[fileURL absoluteString]];
NSString *fileName = fileNameOrNil ? fileNameOrNil : [[fileURL lastPathComponent] stringByAppendingPathExtension:[fileURL pathExtension]];
[self appendPartWithHeaders:[NSDictionary dictionaryWithObject:[NSString stringWithFormat:@"file; filename=\"%@\"", fileName] forKey:@"Content-Disposition"] body:data];
}
- (void)appendData:(NSData *)data {
[self appendString:[[[NSString alloc] initWithData:data encoding:self.stringEncoding] autorelease]];
}
- (void)appendString:(NSString *)string {
[self.mutableLines addObject:string];
}
- (void)appendBlankLine {
[self appendString:@""];
}
@end

View file

@ -22,12 +22,39 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
// Error codes for AFNetworkingErrorDomain correspond to codes in NSURLErrorDomain /**
Indicates an error occured in AFNetworking.
@discussion Error codes for AFNetworkingErrorDomain correspond to codes in NSURLErrorDomain.
*/
extern NSString * const AFNetworkingErrorDomain; extern NSString * const AFNetworkingErrorDomain;
/**
Posted when an operation begins executing.
*/
extern NSString * const AFHTTPOperationDidStartNotification; extern NSString * const AFHTTPOperationDidStartNotification;
/**
Posted when an operation finishes.
*/
extern NSString * const AFHTTPOperationDidFinishNotification; extern NSString * const AFHTTPOperationDidFinishNotification;
/**
`AFHTTPRequestOperation` is an `NSOperation` that implements the `NSURLConnection` delegate methods, and provides a simple block-based interface to asynchronously get the result and context of that operation finishes.
# Subclassing Notes
In cases where you don't need all of the information provided in the callback, or you want to validate and/or represent it in a different way, it makes sense to create a subclass to define this behavior.
For instance, `AFJSONRequestOperation` makes a distinction between successful and unsuccessful requests by validating the HTTP status code and content type of the response, and provides separate callbacks for both the succeeding and failing cases. As another example, `AFImageRequestOperation` offers a pared-down callback, with a single block argument that is an image object that was created from the response data.
## Methods to subclass
Unless you need to override specific `NSURLConnection` delegate methods, you shouldn't need to subclass any methods. Instead, you should provide alternative constructor class methods, that are essentially wrappers around the callback from `AFHTTPRequestOperation`.
@see NSOperation
@see NSURLConnection
*/
@interface AFHTTPRequestOperation : NSOperation { @interface AFHTTPRequestOperation : NSOperation {
@private @private
NSSet *_runLoopModes; NSSet *_runLoopModes;
@ -52,15 +79,60 @@ extern NSString * const AFHTTPOperationDidFinishNotification;
@property (readonly, nonatomic, retain) NSData *responseBody; @property (readonly, nonatomic, retain) NSData *responseBody;
@property (readonly) NSString *responseString; @property (readonly) NSString *responseString;
+ (id)operationWithRequest:(NSURLRequest *)urlRequest ///---------------------------------------
/// @name Creating HTTP Request Operations
///---------------------------------------
/**
Creates and returns an `AFHTTPRequestOperation` object and sets the specified completion callback.
@param urlRequest The request object to be loaded asynchronously during execution of the operation.
@param completion A block object to be executed when the HTTP request operation is finished. This block has no return value and takes four arguments: the request sent from the client, the response received from the server, the HTTP body received by the server during the execution of the request, and an error, which will have been set if an error occured while loading the request.
@see operationWithRequest:inputStream:outputStream:completion
@return A new HTTP request operation
*/
+ (AFHTTPRequestOperation *)operationWithRequest:(NSURLRequest *)urlRequest
completion:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSData *data, NSError *error))completion; completion:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSData *data, NSError *error))completion;
+ (id)operationWithRequest:(NSURLRequest *)urlRequest /**
Creates and returns a streaming `AFHTTPRequestOperation` object and sets the specified input and output streams, and completion callback.
@param urlRequest The request object to be loaded asynchronously during execution of the operation.
@param inputStream The input stream object for reading data to be sent during the request. If set, the input stream is set as the HTTPBodyStream on the NSMutableURLRequest, and the request method is changed to `POST`. This argument may be `nil`.
@param outputStream The output stream object for writing data received during the request. If set, data accumulated in `NSURLConnectionDelegate` methods will be sent to the output stream, and the NSData parameter in the completion block will be `nil`. This argument may be `nil`.
@param completion A block object to be executed when the HTTP request operation is finished. This block has no return value and takes four arguments: the request sent from the client, the response received from the server, the NSData received by the server during the execution of the request, and an error, which will have been set if an error occured while loading the request. This argument may be `nil`.
@see operationWithRequest:completion
@return A new HTTP request operation
*/
+ (AFHTTPRequestOperation *)operationWithRequest:(NSURLRequest *)urlRequest
inputStream:(NSInputStream *)inputStream inputStream:(NSInputStream *)inputStream
outputStream:(NSOutputStream *)outputStream outputStream:(NSOutputStream *)outputStream
completion:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))completion; completion:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))completion;
///---------------------------------
/// @name Setting Progress Callbacks
///---------------------------------
/**
Sets a callback to be called when an undetermined number of bytes have been downloaded from the server.
@param block A block object to be called when an undetermined number of bytes have been downloaded from the server. This block has no return value and takes two arguments: the total bytes written, and the total bytes expected to be written during the request, as initially determined by the length of the HTTP body. This block may be called multiple times.
@see setDownloadProgressBlock
*/
- (void)setUploadProgressBlock:(void (^)(NSUInteger totalBytesWritten, NSUInteger totalBytesExpectedToWrite))block; - (void)setUploadProgressBlock:(void (^)(NSUInteger totalBytesWritten, NSUInteger totalBytesExpectedToWrite))block;
/**
Sets a callback to be called when an undetermined number of bytes have been uploaded to the server.
@param block A block object to be called when an undetermined number of bytes have been uploaded to the server. This block has no return value and takes two arguments: the total bytes read, and the total bytes expected to be read during the request, as initially determined by the expected content size of the `NSHTTPURLResponse` object. This block may be called multiple times.
@see setUploadProgressBlock
*/
- (void)setDownloadProgressBlock:(void (^)(NSUInteger totalBytesRead, NSUInteger totalBytesExpectedToRead))block; - (void)setDownloadProgressBlock:(void (^)(NSUInteger totalBytesRead, NSUInteger totalBytesExpectedToRead))block;
@end @end

View file

@ -134,7 +134,7 @@ static NSThread *_networkRequestThread = nil;
return _networkRequestThread; return _networkRequestThread;
} }
+ (id)operationWithRequest:(NSURLRequest *)urlRequest + (AFHTTPRequestOperation *)operationWithRequest:(NSURLRequest *)urlRequest
completion:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSData *data, NSError *error))completion completion:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSData *data, NSError *error))completion
{ {
AFHTTPRequestOperation *operation = [[[self alloc] initWithRequest:urlRequest] autorelease]; AFHTTPRequestOperation *operation = [[[self alloc] initWithRequest:urlRequest] autorelease];
@ -143,16 +143,18 @@ static NSThread *_networkRequestThread = nil;
return operation; return operation;
} }
+ (id)operationWithRequest:(NSURLRequest *)urlRequest + (AFHTTPRequestOperation *)operationWithRequest:(NSURLRequest *)urlRequest
inputStream:(NSInputStream *)inputStream inputStream:(NSInputStream *)inputStream
outputStream:(NSOutputStream *)outputStream outputStream:(NSOutputStream *)outputStream
completion:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))completion completion:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))completion
{ {
NSMutableURLRequest *mutableURLRequest = [[urlRequest mutableCopy] autorelease]; NSMutableURLRequest *mutableURLRequest = [[urlRequest mutableCopy] autorelease];
if (inputStream) {
[mutableURLRequest setHTTPBodyStream:inputStream]; [mutableURLRequest setHTTPBodyStream:inputStream];
if ([[mutableURLRequest HTTPMethod] isEqualToString:@"GET"]) { if ([[mutableURLRequest HTTPMethod] isEqualToString:@"GET"]) {
[mutableURLRequest setHTTPMethod:@"POST"]; [mutableURLRequest setHTTPMethod:@"POST"];
} }
}
AFHTTPRequestOperation *operation = [self operationWithRequest:mutableURLRequest completion:^(NSURLRequest *request, NSHTTPURLResponse *response, NSData *data, NSError *error) { AFHTTPRequestOperation *operation = [self operationWithRequest:mutableURLRequest completion:^(NSURLRequest *request, NSHTTPURLResponse *response, NSData *data, NSError *error) {
if (completion) { if (completion) {
@ -173,7 +175,7 @@ static NSThread *_networkRequestThread = nil;
self.request = urlRequest; self.request = urlRequest;
self.runLoopModes = [NSSet setWithObjects:NSRunLoopCommonModes, nil]; self.runLoopModes = [NSSet setWithObject:NSRunLoopCommonModes];
self.state = AFHTTPOperationReadyState; self.state = AFHTTPOperationReadyState;
@ -225,11 +227,11 @@ static NSThread *_networkRequestThread = nil;
switch (state) { switch (state) {
case AFHTTPOperationExecutingState: case AFHTTPOperationExecutingState:
[[AFNetworkActivityIndicatorManager sharedManager] startAnimating]; [[AFNetworkActivityIndicatorManager sharedManager] incrementActivityCount];
[[NSNotificationCenter defaultCenter] postNotificationName:AFHTTPOperationDidStartNotification object:self]; [[NSNotificationCenter defaultCenter] postNotificationName:AFHTTPOperationDidStartNotification object:self];
break; break;
case AFHTTPOperationFinishedState: case AFHTTPOperationFinishedState:
[[AFNetworkActivityIndicatorManager sharedManager] stopAnimating]; [[AFNetworkActivityIndicatorManager sharedManager] decrementActivityCount];
[[NSNotificationCenter defaultCenter] postNotificationName:AFHTTPOperationDidFinishNotification object:self]; [[NSNotificationCenter defaultCenter] postNotificationName:AFHTTPOperationDidFinishNotification object:self];
break; break;
default: default:

View file

@ -23,17 +23,40 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "AFImageRequestOperation.h" #import "AFImageRequestOperation.h"
/**
`AFImageCache` is a subclass of `NSCache` that stores and retrieves images from cache.
@discussion `AFImageCache` is used to cache images for successful `AFImageRequestOperations` with the proper cache policy.
*/
@interface AFImageCache : NSCache @interface AFImageCache : NSCache
+ (id)sharedImageCache; /**
Returns the shared image cache object for the system.
- (UIImage *)cachedImageForRequest:(NSURLRequest *)urlRequest @return The systemwide image cache.
imageSize:(CGSize)imageSize */
options:(AFImageRequestOptions)options; + (AFImageCache *)sharedImageCache;
/**
Returns the image associated with a given URL and cache name.
@param url The URL associated with the image in the cache.
@param cacheName The cache name associated with the image in the cache. This allows for multiple versions of an image to be associated for a single URL, such as image thumbnails, for instance.
@return The image associated with the URL and cache name, or `nil` if not image exists.
*/
- (UIImage *)cachedImageForURL:(NSURL *)url
cacheName:(NSString *)cacheName;
/**
Stores an image into cache, associated with a given URL and cache name.
@param image The image to be stored in cache.
@param url The URL to be associated with the image.
@param cacheName The cache name to be associated with the image in the cache. This allows for multiple versions of an image to be associated for a single URL, such as image thumbnails, for instance.
*/
- (void)cacheImage:(UIImage *)image - (void)cacheImage:(UIImage *)image
forRequest:(NSURLRequest *)urlRequest forURL:(NSURL *)url
imageSize:(CGSize)imageSize cacheName:(NSString *)cacheName;
options:(AFImageRequestOptions)options;
@end @end

View file

@ -22,14 +22,14 @@
#import "AFImageCache.h" #import "AFImageCache.h"
static inline NSString * AFImageCacheKey(NSURLRequest *urlRequest, CGSize imageSize, AFImageRequestOptions options) { static inline NSString * AFImageCacheKeyFromURLAndCacheName(NSURL *url, NSString *cacheName) {
return [[[urlRequest URL] absoluteString] stringByAppendingFormat:@"#%fx%f:%d", imageSize.width, imageSize.height, options]; return [[url absoluteString] stringByAppendingFormat:@"#%@", cacheName];
} }
@implementation AFImageCache @implementation AFImageCache
+ (id)sharedImageCache { + (AFImageCache *)sharedImageCache {
static NSCache *_sharedImageCache = nil; static AFImageCache *_sharedImageCache = nil;
static dispatch_once_t oncePredicate; static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{ dispatch_once(&oncePredicate, ^{
@ -39,23 +39,21 @@ static inline NSString * AFImageCacheKey(NSURLRequest *urlRequest, CGSize imageS
return _sharedImageCache; return _sharedImageCache;
} }
- (UIImage *)cachedImageForRequest:(NSURLRequest *)urlRequest - (UIImage *)cachedImageForURL:(NSURL *)url
imageSize:(CGSize)imageSize cacheName:(NSString *)cacheName
options:(AFImageRequestOptions)options
{ {
return [self objectForKey:AFImageCacheKey(urlRequest, imageSize, options)]; return [self objectForKey:AFImageCacheKeyFromURLAndCacheName(url, cacheName)];
} }
- (void)cacheImage:(UIImage *)image - (void)cacheImage:(UIImage *)image
forRequest:(NSURLRequest *)urlRequest forURL:(NSURL *)url
imageSize:(CGSize)imageSize cacheName:(NSString *)cacheName
options:(AFImageRequestOptions)options
{ {
if (!image) { if (!image) {
return; return;
} }
[self setObject:image forKey:AFImageCacheKey(urlRequest, imageSize, options)]; [self setObject:image forKey:AFImageCacheKeyFromURLAndCacheName(url, cacheName)];
} }
@end @end

View file

@ -24,19 +24,38 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "AFHTTPRequestOperation.h" #import "AFHTTPRequestOperation.h"
typedef enum { /**
AFImageRequestDefaultOptions = 0, `AFImageRequestOperation` is an `NSOperation` that wraps the callback from `AFHTTPRequestOperation` to create an image from the response body, and optionally cache the image to memory.
AFImageRequestRoundCorners = 1 << 1,
} AFImageRequestOptions;
@see NSOperation
@see AFHTTPRequestOperation
*/
@interface AFImageRequestOperation : AFHTTPRequestOperation @interface AFImageRequestOperation : AFHTTPRequestOperation
+ (id)operationWithRequest:(NSURLRequest *)urlRequest /**
Creates and returns an `AFImageRequestOperation` object and sets the specified success callback.
@param urlRequest The request object to be loaded asynchronously during execution of the operation.
@param success A block object to be executed when the request finishes successfully, with a status code in the 2xx range, and with an acceptable content type (e.g. `image/png`). This block has no return value and takes a single arguments, the image created from the response data of the request.
@return A new image request operation
*/
+ (AFImageRequestOperation *)operationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(UIImage *image))success; success:(void (^)(UIImage *image))success;
+ (id)operationWithRequest:(NSURLRequest *)urlRequest /**
imageSize:(CGSize)imageSize Creates and returns an `AFImageRequestOperation` object and sets the specified success callback.
options:(AFImageRequestOptions)options
success:(void (^)(UIImage *image))success; @param urlRequest The request object to be loaded asynchronously during execution of the operation.
@param success A block object to be executed when the request finishes successfully, with a status code in the 2xx range, and with an acceptable content type (e.g. `image/png`). This block has no return value and takes a three arguments: the request object of the operation, the response for the request, and the image created from the response data.
@param failure A block object to be executed when the request finishes unsuccessfully. This block has no return value and takes a three arguments: the request object of the operation, the response for the request, and the error associated with the cause for the unsuccessful operation.
@return A new image request operation
*/
+ (AFImageRequestOperation *)operationWithRequest:(NSURLRequest *)urlRequest
imageProcessingBlock:(UIImage *(^)(UIImage *))imageProcessingBlock
cacheName:(NSString *)cacheNameOrNil
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure;
@end @end

View file

@ -23,16 +23,6 @@
#import "AFImageRequestOperation.h" #import "AFImageRequestOperation.h"
#import "AFImageCache.h" #import "AFImageCache.h"
#import "UIImage+AFNetworking.h"
static CGFloat const kAFImageRequestJPEGQuality = 0.8;
static NSUInteger const kAFImageRequestMaximumResponseSize = 8 * 1024 * 1024;
static inline CGSize kAFImageRequestRoundedCornerRadii(CGSize imageSize) {
CGFloat dimension = fmaxf(imageSize.width, imageSize.height) * 0.1;
return CGSizeMake(dimension, dimension);
}
static dispatch_queue_t af_image_request_operation_processing_queue; static dispatch_queue_t af_image_request_operation_processing_queue;
static dispatch_queue_t image_request_operation_processing_queue() { static dispatch_queue_t image_request_operation_processing_queue() {
if (af_image_request_operation_processing_queue == NULL) { if (af_image_request_operation_processing_queue == NULL) {
@ -44,19 +34,31 @@ static dispatch_queue_t image_request_operation_processing_queue() {
@implementation AFImageRequestOperation @implementation AFImageRequestOperation
+ (id)operationWithRequest:(NSURLRequest *)urlRequest + (AFImageRequestOperation *)operationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(UIImage *image))success success:(void (^)(UIImage *image))success
{ {
return [self operationWithRequest:urlRequest imageSize:CGSizeZero options:AFImageRequestDefaultOptions success:success]; return [self operationWithRequest:urlRequest imageProcessingBlock:nil cacheName:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
if (success) {
success(image);
}
} failure:nil];
} }
+ (id)operationWithRequest:(NSURLRequest *)urlRequest + (AFImageRequestOperation *)operationWithRequest:(NSURLRequest *)urlRequest
imageSize:(CGSize)imageSize imageProcessingBlock:(UIImage *(^)(UIImage *))imageProcessingBlock
options:(AFImageRequestOptions)options cacheName:(NSString *)cacheNameOrNil
success:(void (^)(UIImage *image))success success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure
{ {
AFImageRequestOperation *operation = [self operationWithRequest:urlRequest completion:^(NSURLRequest *request, NSHTTPURLResponse *response, NSData *data, NSError *error) { return (AFImageRequestOperation *)[self operationWithRequest:urlRequest completion:^(NSURLRequest *request, NSHTTPURLResponse *response, NSData *data, NSError *error) {
dispatch_async(image_request_operation_processing_queue(), ^(void) { dispatch_async(image_request_operation_processing_queue(), ^(void) {
if (error) {
if (failure) {
dispatch_async(dispatch_get_main_queue(), ^(void) {
failure(request, response, error);
});
}
} else {
UIImage *image = nil; UIImage *image = nil;
if ([[UIScreen mainScreen] scale] == 2.0) { if ([[UIScreen mainScreen] scale] == 2.0) {
CGImageRef imageRef = [[UIImage imageWithData:data] CGImage]; CGImageRef imageRef = [[UIImage imageWithData:data] CGImage];
@ -65,26 +67,22 @@ static dispatch_queue_t image_request_operation_processing_queue() {
image = [UIImage imageWithData:data]; image = [UIImage imageWithData:data];
} }
if (!(CGSizeEqualToSize(image.size, imageSize) || CGSizeEqualToSize(imageSize, CGSizeZero))) { if (imageProcessingBlock) {
image = [UIImage imageByScalingAndCroppingImage:image size:imageSize]; image = imageProcessingBlock(image);
}
if ((options & AFImageRequestRoundCorners)) {
image = [UIImage imageByRoundingCornersOfImage:image corners:UIRectCornerAllCorners cornerRadii:kAFImageRequestRoundedCornerRadii(image.size)];
} }
dispatch_sync(dispatch_get_main_queue(), ^(void) { dispatch_async(dispatch_get_main_queue(), ^(void) {
if (success) { if (success) {
success(image); success(request, response, image);
} }
}); });
[[AFImageCache sharedImageCache] cacheImage:image forRequest:request imageSize:imageSize options:options]; if ([request cachePolicy] != NSURLCacheStorageNotAllowed) {
[[AFImageCache sharedImageCache] cacheImage:image forURL:[request URL] cacheName:cacheNameOrNil];
}
}
}); });
}]; }];
operation.runLoopModes = [NSSet setWithObject:NSRunLoopCommonModes];
return operation;
} }
@end @end

View file

@ -20,24 +20,87 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE. // THE SOFTWARE.
#import <Foundation/Foundation.h>
#import "AFHTTPRequestOperation.h" #import "AFHTTPRequestOperation.h"
/**
`AFJSONRequestOperation` is an `NSOperation` that wraps the callback from `AFHTTPRequestOperation` to determine the success or failure of a request based on its status code and response content type, and parse the response body into a JSON object.
@see NSOperation
@see AFHTTPRequestOperation
*/
@interface AFJSONRequestOperation : AFHTTPRequestOperation @interface AFJSONRequestOperation : AFHTTPRequestOperation
+ (id)operationWithRequest:(NSURLRequest *)urlRequest ///---------------------------------------
/// @name Creating JSON Request Operations
///---------------------------------------
/**
Creates and returns an `AFJSONRequestOperation` object and sets the specified success callback.
@param urlRequest The request object to be loaded asynchronously during execution of the operation
@param success A block object to be executed when the JSON request operation finishes successfully, with a status code in the 2xx range, and with an acceptable content type (e.g. `application/json`). This block has no return value and takes a single argument, which is the JSON object created from the response data of request, or nil if there was an error.
@see defaultAcceptableStatusCodes
@see defaultAcceptableContentTypes
@see operationWithRequest:success:failure:
@return A new JSON request operation
*/
+ (AFJSONRequestOperation *)operationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(id JSON))success; success:(void (^)(id JSON))success;
+ (id)operationWithRequest:(NSURLRequest *)urlRequest /**
Creates and returns an `AFJSONRequestOperation` object and sets the specified success and failure callbacks.
@param urlRequest The request object to be loaded asynchronously during execution of the operation
@param success A block object to be executed when the JSON request operation finishes successfully, with a status code in the 2xx range, and with an acceptable content type (e.g. `application/json`). This block has no return value and takes a single argument, which is the JSON object created from the response data of request.
@param failure A block object to be executed when the JSON request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the resonse data as JSON. This block has no return value and takes a single argument, which is the error describing the network or parsing error that occurred.
@see defaultAcceptableStatusCodes
@see defaultAcceptableContentTypes
@see operationWithRequest:success:
@return A new JSON request operation
*/
+ (AFJSONRequestOperation *)operationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(id JSON))success success:(void (^)(id JSON))success
failure:(void (^)(NSError *error))failure; failure:(void (^)(NSError *error))failure;
+ (id)operationWithRequest:(NSURLRequest *)urlRequest /**
Creates and returns an `AFJSONRequestOperation` object and sets the specified success and failure callbacks, as well as the status codes and content types that are acceptable for a successful request.
@param urlRequest The request object to be loaded asynchronously during execution of the operation
@param acceptableStatusCodes An `NSIndexSet` object that specifies the ranges of acceptable status codes. If you specify nil, all status codes will be considered acceptable.
@param acceptableContentTypes An `NSSet` object that specifies the acceptable content types. If you specify nil, all content types will be considered acceptable.
@param success A block object to be executed when the JSON request operation finishes successfully, with a status code in the 2xx range, and with an acceptable content type (e.g. `application/json`). This block has no return value and takes three arguments, the request sent from the client, the response received from the server, and the JSON object created from the response data of request.
@param failure A block object to be executed when the JSON request operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the resonse data as JSON. This block has no return value and takes three arguments, the request sent from the client, the response received from the server, and the error describing the network or parsing error that occurred.
@return A new JSON request operation
*/
+ (AFJSONRequestOperation *)operationWithRequest:(NSURLRequest *)urlRequest
acceptableStatusCodes:(NSIndexSet *)acceptableStatusCodes acceptableStatusCodes:(NSIndexSet *)acceptableStatusCodes
acceptableContentTypes:(NSSet *)acceptableContentTypes acceptableContentTypes:(NSSet *)acceptableContentTypes
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id JSON))success success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id JSON))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure; failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure;
///----------------------------------
/// @name Getting Default HTTP Values
///----------------------------------
/**
Returns an `NSIndexSet` object containing the ranges of acceptable HTTP status codes (http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html) used in operationWithRequest:success and operationWithRequest:success:failure.
By default, this is the range 200 to 299, inclusive.
*/
+ (NSIndexSet *)defaultAcceptableStatusCodes; + (NSIndexSet *)defaultAcceptableStatusCodes;
/**
Returns an `NSSet` object containing the acceptable HTTP content type (http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17) used in operationWithRequest:success and operationWithRequest:success:failure.
By default, this contains `application/json`, `application/x-javascript`, `text/javascript`, `text/x-javascript`, `text/x-json`, `text/json`, and `text/plain`
*/
+ (NSSet *)defaultAcceptableContentTypes; + (NSSet *)defaultAcceptableContentTypes;
@end @end

View file

@ -36,13 +36,13 @@ static dispatch_queue_t json_request_operation_processing_queue() {
@implementation AFJSONRequestOperation @implementation AFJSONRequestOperation
+ (id)operationWithRequest:(NSURLRequest *)urlRequest + (AFJSONRequestOperation *)operationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(id JSON))success success:(void (^)(id JSON))success
{ {
return [self operationWithRequest:urlRequest success:success failure:nil]; return [self operationWithRequest:urlRequest success:success failure:nil];
} }
+ (id)operationWithRequest:(NSURLRequest *)urlRequest + (AFJSONRequestOperation *)operationWithRequest:(NSURLRequest *)urlRequest
success:(void (^)(id JSON))success success:(void (^)(id JSON))success
failure:(void (^)(NSError *error))failure failure:(void (^)(NSError *error))failure
{ {
@ -57,15 +57,15 @@ static dispatch_queue_t json_request_operation_processing_queue() {
}]; }];
} }
+ (id)operationWithRequest:(NSURLRequest *)urlRequest + (AFJSONRequestOperation *)operationWithRequest:(NSURLRequest *)urlRequest
acceptableStatusCodes:(NSIndexSet *)acceptableStatusCodes acceptableStatusCodes:(NSIndexSet *)acceptableStatusCodes
acceptableContentTypes:(NSSet *)acceptableContentTypes acceptableContentTypes:(NSSet *)acceptableContentTypes
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id JSON))success success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id JSON))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure
{ {
return [self operationWithRequest:urlRequest completion:^(NSURLRequest *request, NSHTTPURLResponse *response, NSData *data, NSError *error) { return (AFJSONRequestOperation *)[self operationWithRequest:urlRequest completion:^(NSURLRequest *request, NSHTTPURLResponse *response, NSData *data, NSError *error) {
if (!error) { if (!error) {
if (![acceptableStatusCodes containsIndex:[response statusCode]]) { if (acceptableStatusCodes && ![acceptableStatusCodes containsIndex:[response statusCode]]) {
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
[userInfo setValue:[NSString stringWithFormat:NSLocalizedString(@"Expected status code %@, got %d", nil), acceptableStatusCodes, [response statusCode]] forKey:NSLocalizedDescriptionKey]; [userInfo setValue:[NSString stringWithFormat:NSLocalizedString(@"Expected status code %@, got %d", nil), acceptableStatusCodes, [response statusCode]] forKey:NSLocalizedDescriptionKey];
[userInfo setValue:[request URL] forKey:NSURLErrorFailingURLErrorKey]; [userInfo setValue:[request URL] forKey:NSURLErrorFailingURLErrorKey];
@ -73,7 +73,7 @@ static dispatch_queue_t json_request_operation_processing_queue() {
error = [[[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorBadServerResponse userInfo:userInfo] autorelease]; error = [[[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorBadServerResponse userInfo:userInfo] autorelease];
} }
if (![acceptableContentTypes containsObject:[response MIMEType]]) { if (acceptableContentTypes && ![acceptableContentTypes containsObject:[response MIMEType]]) {
NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
[userInfo setValue:[NSString stringWithFormat:NSLocalizedString(@"Expected content type %@, got %@", nil), acceptableContentTypes, [response MIMEType]] forKey:NSLocalizedDescriptionKey]; [userInfo setValue:[NSString stringWithFormat:NSLocalizedString(@"Expected content type %@, got %@", nil), acceptableContentTypes, [response MIMEType]] forKey:NSLocalizedDescriptionKey];
[userInfo setValue:[request URL] forKey:NSURLErrorFailingURLErrorKey]; [userInfo setValue:[request URL] forKey:NSURLErrorFailingURLErrorKey];
@ -108,7 +108,7 @@ static dispatch_queue_t json_request_operation_processing_queue() {
JSON = [[JSONDecoder decoder] objectWithData:data error:&JSONError]; JSON = [[JSONDecoder decoder] objectWithData:data error:&JSONError];
#endif #endif
dispatch_sync(dispatch_get_main_queue(), ^(void) { dispatch_async(dispatch_get_main_queue(), ^(void) {
if (JSONError) { if (JSONError) {
if (failure) { if (failure) {
failure(request, response, JSONError); failure(request, response, JSONError);

View file

@ -22,14 +22,29 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
/**
`AFNetworkActivityIndicatorManager` manages the state of the network activity indicator in the status bar. When network operations start, they can call `-incrementActivityCount`, and once they're finished, call `-decrementActivityCount`. The number of active requests is incremented and decremented much like a stack or a semaphore, and the activity indicator will animate so long as that number is greater than zero.
*/
@interface AFNetworkActivityIndicatorManager : NSObject { @interface AFNetworkActivityIndicatorManager : NSObject {
@private @private
NSInteger _activityCount; NSInteger _activityCount;
} }
/**
Returns the shared network activity indicator manager object for the system.
@return The systemwide network activity indicator manager.
*/
+ (AFNetworkActivityIndicatorManager *)sharedManager; + (AFNetworkActivityIndicatorManager *)sharedManager;
- (void)startAnimating; /**
- (void)stopAnimating; Increments the number of active network requests. If this number was zero before incrementing, this will start animating the status bar network activity indicator.
*/
- (void)incrementActivityCount;
/**
Decrements the number of active network requests. If this number becomes zero before decrementing, this will stop animating the status bar network activity indicator.
*/
- (void)decrementActivityCount;
@end @end

View file

@ -47,13 +47,13 @@
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:self.activityCount > 0]; [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:self.activityCount > 0];
} }
- (void)startAnimating { - (void)incrementActivityCount {
@synchronized(self) { @synchronized(self) {
self.activityCount += 1; self.activityCount += 1;
} }
} }
- (void)stopAnimating { - (void)decrementActivityCount {
@synchronized(self) { @synchronized(self) {
self.activityCount -= 1; self.activityCount -= 1;
} }

View file

@ -1,92 +0,0 @@
// AFRestClient.h
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
#import "AFHTTPRequestOperation.h"
#import "NSMutableURLRequest+AFNetworking.h"
#import "NSString+AFNetworking.h"
@interface AFRestClient : NSObject {
@private
NSURL *_baseURL;
NSMutableDictionary *_defaultHeaders;
NSOperationQueue *_operationQueue;
}
- (id)initWithBaseURL:(NSURL *)url;
- (NSString *)defaultValueForHeader:(NSString *)header;
- (void)setDefaultHeader:(NSString *)header value:(NSString *)value;
- (void)setAuthorizationHeaderWithUsername:(NSString *)username password:(NSString *)password;
- (void)setAuthorizationHeaderWithToken:(NSString *)token;
- (void)clearAuthorizationHeader;
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
path:(NSString *)path parameters:(NSDictionary *)parameters;
- (void)enqueueHTTPOperation:(AFHTTPRequestOperation *)operation;
- (void)enqueueHTTPOperationWithRequest:(NSURLRequest *)request
success:(void (^)(id response))success
failure:(void (^)(NSError *error))failure;
- (void)cancelHTTPOperationsWithRequest:(NSURLRequest *)request;
- (void)cancelAllHTTPOperations;
- (void)getPath:(NSString *)path
parameters:(NSDictionary *)parameters
success:(void (^)(id response))success;
- (void)getPath:(NSString *)path
parameters:(NSDictionary *)parameters
success:(void (^)(id response))success
failure:(void (^)(NSError *error))failure;
- (void)postPath:(NSString *)path
parameters:(NSDictionary *)parameters
success:(void (^)(id response))success;
- (void)postPath:(NSString *)path
parameters:(NSDictionary *)parameters
success:(void (^)(id response))success
failure:(void (^)(NSError *error))failure;
- (void)putPath:(NSString *)path
parameters:(NSDictionary *)parameters
success:(void (^)(id response))success;
- (void)putPath:(NSString *)path
parameters:(NSDictionary *)parameters
success:(void (^)(id response))success
failure:(void (^)(NSError *error))failure;
- (void)deletePath:(NSString *)path
parameters:(NSDictionary *)parameters
success:(void (^)(id response))success;
- (void)deletePath:(NSString *)path
parameters:(NSDictionary *)parameters
success:(void (^)(id response))success
failure:(void (^)(NSError *error))failure;
@end

View file

@ -1,194 +0,0 @@
// AFRestClient.m
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "AFRestClient.h"
#import "AFJSONRequestOperation.h"
#import "NSData+AFNetworking.h"
static NSStringEncoding const kAFRestClientStringEncoding = NSUTF8StringEncoding;
@interface AFRestClient ()
@property (readwrite, nonatomic, retain) NSURL *baseURL;
@property (readwrite, nonatomic, retain) NSMutableDictionary *defaultHeaders;
@property (readwrite, nonatomic, retain) NSOperationQueue *operationQueue;
@end
@implementation AFRestClient
@synthesize baseURL = _baseURL;
@synthesize defaultHeaders = _defaultHeaders;
@synthesize operationQueue = _operationQueue;
- (id)initWithBaseURL:(NSURL *)url {
self = [super init];
if (!self) {
return nil;
}
self.baseURL = url;
self.operationQueue = [[[NSOperationQueue alloc] init] autorelease];
[self.operationQueue setMaxConcurrentOperationCount:2];
self.defaultHeaders = [NSMutableDictionary dictionary];
// Accept HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
[self setDefaultHeader:@"Accept" value:@"application/json"];
// Accept-Encoding HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
[self setDefaultHeader:@"Accept-Encoding" value:@"gzip"];
// Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
NSString *preferredLanguageCodes = [[NSLocale preferredLanguages] componentsJoinedByString:@", "];
[self setDefaultHeader:@"Accept-Language" value:[NSString stringWithFormat:@"%@, en-us;q=0.8", preferredLanguageCodes]];
// User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
[self setDefaultHeader:@"User-Agent" value:[NSString stringWithFormat:@"%@/%@ (%@, %@ %@, %@, Scale/%f)", [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleIdentifierKey], [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleVersionKey], @"unknown", [[UIDevice currentDevice] systemName], [[UIDevice currentDevice] systemVersion], [[UIDevice currentDevice] model], ([[UIScreen mainScreen] respondsToSelector:@selector(scale)] ? [[UIScreen mainScreen] scale] : 1.0)]];
return self;
}
- (void)dealloc {
[_baseURL release];
[_defaultHeaders release];
[_operationQueue release];
[super dealloc];
}
- (NSString *)defaultValueForHeader:(NSString *)header {
return [self.defaultHeaders valueForKey:header];
}
- (void)setDefaultHeader:(NSString *)header value:(NSString *)value {
[self.defaultHeaders setObject:value forKey:header];
}
- (void)setAuthorizationHeaderWithUsername:(NSString *)username password:(NSString *)password {
NSString *authHeader = [NSString stringWithFormat:@"%@:%@", username, password];
NSString *encodedAuthHeader = [[NSData dataWithBytes:[authHeader UTF8String] length:[authHeader length]] base64EncodedString];
[self setDefaultHeader:@"Authorization" value:[NSString stringWithFormat:@"Basic %@", encodedAuthHeader]];
}
- (void)setAuthorizationHeaderWithToken:(NSString *)token {
[self setDefaultHeader:@"Authorization" value:[NSString stringWithFormat:@"Token token=\"%@\"", token]];
}
- (void)clearAuthorizationHeader {
[self.defaultHeaders removeObjectForKey:@"Authorization"];
}
#pragma mark -
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters {
NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] init] autorelease];
NSMutableDictionary *headers = [NSMutableDictionary dictionaryWithDictionary:self.defaultHeaders];
NSURL *url = [NSURL URLWithString:path relativeToURL:self.baseURL];
if (parameters) {
NSMutableArray *mutableParameterComponents = [NSMutableArray array];
for (id key in [parameters allKeys]) {
NSString *component = [NSString stringWithFormat:@"%@=%@", [[key description] urlEncodedStringWithEncoding:kAFRestClientStringEncoding], [[[parameters valueForKey:key] description] urlEncodedStringWithEncoding:kAFRestClientStringEncoding]];
[mutableParameterComponents addObject:component];
}
NSString *queryString = [mutableParameterComponents componentsJoinedByString:@"&"];
if ([method isEqualToString:@"GET"]) {
url = [NSURL URLWithString:[[url absoluteString] stringByAppendingFormat:[path rangeOfString:@"?"].location == NSNotFound ? @"?%@" : @"&%@", queryString]];
} else {
NSString *charset = (NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding(kAFRestClientStringEncoding));
[headers setObject:[NSString stringWithFormat:@"application/x-www-form-urlencoded; charset=%@", charset] forKey:@"Content-Type"];
[request setHTTPBody:[queryString dataUsingEncoding:NSUTF8StringEncoding]];
}
}
[request setURL:url];
[request setHTTPMethod:method];
[request setHTTPShouldHandleCookies:NO];
[request setAllHTTPHeaderFields:headers];
return request;
}
- (void)enqueueHTTPOperation:(AFHTTPRequestOperation *)operation {
[self.operationQueue addOperation:operation];
}
- (void)enqueueHTTPOperationWithRequest:(NSURLRequest *)request success:(void (^)(id response))success failure:(void (^)(NSError *error))failure {
if ([request URL] == nil || [[request URL] isEqual:[NSNull null]]) {
return;
}
AFHTTPRequestOperation *operation = [AFJSONRequestOperation operationWithRequest:request success:success failure:failure];
[self enqueueHTTPOperation:operation];
}
- (void)cancelHTTPOperationsWithRequest:(NSURLRequest *)request {
for (AFHTTPRequestOperation *operation in [self.operationQueue operations]) {
if ([[operation request] isEqual:request]) {
[operation cancel];
}
}
}
- (void)cancelAllHTTPOperations {
[self.operationQueue cancelAllOperations];
}
#pragma mark -
- (void)getPath:(NSString *)path parameters:(NSDictionary *)parameters success:(void (^)(id response))success {
[self getPath:path parameters:parameters success:success failure:nil];
}
- (void)getPath:(NSString *)path parameters:(NSDictionary *)parameters success:(void (^)(id response))success failure:(void (^)(NSError *error))failure {
NSURLRequest *request = [self requestWithMethod:@"GET" path:path parameters:parameters];
[self enqueueHTTPOperationWithRequest:request success:success failure:failure];
}
- (void)postPath:(NSString *)path parameters:(NSDictionary *)parameters success:(void (^)(id response))success {
[self postPath:path parameters:parameters success:success failure:nil];
}
- (void)postPath:(NSString *)path parameters:(NSDictionary *)parameters success:(void (^)(id response))success failure:(void (^)(NSError *error))failure {
NSURLRequest *request = [self requestWithMethod:@"POST" path:path parameters:parameters];
[self enqueueHTTPOperationWithRequest:request success:success failure:failure];
}
- (void)putPath:(NSString *)path parameters:(NSDictionary *)parameters success:(void (^)(id response))success {
[self putPath:path parameters:parameters success:success failure:nil];
}
- (void)putPath:(NSString *)path parameters:(NSDictionary *)parameters success:(void (^)(id response))success failure:(void (^)(NSError *error))failure {
NSURLRequest *request = [self requestWithMethod:@"PUT" path:path parameters:parameters];
[self enqueueHTTPOperationWithRequest:request success:success failure:failure];
}
- (void)deletePath:(NSString *)path parameters:(NSDictionary *)parameters success:(void (^)(id response))success {
[self deletePath:path parameters:parameters success:success failure:nil];
}
- (void)deletePath:(NSString *)path parameters:(NSDictionary *)parameters success:(void (^)(id response))success failure:(void (^)(NSError *error))failure {
NSURLRequest *request = [self requestWithMethod:@"DELETE" path:path parameters:parameters];
[self enqueueHTTPOperationWithRequest:request success:success failure:failure];
}
@end

View file

@ -1,33 +0,0 @@
// NSData+AFNetworking.h
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
extern NSString * const AFZlibErrorDomain;
@interface NSData (AFNetworking)
- (NSString *)base64EncodedString;
- (NSData *)dataByGZipCompressingWithError:(NSError **)error;
- (NSData *)dataByGZipDecompressingDataWithError:(NSError **)error;
@end

View file

@ -1,141 +0,0 @@
// NSData+AFNetworking.m
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "NSData+AFNetworking.h"
#import "AFHTTPRequestOperation.h"
#import <zlib.h>
NSString * const AFZlibErrorDomain = @"com.alamofire.networking.zlib.error";
static char Base64EncodingTable[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
#pragma mark -
@implementation NSData (AFNetworking)
- (NSString *)base64EncodedString {
NSUInteger length = [self length];
NSMutableData *mutableData = [NSMutableData dataWithLength:((length + 2) / 3) * 4];
uint8_t *input = (uint8_t *)[self bytes];
uint8_t *output = (uint8_t *)[mutableData mutableBytes];
for (NSUInteger i = 0; i < length; i += 3) {
NSUInteger value = 0;
for (NSUInteger j = i; j < (i + 3); j++) {
value <<= 8;
if (j < length) {
value |= (0xFF & input[j]);
}
}
NSInteger idx = (i / 3) * 4;
output[idx + 0] = Base64EncodingTable[(value >> 18) & 0x3F];
output[idx + 1] = Base64EncodingTable[(value >> 12) & 0x3F];
output[idx + 2] = (i + 1) < length ? Base64EncodingTable[(value >> 6) & 0x3F] : '=';
output[idx + 3] = (i + 2) < length ? Base64EncodingTable[(value >> 0) & 0x3F] : '=';
}
return [[[NSString alloc] initWithData:mutableData encoding:NSASCIIStringEncoding] autorelease];
}
- (NSData *)dataByGZipCompressingWithError:(NSError **)error {
if ([self length] == 0) {
return self;
}
z_stream zStream;
zStream.zalloc = Z_NULL;
zStream.zfree = Z_NULL;
zStream.opaque = Z_NULL;
zStream.next_in = (Bytef *)[self bytes];
zStream.avail_in = [self length];
zStream.total_out = 0;
if (deflateInit(&zStream, Z_DEFAULT_COMPRESSION) != Z_OK) {
return nil;
}
NSUInteger compressionChunkSize = 16384; // 16Kb
NSMutableData *compressedData = [NSMutableData dataWithLength:compressionChunkSize];
do {
if (zStream.total_out >= [compressedData length]) {
[compressedData increaseLengthBy:compressionChunkSize];
}
zStream.next_out = [compressedData mutableBytes] + zStream.total_out;
zStream.avail_out = [compressedData length] - zStream.total_out;
deflate(&zStream, Z_FINISH);
} while (zStream.avail_out == 0);
deflateEnd(&zStream);
[compressedData setLength:zStream.total_out];
return [NSData dataWithData:compressedData];
}
- (NSData *)dataByGZipDecompressingDataWithError:(NSError **)error {
z_stream zStream;
zStream.zalloc = Z_NULL;
zStream.zfree = Z_NULL;
zStream.next_in = (Bytef *)[self bytes];
zStream.avail_in = [self length];
zStream.avail_out = 0;
zStream.total_out = 0;
NSUInteger estimatedLength = [self length] / 2;
NSMutableData *decompressedData = [NSMutableData dataWithLength:estimatedLength];
do {
if (zStream.total_out >= [decompressedData length]) {
[decompressedData increaseLengthBy:estimatedLength / 2];
}
zStream.next_out = [decompressedData mutableBytes] + zStream.total_out;
zStream.avail_out = [decompressedData length] - zStream.total_out;
int status = inflate(&zStream, Z_FINISH);
if (status == Z_STREAM_END) {
break;
} else if (status != Z_OK) {
if (error) {
*error = [NSError errorWithDomain:AFZlibErrorDomain code:status userInfo:nil];
}
return nil;
}
} while (zStream.avail_out == 0);
[decompressedData setLength:zStream.total_out];
return [NSData dataWithData:decompressedData];
}
@end

View file

@ -1,33 +0,0 @@
// NSMutableURLRequest+AFNetworking.h
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
@interface NSMutableURLRequest (AFNetworking)
- (void)setHTTPBodyWithData:(NSData *)data
mimeType:(NSString *)mimeType
forParameterNamed:(NSString *)parameterName
parameters:(NSDictionary *)parameters
useGzipCompression:(BOOL)useGzipCompression;
@end

View file

@ -1,76 +0,0 @@
// NSMutableURLRequest+AFNetworking.m
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "NSMutableURLRequest+AFNetworking.h"
#import "NSData+AFNetworking.h"
@implementation NSMutableURLRequest (AFNetworking)
- (void)setHTTPBodyWithData:(NSData *)data
mimeType:(NSString *)mimeType
forParameterNamed:(NSString *)parameterName
parameters:(NSDictionary *)parameters
useGzipCompression:(BOOL)useGzipCompression
{
if ([[self HTTPMethod] isEqualToString:@"GET"]) {
[self setHTTPMethod:@"POST"];
}
NSString *filename = [[NSString stringWithFormat:@"%d", [[NSDate date] hash]] stringByAppendingPathExtension:[mimeType lastPathComponent]];
static NSString * const boundary = @"----Boundary+0xAbCdEfGbOuNdArY";
[self setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary] forHTTPHeaderField:@"Content-Type"];
NSMutableData *mutableData = [NSMutableData data];
[mutableData appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
id key;
NSEnumerator *enumerator = [parameters keyEnumerator];
while ((key = [enumerator nextObject])) {
[mutableData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n", key] dataUsingEncoding:NSUTF8StringEncoding]];
[mutableData appendData:[[NSString stringWithFormat:@"%@", [parameters valueForKey:key]] dataUsingEncoding:NSUTF8StringEncoding]];
[mutableData appendData:[[NSString stringWithFormat:@"\r\n--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
}
[mutableData appendData:[[NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"", parameterName, filename, nil] dataUsingEncoding:NSUTF8StringEncoding]];
[mutableData appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[mutableData appendData:[[NSString stringWithFormat:@"Content-Type: %@", mimeType] dataUsingEncoding:NSUTF8StringEncoding]];
[mutableData appendData:[@"\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[mutableData appendData:data];
[mutableData appendData:[[NSString stringWithFormat:@"\r\n--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
if (useGzipCompression) {
NSError *error = nil;
NSData *compressedData = [mutableData dataByGZipCompressingWithError:&error];
if (!error && compressedData) {
[self setHTTPBody:compressedData];
// Content-Encoding HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
[self setValue:@"gzip" forHTTPHeaderField:@"Content-Encoding"];
}
} else {
[self setHTTPBody:mutableData];
}
}
@end

View file

@ -1,30 +0,0 @@
// NSString+AFNetworking.h
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
@interface NSString (AFNetworking)
- (NSString *)urlEncodedString;
- (NSString *)urlEncodedStringWithEncoding:(NSStringEncoding)encoding;
@end

View file

@ -1,38 +0,0 @@
// NSString+AFNetworking.m
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "NSString+AFNetworking.h"
@implementation NSString (AFNetworking)
- (NSString*)urlEncodedString {
return [self urlEncodedStringWithEncoding:NSUTF8StringEncoding];
}
// See http://github.com/pokeb/asi-http-request/raw/master/Classes/ASIFormDataRequest.m
- (NSString *)urlEncodedStringWithEncoding:(NSStringEncoding)encoding {
NSString *urlEncodedString = [(NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)self, NULL, (CFStringRef)@":/?#[]@!$ &'()*+,;=\"<>%{}|\\^~`", CFStringConvertNSStringEncodingToEncoding(encoding)) autorelease];
return urlEncodedString ? urlEncodedString : @"";
}
@end

View file

@ -1,28 +0,0 @@
// UIImage+AFNetworking.h
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
@interface UIImage (AFNetworking)
+ (UIImage *)imageByScalingAndCroppingImage:(UIImage *)image size:(CGSize)size;
+ (UIImage *)imageByRoundingCornersOfImage:(UIImage *)image corners:(UIRectCorner)corners cornerRadii:(CGSize)radii;
@end

View file

@ -1,80 +0,0 @@
// UIImage+AFNetworking.m
//
// Copyright (c) 2011 Gowalla (http://gowalla.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "UIImage+AFNetworking.h"
@implementation UIImage (AFNetworking)
+ (UIImage *)imageByScalingAndCroppingImage:(UIImage *)image size:(CGSize)size {
if (image == nil) {
return nil;
} else if (CGSizeEqualToSize(image.size, size) || CGSizeEqualToSize(size, CGSizeZero)) {
return image;
}
CGSize scaledSize = size;
CGPoint thumbnailPoint = CGPointZero;
CGFloat widthFactor = size.width / image.size.width;
CGFloat heightFactor = size.height / image.size.height;
CGFloat scaleFactor = (widthFactor > heightFactor) ? widthFactor : heightFactor;
scaledSize.width = image.size.width * scaleFactor;
scaledSize.height = image.size.height * scaleFactor;
if (widthFactor > heightFactor) {
thumbnailPoint.y = (size.height - scaledSize.height) * 0.5;
} else if (widthFactor < heightFactor) {
thumbnailPoint.x = (size.width - scaledSize.width) * 0.5;
}
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
[image drawInRect:CGRectMake(thumbnailPoint.x, thumbnailPoint.y, scaledSize.width, scaledSize.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
+ (UIImage *)imageByRoundingCornersOfImage:(UIImage *)image corners:(UIRectCorner)corners cornerRadii:(CGSize)radii {
if (image == nil) {
return nil;
} else if(UIGraphicsBeginImageContextWithOptions != NULL) {
UIGraphicsBeginImageContextWithOptions(image.size, NO, 0.0);
} else {
UIGraphicsBeginImageContext(image.size);
}
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextBeginPath(context);
CGContextAddPath(context, [[UIBezierPath bezierPathWithRoundedRect:CGRectMake(0.0f, 0.0f, image.size.width, image.size.height) byRoundingCorners:corners cornerRadii:radii] CGPath]);
CGContextClosePath(context);
CGContextClip(context);
CGRect rect = CGRectMake(0.0f, 0.0f, image.size.width, image.size.height);
[image drawInRect:rect];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
@end

View file

@ -23,23 +23,50 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
#import "AFImageRequestOperation.h" #import "AFImageRequestOperation.h"
/**
This category adds methods to the UIKit framework's `UIImageView` class. The methods in this category provide support for loading remote images asynchronously from a URL.
*/
@interface UIImageView (AFNetworking) @interface UIImageView (AFNetworking)
/**
Creates and enqueues an image request operation, which asynchronously downloads the image from the specified URL, and sets it the request is finished. If the image is cached locally, the image is set immediately, otherwise, the image is set once the request is finished.
@discussion By default, url requests have a cache policy of `NSURLCacheStorageAllowed` and a timeout interval of 30 seconds, and are set to use HTTP pipelining, and not handle cookies. To configure url requests differently, use `setImageWithURLRequest:placeholderImage:success:failure:`
@param url The URL used for the image request.
*/
- (void)setImageWithURL:(NSURL *)url; - (void)setImageWithURL:(NSURL *)url;
/**
Creates and enqueues an image request operation, which asynchronously downloads the image from the specified URL. If the image is cached locally, the image is set immediately. Otherwise, the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished.
@param url The URL used for the image request.
@param placeholderImage The image to be set initially, until the image request finishes. If `nil`, the image view will not change its image until the image request finishes.
@discussion By default, url requests have a cache policy of `NSURLCacheStorageAllowed` and a timeout interval of 30 seconds, and are set to use HTTP pipelining, and not handle cookies. To configure url requests differently, use `setImageWithURLRequest:placeholderImage:success:failure:`
@warning If `placeholderImage` is specified, the remote image will be resized to the dimensions of the placeholder image before being set.
*/
- (void)setImageWithURL:(NSURL *)url - (void)setImageWithURL:(NSURL *)url
placeholderImage:(UIImage *)placeholderImage; placeholderImage:(UIImage *)placeholderImage;
- (void)setImageWithURL:(NSURL *)url /**
placeholderImage:(UIImage *)placeholderImage Creates and enqueues an image request operation, which asynchronously downloads the image with the specified url request object. If the image is cached locally, the image is set immediately. Otherwise, the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished.
imageSize:(CGSize)imageSize
options:(AFImageRequestOptions)options;
- (void)setImageWithURL:(NSURL *)url @param urlRequest The url request used for the image request.
@param placeholderImage The image to be set initially, until the image request finishes. If `nil`, the image view will not change its image until the image request finishes.
@param success A block to be executed when the image request operation finishes successfully, with a status code in the 2xx range, and with an acceptable content type (e.g. `image/png`). This block has no return value and takes three arguments, the request sent from the client, the response received from the server, and the image created from the response data of request. If the image was returned from cache, the request and response parameters will be `nil`.
@param failure A block object to be executed when the image request operation finishes unsuccessfully, or that finishes successfully. This block has no return value and takes three arguments, the request sent from the client, the response received from the server, and the error object describing the network or parsing error that occurred.
@discussion By default, url requests have a cache policy of `NSURLCacheStorageAllowed` and a timeout interval of 30 seconds, and are set to use HTTP pipelining, and not handle cookies. To configure url requests differently, use `setImageWithURLRequest:placeholderImage:success:failure:`
@warning If `placeholderImage` is specified, the remote image will be resized to the dimensions of the placeholder image before being set.
*/
- (void)setImageWithURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(UIImage *)placeholderImage placeholderImage:(UIImage *)placeholderImage
imageSize:(CGSize)imageSize success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response,UIImage *image))success
options:(AFImageRequestOptions)options failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure;
block:(void (^)(UIImage *image, BOOL cacheUsed))block;
- (void)cancelImageRequestOperation; - (void)cancelImageRequestOperation;

View file

@ -27,34 +27,63 @@
#import "AFImageCache.h" #import "AFImageCache.h"
static UIImage * AFImageByScalingAndCroppingImageToSize(UIImage *image, CGSize size) {
if (image == nil) {
return nil;
} else if (CGSizeEqualToSize(image.size, size) || CGSizeEqualToSize(size, CGSizeZero)) {
return image;
}
CGSize scaledSize = size;
CGPoint thumbnailPoint = CGPointZero;
CGFloat widthFactor = size.width / image.size.width;
CGFloat heightFactor = size.height / image.size.height;
CGFloat scaleFactor = (widthFactor > heightFactor) ? widthFactor : heightFactor;
scaledSize.width = image.size.width * scaleFactor;
scaledSize.height = image.size.height * scaleFactor;
if (widthFactor > heightFactor) {
thumbnailPoint.y = (size.height - scaledSize.height) * 0.5;
} else if (widthFactor < heightFactor) {
thumbnailPoint.x = (size.width - scaledSize.width) * 0.5;
}
UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
[image drawInRect:CGRectMake(thumbnailPoint.x, thumbnailPoint.y, scaledSize.width, scaledSize.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
static NSString * const kUIImageViewImageRequestObjectKey = @"_af_imageRequestOperation"; static NSString * const kUIImageViewImageRequestObjectKey = @"_af_imageRequestOperation";
@interface UIImageView (_AFNetworking) @interface UIImageView (_AFNetworking)
@property (readwrite, nonatomic, retain) AFImageRequestOperation *imageRequestOperation; @property (readwrite, nonatomic, retain, setter = af_setImageRequestOperation:) AFImageRequestOperation *af_imageRequestOperation;
@end @end
@implementation UIImageView (_AFNetworking) @implementation UIImageView (_AFNetworking)
@dynamic imageRequestOperation; @dynamic af_imageRequestOperation;
@end @end
#pragma mark - #pragma mark -
@implementation UIImageView (AFNetworking) @implementation UIImageView (AFNetworking)
- (AFHTTPRequestOperation *)imageRequestOperation { - (AFHTTPRequestOperation *)af_imageRequestOperation {
return (AFHTTPRequestOperation *)objc_getAssociatedObject(self, kUIImageViewImageRequestObjectKey); return (AFHTTPRequestOperation *)objc_getAssociatedObject(self, kUIImageViewImageRequestObjectKey);
} }
- (void)setImageRequestOperation:(AFImageRequestOperation *)imageRequestOperation { - (void)af_setImageRequestOperation:(AFImageRequestOperation *)imageRequestOperation {
objc_setAssociatedObject(self, kUIImageViewImageRequestObjectKey, imageRequestOperation, OBJC_ASSOCIATION_RETAIN_NONATOMIC); objc_setAssociatedObject(self, kUIImageViewImageRequestObjectKey, imageRequestOperation, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
} }
+ (NSOperationQueue *)sharedImageRequestOperationQueue { + (NSOperationQueue *)af_sharedImageRequestOperationQueue {
static NSOperationQueue *_imageRequestOperationQueue = nil; static NSOperationQueue *_imageRequestOperationQueue = nil;
if (!_imageRequestOperationQueue) { if (!_imageRequestOperationQueue) {
_imageRequestOperationQueue = [[NSOperationQueue alloc] init]; _imageRequestOperationQueue = [[NSOperationQueue alloc] init];
[_imageRequestOperationQueue setMaxConcurrentOperationCount:6]; [_imageRequestOperationQueue setMaxConcurrentOperationCount:8];
} }
return _imageRequestOperationQueue; return _imageRequestOperationQueue;
@ -69,63 +98,75 @@ static NSString * const kUIImageViewImageRequestObjectKey = @"_af_imageRequestOp
- (void)setImageWithURL:(NSURL *)url - (void)setImageWithURL:(NSURL *)url
placeholderImage:(UIImage *)placeholderImage placeholderImage:(UIImage *)placeholderImage
{ {
[self setImageWithURL:url placeholderImage:placeholderImage imageSize:self.frame.size options:AFImageRequestDefaultOptions]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLCacheStorageAllowed timeoutInterval:30.0];
[request setHTTPShouldHandleCookies:NO];
[request setHTTPShouldUsePipelining:YES];
[self setImageWithURLRequest:request placeholderImage:placeholderImage success:nil failure:nil];
} }
- (void)setImageWithURL:(NSURL *)url - (void)setImageWithURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(UIImage *)placeholderImage placeholderImage:(UIImage *)placeholderImage
imageSize:(CGSize)imageSize success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response,UIImage *image))success
options:(AFImageRequestOptions)options failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure
{ {
[self setImageWithURL:url placeholderImage:placeholderImage imageSize:imageSize options:options block:nil]; if (![urlRequest URL] || (![self.af_imageRequestOperation isCancelled] && [[urlRequest URL] isEqual:self.af_imageRequestOperation.request.URL])) {
}
- (void)setImageWithURL:(NSURL *)url
placeholderImage:(UIImage *)placeholderImage
imageSize:(CGSize)imageSize
options:(AFImageRequestOptions)options
block:(void (^)(UIImage *image, BOOL cacheUsed))block
{
if (!url || [url isEqual:self.imageRequestOperation.request.URL]) {
return; return;
} else { } else {
[self cancelImageRequestOperation]; [self cancelImageRequestOperation];
} }
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLCacheStorageAllowed timeoutInterval:30.0]; NSString *cacheName = @"UIImageView";
[request setHTTPShouldHandleCookies:NO]; if (placeholderImage) {
[request setHTTPShouldUsePipelining:YES]; cacheName = [cacheName stringByAppendingFormat:@"(%@)", NSStringFromCGSize(placeholderImage.size)];
}
UIImage *cachedImage = [[AFImageCache sharedImageCache] cachedImageForRequest:request imageSize:imageSize options:options]; UIImage *cachedImage = [[AFImageCache sharedImageCache] cachedImageForURL:[urlRequest URL] cacheName:cacheName];
if (cachedImage) { if (cachedImage) {
self.image = cachedImage; self.image = cachedImage;
if (block) { if (success) {
block(cachedImage, YES); success(nil, nil, cachedImage);
} }
} else { } else {
self.image = placeholderImage; self.image = placeholderImage;
self.imageRequestOperation = [AFImageRequestOperation operationWithRequest:request imageSize:imageSize options:options success:^(UIImage *image) { self.af_imageRequestOperation = [AFImageRequestOperation operationWithRequest:urlRequest imageProcessingBlock:^UIImage *(UIImage *image) {
if (self.imageRequestOperation && ![self.imageRequestOperation isCancelled]) { if (placeholderImage) {
if (block) { image = AFImageByScalingAndCroppingImageToSize(image, placeholderImage.size);
block(image, NO);
} }
if ([[request URL] isEqual:[[self.imageRequestOperation request] URL]]) { return image;
} cacheName:cacheName success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
if (self.af_imageRequestOperation && ![self.af_imageRequestOperation isCancelled]) {
dispatch_async(dispatch_get_main_queue(), ^{
if (success) {
success(request, response, image);
}
if ([[request URL] isEqual:[[self.af_imageRequestOperation request] URL]]) {
self.image = image; self.image = image;
} else { } else {
self.image = placeholderImage; self.image = placeholderImage;
} }
});
} }
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
self.af_imageRequestOperation = nil;
dispatch_async(dispatch_get_main_queue(), ^{
if (failure) {
failure(request, response, error);
}
});
}]; }];
[[[self class] sharedImageRequestOperationQueue] addOperation:self.imageRequestOperation]; [[[self class] af_sharedImageRequestOperationQueue] addOperation:self.af_imageRequestOperation];
} }
} }
- (void)cancelImageRequestOperation { - (void)cancelImageRequestOperation {
[self.imageRequestOperation cancel]; [self.af_imageRequestOperation cancel];
} }
@end @end

View file

@ -7,16 +7,11 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
F85CE2D413EC478F00BFAE01 /* NSString+AFNetworking.m in Sources */ = {isa = PBXBuildFile; fileRef = F85CE2D313EC478F00BFAE01 /* NSString+AFNetworking.m */; };
F85CE2DC13EC4A4200BFAE01 /* NSMutableURLRequest+AFNetworking.m in Sources */ = {isa = PBXBuildFile; fileRef = F85CE2DB13EC4A4200BFAE01 /* NSMutableURLRequest+AFNetworking.m */; };
F85CE55513EC759200BFAE01 /* NSData+AFNetworking.m in Sources */ = {isa = PBXBuildFile; fileRef = F85CE55413EC759200BFAE01 /* NSData+AFNetworking.m */; };
F874B5D913E0AA6500B28E3E /* AFHTTPRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = F874B5C913E0AA6500B28E3E /* AFHTTPRequestOperation.m */; }; F874B5D913E0AA6500B28E3E /* AFHTTPRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = F874B5C913E0AA6500B28E3E /* AFHTTPRequestOperation.m */; };
F874B5DA13E0AA6500B28E3E /* AFImageCache.m in Sources */ = {isa = PBXBuildFile; fileRef = F874B5CA13E0AA6500B28E3E /* AFImageCache.m */; }; F874B5DA13E0AA6500B28E3E /* AFImageCache.m in Sources */ = {isa = PBXBuildFile; fileRef = F874B5CA13E0AA6500B28E3E /* AFImageCache.m */; };
F874B5DB13E0AA6500B28E3E /* AFImageRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = F874B5CB13E0AA6500B28E3E /* AFImageRequestOperation.m */; }; F874B5DB13E0AA6500B28E3E /* AFImageRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = F874B5CB13E0AA6500B28E3E /* AFImageRequestOperation.m */; };
F874B5DC13E0AA6500B28E3E /* AFJSONRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = F874B5CC13E0AA6500B28E3E /* AFJSONRequestOperation.m */; }; F874B5DC13E0AA6500B28E3E /* AFJSONRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = F874B5CC13E0AA6500B28E3E /* AFJSONRequestOperation.m */; };
F874B5DD13E0AA6500B28E3E /* AFNetworkActivityIndicatorManager.m in Sources */ = {isa = PBXBuildFile; fileRef = F874B5CD13E0AA6500B28E3E /* AFNetworkActivityIndicatorManager.m */; }; F874B5DD13E0AA6500B28E3E /* AFNetworkActivityIndicatorManager.m in Sources */ = {isa = PBXBuildFile; fileRef = F874B5CD13E0AA6500B28E3E /* AFNetworkActivityIndicatorManager.m */; };
F874B5DE13E0AA6500B28E3E /* AFRestClient.m in Sources */ = {isa = PBXBuildFile; fileRef = F874B5CE13E0AA6500B28E3E /* AFRestClient.m */; };
F874B5DF13E0AA6500B28E3E /* UIImage+AFNetworking.m in Sources */ = {isa = PBXBuildFile; fileRef = F874B5CF13E0AA6500B28E3E /* UIImage+AFNetworking.m */; };
F874B5E013E0AA6500B28E3E /* UIImageView+AFNetworking.m in Sources */ = {isa = PBXBuildFile; fileRef = F874B5D013E0AA6500B28E3E /* UIImageView+AFNetworking.m */; }; F874B5E013E0AA6500B28E3E /* UIImageView+AFNetworking.m in Sources */ = {isa = PBXBuildFile; fileRef = F874B5D013E0AA6500B28E3E /* UIImageView+AFNetworking.m */; };
F8D25D191396A9D300CF3BD6 /* placeholder-stamp.png in Resources */ = {isa = PBXBuildFile; fileRef = F8D25D171396A9D300CF3BD6 /* placeholder-stamp.png */; }; F8D25D191396A9D300CF3BD6 /* placeholder-stamp.png in Resources */ = {isa = PBXBuildFile; fileRef = F8D25D171396A9D300CF3BD6 /* placeholder-stamp.png */; };
F8D25D1A1396A9D300CF3BD6 /* placeholder-stamp@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F8D25D181396A9D300CF3BD6 /* placeholder-stamp@2x.png */; }; F8D25D1A1396A9D300CF3BD6 /* placeholder-stamp@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F8D25D181396A9D300CF3BD6 /* placeholder-stamp@2x.png */; };
@ -32,31 +27,22 @@
F8E469671395739D00DB05C8 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8E469661395739D00DB05C8 /* Foundation.framework */; }; F8E469671395739D00DB05C8 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8E469661395739D00DB05C8 /* Foundation.framework */; };
F8E469691395739D00DB05C8 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8E469681395739D00DB05C8 /* CoreGraphics.framework */; }; F8E469691395739D00DB05C8 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8E469681395739D00DB05C8 /* CoreGraphics.framework */; };
F8E469DF13957DD500DB05C8 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8E469DE13957DD500DB05C8 /* CoreLocation.framework */; }; F8E469DF13957DD500DB05C8 /* CoreLocation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8E469DE13957DD500DB05C8 /* CoreLocation.framework */; };
F8FBFA98142AA239001409DB /* AFHTTPClient.m in Sources */ = {isa = PBXBuildFile; fileRef = F8FBFA97142AA238001409DB /* AFHTTPClient.m */; };
FF2B770D140DAC4E00A8DEC2 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = FF2B770C140DAC4E00A8DEC2 /* libz.dylib */; }; FF2B770D140DAC4E00A8DEC2 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = FF2B770C140DAC4E00A8DEC2 /* libz.dylib */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
F85CE2D213EC478F00BFAE01 /* NSString+AFNetworking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSString+AFNetworking.h"; path = "../AFNetworking/NSString+AFNetworking.h"; sourceTree = "<group>"; };
F85CE2D313EC478F00BFAE01 /* NSString+AFNetworking.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSString+AFNetworking.m"; path = "../AFNetworking/NSString+AFNetworking.m"; sourceTree = "<group>"; };
F85CE2DA13EC4A4200BFAE01 /* NSMutableURLRequest+AFNetworking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSMutableURLRequest+AFNetworking.h"; path = "../AFNetworking/NSMutableURLRequest+AFNetworking.h"; sourceTree = "<group>"; };
F85CE2DB13EC4A4200BFAE01 /* NSMutableURLRequest+AFNetworking.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSMutableURLRequest+AFNetworking.m"; path = "../AFNetworking/NSMutableURLRequest+AFNetworking.m"; sourceTree = "<group>"; };
F85CE55313EC759100BFAE01 /* NSData+AFNetworking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSData+AFNetworking.h"; path = "../AFNetworking/NSData+AFNetworking.h"; sourceTree = "<group>"; };
F85CE55413EC759200BFAE01 /* NSData+AFNetworking.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "NSData+AFNetworking.m"; path = "../AFNetworking/NSData+AFNetworking.m"; sourceTree = "<group>"; };
F874B5C913E0AA6500B28E3E /* AFHTTPRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AFHTTPRequestOperation.m; path = ../AFNetworking/AFHTTPRequestOperation.m; sourceTree = "<group>"; }; F874B5C913E0AA6500B28E3E /* AFHTTPRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AFHTTPRequestOperation.m; path = ../AFNetworking/AFHTTPRequestOperation.m; sourceTree = "<group>"; };
F874B5CA13E0AA6500B28E3E /* AFImageCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AFImageCache.m; path = ../AFNetworking/AFImageCache.m; sourceTree = "<group>"; }; F874B5CA13E0AA6500B28E3E /* AFImageCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AFImageCache.m; path = ../AFNetworking/AFImageCache.m; sourceTree = "<group>"; };
F874B5CB13E0AA6500B28E3E /* AFImageRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AFImageRequestOperation.m; path = ../AFNetworking/AFImageRequestOperation.m; sourceTree = "<group>"; }; F874B5CB13E0AA6500B28E3E /* AFImageRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AFImageRequestOperation.m; path = ../AFNetworking/AFImageRequestOperation.m; sourceTree = "<group>"; };
F874B5CC13E0AA6500B28E3E /* AFJSONRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AFJSONRequestOperation.m; path = ../AFNetworking/AFJSONRequestOperation.m; sourceTree = "<group>"; }; F874B5CC13E0AA6500B28E3E /* AFJSONRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AFJSONRequestOperation.m; path = ../AFNetworking/AFJSONRequestOperation.m; sourceTree = "<group>"; };
F874B5CD13E0AA6500B28E3E /* AFNetworkActivityIndicatorManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AFNetworkActivityIndicatorManager.m; path = ../AFNetworking/AFNetworkActivityIndicatorManager.m; sourceTree = "<group>"; }; F874B5CD13E0AA6500B28E3E /* AFNetworkActivityIndicatorManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AFNetworkActivityIndicatorManager.m; path = ../AFNetworking/AFNetworkActivityIndicatorManager.m; sourceTree = "<group>"; };
F874B5CE13E0AA6500B28E3E /* AFRestClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AFRestClient.m; path = ../AFNetworking/AFRestClient.m; sourceTree = "<group>"; };
F874B5CF13E0AA6500B28E3E /* UIImage+AFNetworking.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIImage+AFNetworking.m"; path = "../AFNetworking/UIImage+AFNetworking.m"; sourceTree = "<group>"; };
F874B5D013E0AA6500B28E3E /* UIImageView+AFNetworking.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIImageView+AFNetworking.m"; path = "../AFNetworking/UIImageView+AFNetworking.m"; sourceTree = "<group>"; }; F874B5D013E0AA6500B28E3E /* UIImageView+AFNetworking.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIImageView+AFNetworking.m"; path = "../AFNetworking/UIImageView+AFNetworking.m"; sourceTree = "<group>"; };
F874B5D113E0AA6500B28E3E /* AFHTTPRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AFHTTPRequestOperation.h; path = ../AFNetworking/AFHTTPRequestOperation.h; sourceTree = "<group>"; }; F874B5D113E0AA6500B28E3E /* AFHTTPRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AFHTTPRequestOperation.h; path = ../AFNetworking/AFHTTPRequestOperation.h; sourceTree = "<group>"; };
F874B5D213E0AA6500B28E3E /* AFImageCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AFImageCache.h; path = ../AFNetworking/AFImageCache.h; sourceTree = "<group>"; }; F874B5D213E0AA6500B28E3E /* AFImageCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AFImageCache.h; path = ../AFNetworking/AFImageCache.h; sourceTree = "<group>"; };
F874B5D313E0AA6500B28E3E /* AFImageRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AFImageRequestOperation.h; path = ../AFNetworking/AFImageRequestOperation.h; sourceTree = "<group>"; }; F874B5D313E0AA6500B28E3E /* AFImageRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AFImageRequestOperation.h; path = ../AFNetworking/AFImageRequestOperation.h; sourceTree = "<group>"; };
F874B5D413E0AA6500B28E3E /* AFJSONRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AFJSONRequestOperation.h; path = ../AFNetworking/AFJSONRequestOperation.h; sourceTree = "<group>"; }; F874B5D413E0AA6500B28E3E /* AFJSONRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AFJSONRequestOperation.h; path = ../AFNetworking/AFJSONRequestOperation.h; sourceTree = "<group>"; };
F874B5D513E0AA6500B28E3E /* AFNetworkActivityIndicatorManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AFNetworkActivityIndicatorManager.h; path = ../AFNetworking/AFNetworkActivityIndicatorManager.h; sourceTree = "<group>"; }; F874B5D513E0AA6500B28E3E /* AFNetworkActivityIndicatorManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AFNetworkActivityIndicatorManager.h; path = ../AFNetworking/AFNetworkActivityIndicatorManager.h; sourceTree = "<group>"; };
F874B5D613E0AA6500B28E3E /* AFRestClient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; name = AFRestClient.h; path = ../AFNetworking/AFRestClient.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
F874B5D713E0AA6500B28E3E /* UIImage+AFNetworking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIImage+AFNetworking.h"; path = "../AFNetworking/UIImage+AFNetworking.h"; sourceTree = "<group>"; };
F874B5D813E0AA6500B28E3E /* UIImageView+AFNetworking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIImageView+AFNetworking.h"; path = "../AFNetworking/UIImageView+AFNetworking.h"; sourceTree = "<group>"; }; F874B5D813E0AA6500B28E3E /* UIImageView+AFNetworking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIImageView+AFNetworking.h"; path = "../AFNetworking/UIImageView+AFNetworking.h"; sourceTree = "<group>"; };
F8D25D101396A9C400CF3BD6 /* JSONKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONKit.h; sourceTree = "<group>"; }; F8D25D101396A9C400CF3BD6 /* JSONKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONKit.h; sourceTree = "<group>"; };
F8D25D111396A9C400CF3BD6 /* JSONKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSONKit.m; sourceTree = "<group>"; }; F8D25D111396A9C400CF3BD6 /* JSONKit.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JSONKit.m; sourceTree = "<group>"; };
@ -85,6 +71,8 @@
F8E469E013957DF100DB05C8 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; F8E469E013957DF100DB05C8 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
F8E469E213957DF700DB05C8 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; F8E469E213957DF700DB05C8 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
F8E469E413957E0400DB05C8 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; }; F8E469E413957E0400DB05C8 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
F8FBFA96142AA237001409DB /* AFHTTPClient.h */ = {isa = PBXFileReference; fileEncoding = 4; name = AFHTTPClient.h; path = ../AFNetworking/AFHTTPClient.h; sourceTree = "<group>"; };
F8FBFA97142AA238001409DB /* AFHTTPClient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AFHTTPClient.m; path = ../AFNetworking/AFHTTPClient.m; sourceTree = "<group>"; };
FF2B770C140DAC4E00A8DEC2 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; }; FF2B770C140DAC4E00A8DEC2 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@ -107,16 +95,8 @@
F85CE2D613EC47BC00BFAE01 /* Categories */ = { F85CE2D613EC47BC00BFAE01 /* Categories */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
F85CE2DA13EC4A4200BFAE01 /* NSMutableURLRequest+AFNetworking.h */,
F85CE2DB13EC4A4200BFAE01 /* NSMutableURLRequest+AFNetworking.m */,
F85CE2D213EC478F00BFAE01 /* NSString+AFNetworking.h */,
F85CE2D313EC478F00BFAE01 /* NSString+AFNetworking.m */,
F874B5D713E0AA6500B28E3E /* UIImage+AFNetworking.h */,
F874B5CF13E0AA6500B28E3E /* UIImage+AFNetworking.m */,
F874B5D813E0AA6500B28E3E /* UIImageView+AFNetworking.h */, F874B5D813E0AA6500B28E3E /* UIImageView+AFNetworking.h */,
F874B5D013E0AA6500B28E3E /* UIImageView+AFNetworking.m */, F874B5D013E0AA6500B28E3E /* UIImageView+AFNetworking.m */,
F85CE55313EC759100BFAE01 /* NSData+AFNetworking.h */,
F85CE55413EC759200BFAE01 /* NSData+AFNetworking.m */,
); );
name = Categories; name = Categories;
sourceTree = "<group>"; sourceTree = "<group>";
@ -247,8 +227,8 @@
F874B5C913E0AA6500B28E3E /* AFHTTPRequestOperation.m */, F874B5C913E0AA6500B28E3E /* AFHTTPRequestOperation.m */,
F874B5D413E0AA6500B28E3E /* AFJSONRequestOperation.h */, F874B5D413E0AA6500B28E3E /* AFJSONRequestOperation.h */,
F874B5CC13E0AA6500B28E3E /* AFJSONRequestOperation.m */, F874B5CC13E0AA6500B28E3E /* AFJSONRequestOperation.m */,
F874B5D613E0AA6500B28E3E /* AFRestClient.h */, F8FBFA96142AA237001409DB /* AFHTTPClient.h */,
F874B5CE13E0AA6500B28E3E /* AFRestClient.m */, F8FBFA97142AA238001409DB /* AFHTTPClient.m */,
F874B5D313E0AA6500B28E3E /* AFImageRequestOperation.h */, F874B5D313E0AA6500B28E3E /* AFImageRequestOperation.h */,
F874B5CB13E0AA6500B28E3E /* AFImageRequestOperation.m */, F874B5CB13E0AA6500B28E3E /* AFImageRequestOperation.m */,
F874B5D213E0AA6500B28E3E /* AFImageCache.h */, F874B5D213E0AA6500B28E3E /* AFImageCache.h */,
@ -355,12 +335,8 @@
F874B5DB13E0AA6500B28E3E /* AFImageRequestOperation.m in Sources */, F874B5DB13E0AA6500B28E3E /* AFImageRequestOperation.m in Sources */,
F874B5DC13E0AA6500B28E3E /* AFJSONRequestOperation.m in Sources */, F874B5DC13E0AA6500B28E3E /* AFJSONRequestOperation.m in Sources */,
F874B5DD13E0AA6500B28E3E /* AFNetworkActivityIndicatorManager.m in Sources */, F874B5DD13E0AA6500B28E3E /* AFNetworkActivityIndicatorManager.m in Sources */,
F874B5DE13E0AA6500B28E3E /* AFRestClient.m in Sources */,
F874B5DF13E0AA6500B28E3E /* UIImage+AFNetworking.m in Sources */,
F874B5E013E0AA6500B28E3E /* UIImageView+AFNetworking.m in Sources */, F874B5E013E0AA6500B28E3E /* UIImageView+AFNetworking.m in Sources */,
F85CE2D413EC478F00BFAE01 /* NSString+AFNetworking.m in Sources */, F8FBFA98142AA239001409DB /* AFHTTPClient.m in Sources */,
F85CE2DC13EC4A4200BFAE01 /* NSMutableURLRequest+AFNetworking.m in Sources */,
F85CE55513EC759200BFAE01 /* NSData+AFNetworking.m in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
version = "1.3"> version = "1.8">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"> buildImplicitDependencies = "YES">
@ -36,7 +36,9 @@
displayScale = "1.00" displayScale = "1.00"
launchStyle = "0" launchStyle = "0"
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
buildConfiguration = "Debug"> buildConfiguration = "Debug"
debugDocumentVersioning = "YES"
allowLocationSimulation = "YES">
<BuildableProductRunnable> <BuildableProductRunnable>
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
@ -47,6 +49,11 @@
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>
<AdditionalOptions> <AdditionalOptions>
<AdditionalOption
key = "NSZombieEnabled"
value = "YES"
isEnabled = "YES">
</AdditionalOption>
</AdditionalOptions> </AdditionalOptions>
</LaunchAction> </LaunchAction>
<ProfileAction <ProfileAction
@ -55,7 +62,8 @@
shouldUseLaunchSchemeArgsEnv = "YES" shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = "" savedToolIdentifier = ""
useCustomWorkingDirectory = "NO" useCustomWorkingDirectory = "NO"
buildConfiguration = "Release"> buildConfiguration = "Release"
debugDocumentVersioning = "YES">
<BuildableProductRunnable> <BuildableProductRunnable>
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"

View file

@ -21,11 +21,11 @@
// THE SOFTWARE. // THE SOFTWARE.
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "AFRestClient.h" #import "AFHTTPClient.h"
extern NSString * const kAFGowallaClientID; extern NSString * const kAFGowallaClientID;
extern NSString * const kAFGowallaBaseURLString; extern NSString * const kAFGowallaBaseURLString;
@interface AFGowallaAPIClient : AFRestClient @interface AFGowallaAPIClient : AFHTTPClient
+ (id)sharedClient; + (AFGowallaAPIClient *)sharedClient;
@end @end

View file

@ -22,8 +22,6 @@
#import "AFGowallaAPIClient.h" #import "AFGowallaAPIClient.h"
static AFGowallaAPIClient *_sharedClient = nil;
// Replace this with your own API Key, available at http://api.gowalla.com/api/keys/ // Replace this with your own API Key, available at http://api.gowalla.com/api/keys/
NSString * const kAFGowallaClientID = @"e7ccb7d3d2414eb2af4663fc91eb2793"; NSString * const kAFGowallaClientID = @"e7ccb7d3d2414eb2af4663fc91eb2793";
@ -31,7 +29,8 @@ NSString * const kAFGowallaBaseURLString = @"https://api.gowalla.com/";
@implementation AFGowallaAPIClient @implementation AFGowallaAPIClient
+ (id)sharedClient { + (AFGowallaAPIClient *)sharedClient {
static AFGowallaAPIClient *_sharedClient = nil;
static dispatch_once_t oncePredicate; static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{ dispatch_once(&oncePredicate, ^{
_sharedClient = [[self alloc] initWithBaseURL:[NSURL URLWithString:kAFGowallaBaseURLString]]; _sharedClient = [[self alloc] initWithBaseURL:[NSURL URLWithString:kAFGowallaBaseURLString]];

View file

@ -70,7 +70,7 @@
[self.activityIndicatorView startAnimating]; [self.activityIndicatorView startAnimating];
self.navigationItem.rightBarButtonItem.enabled = NO; self.navigationItem.rightBarButtonItem.enabled = NO;
[Spot spotsWithURLString:@"/spots/advanced_search" near:location parameters:[NSDictionary dictionaryWithObject:@"128" forKey:@"per_page"] block:^(NSArray *records) { [Spot spotsWithURLString:@"/spots" near:location parameters:[NSDictionary dictionaryWithObject:@"128" forKey:@"per_page"] block:^(NSArray *records) {
self.nearbySpots = [records sortedArrayUsingComparator:^ NSComparisonResult(id obj1, id obj2) { self.nearbySpots = [records sortedArrayUsingComparator:^ NSComparisonResult(id obj1, id obj2) {
CLLocationDistance d1 = [[(Spot *)obj1 location] distanceFromLocation:location]; CLLocationDistance d1 = [[(Spot *)obj1 location] distanceFromLocation:location];
CLLocationDistance d2 = [[(Spot *)obj2 location] distanceFromLocation:location]; CLLocationDistance d2 = [[(Spot *)obj2 location] distanceFromLocation:location];
@ -154,18 +154,22 @@
cell = [[[SpotTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease]; cell = [[[SpotTableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
} }
Spot *spot = [self.nearbySpots objectAtIndex:indexPath.row];
static TTTLocationFormatter *_locationFormatter = nil; static TTTLocationFormatter *_locationFormatter = nil;
if (!_locationFormatter) { if (!_locationFormatter) {
_locationFormatter = [[TTTLocationFormatter alloc] init]; _locationFormatter = [[TTTLocationFormatter alloc] init];
if (![[[NSLocale currentLocale] objectForKey:NSLocaleUsesMetricSystem] boolValue]) {
[_locationFormatter setUnitSystem:TTTImperialSystem]; [_locationFormatter setUnitSystem:TTTImperialSystem];
} }
}
Spot *spot = [self.nearbySpots objectAtIndex:indexPath.row];
cell.textLabel.text = spot.name;
if (self.locationManager.location) { if (self.locationManager.location) {
cell.detailTextLabel.text = [_locationFormatter stringFromDistanceAndBearingFromLocation:self.locationManager.location toLocation:spot.location]; cell.detailTextLabel.text = [_locationFormatter stringFromDistanceAndBearingFromLocation:self.locationManager.location toLocation:spot.location];
} }
[cell.imageView setImageWithURL:[NSURL URLWithString:spot.imageURLString] placeholderImage:[UIImage imageNamed:@"placeholder-stamp.png"]];
cell.spot = spot;
return cell; return cell;
} }

View file

@ -22,6 +22,10 @@
#import <UIKit/UIKit.h> #import <UIKit/UIKit.h>
@class Spot;
@interface SpotTableViewCell : UITableViewCell @interface SpotTableViewCell : UITableViewCell
@property (nonatomic, retain) Spot *spot;
@end @end

View file

@ -22,7 +22,12 @@
#import "SpotTableViewCell.h" #import "SpotTableViewCell.h"
#import "Spot.h"
#import "UIImageView+AFNetworking.h"
@implementation SpotTableViewCell @implementation SpotTableViewCell
@synthesize spot = _spot;
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
@ -32,35 +37,40 @@
self.textLabel.textColor = [UIColor darkGrayColor]; self.textLabel.textColor = [UIColor darkGrayColor];
self.textLabel.numberOfLines = 2; self.textLabel.numberOfLines = 2;
self.textLabel.backgroundColor = self.backgroundColor;
self.detailTextLabel.textColor = [UIColor grayColor]; self.detailTextLabel.textColor = [UIColor grayColor];
self.detailTextLabel.backgroundColor = self.backgroundColor;
self.imageView.backgroundColor = self.backgroundColor;
self.selectionStyle = UITableViewCellSelectionStyleGray; self.selectionStyle = UITableViewCellSelectionStyleGray;
return self; return self;
} }
#pragma mark - UIView - (void)dealloc {
[_spot release];
[super dealloc];
}
- (void)layoutSubviews { - (void)setSpot:(Spot *)spot {
[super layoutSubviews]; [self willChangeValueForKey:@"spot"];
[_spot autorelease];
_spot = [spot retain];
[self didChangeValueForKey:@"spot"];
CGRect imageViewFrame = self.imageView.frame; [self.imageView setImageWithURL:[NSURL URLWithString:self.spot.imageURLString] placeholderImage:[UIImage imageNamed:@"placeholder-stamp.png"]];
CGRect textLabelFrame = self.textLabel.frame;
CGRect detailTextLabelFrame = self.detailTextLabel.frame;
imageViewFrame.origin = CGPointMake(10.0f, 10.0f); self.textLabel.text = spot.name;
imageViewFrame.size = CGSizeMake(50.0f, 50.0f); }
textLabelFrame.origin.x = imageViewFrame.size.width + 25.0f; #pragma mark - UITableViewCell
detailTextLabelFrame.origin.x = textLabelFrame.origin.x;
textLabelFrame.size.width = 240.0f; - (void)prepareForReuse {
detailTextLabelFrame.size.width = textLabelFrame.size.width; [self.imageView cancelImageRequestOperation];
self.textLabel.text = nil;
self.textLabel.frame = textLabelFrame; self.detailTextLabel.text = nil;
self.detailTextLabel.frame = detailTextLabelFrame;
self.imageView.frame = imageViewFrame;
} }
@end @end

View file

@ -47,11 +47,11 @@ static inline double CLLocationDistanceToMiles(CLLocationDistance distance) {
static inline double DEG2RAD(double degrees) { static inline double DEG2RAD(double degrees) {
return degrees * M_PI / 180; return degrees * M_PI / 180;
}; }
static inline double RAD2DEG(double radians) { static inline double RAD2DEG(double radians) {
return radians * 180 / M_PI; return radians * 180 / M_PI;
}; }
static inline CLLocationDegrees CLLocationDegreesBearingBetweenCoordinates(CLLocationCoordinate2D originCoordinate, CLLocationCoordinate2D destinationCoordinate) { static inline CLLocationDegrees CLLocationDegreesBearingBetweenCoordinates(CLLocationCoordinate2D originCoordinate, CLLocationCoordinate2D destinationCoordinate) {
double lat1 = DEG2RAD(originCoordinate.latitude); double lat1 = DEG2RAD(originCoordinate.latitude);
@ -159,13 +159,12 @@ static inline double CLLocationSpeedToMilesPerHour(CLLocationSpeed speed) {
switch (self.unitSystem) { switch (self.unitSystem) {
case TTTMetricSystem: { case TTTMetricSystem: {
double meterDistance = distance;
double kilometerDistance = CLLocationDistanceToKilometers(distance); double kilometerDistance = CLLocationDistanceToKilometers(distance);
if (kilometerDistance > 1) { if (kilometerDistance > 1) {
distanceString = [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:kilometerDistance]]; distanceString = [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:kilometerDistance]];
unitString = NSLocalizedString(@"km", @"Kilometer Unit"); unitString = NSLocalizedString(@"km", @"Kilometer Unit");
} else { } else {
double meterDistance = distance;
distanceString = [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:meterDistance]]; distanceString = [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:meterDistance]];
unitString = NSLocalizedString(@"m", @"Meter Unit"); unitString = NSLocalizedString(@"m", @"Meter Unit");
} }
@ -174,19 +173,20 @@ static inline double CLLocationSpeedToMilesPerHour(CLLocationSpeed speed) {
case TTTImperialSystem: { case TTTImperialSystem: {
double feetDistance = CLLocationDistanceToFeet(distance); double feetDistance = CLLocationDistanceToFeet(distance);
double yardDistance = CLLocationDistanceToYards(distance);
double milesDistance = CLLocationDistanceToMiles(distance);
if (feetDistance < 300) { if (feetDistance < 300) {
distanceString = [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:feetDistance]]; distanceString = [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:feetDistance]];
unitString = NSLocalizedString(@"ft", @"Feet Unit"); unitString = NSLocalizedString(@"ft", @"Feet Unit");
} else if (yardDistance < 500) { } else {
double yardDistance = CLLocationDistanceToYards(distance);
if (yardDistance < 500) {
distanceString = [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:yardDistance]]; distanceString = [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:yardDistance]];
unitString = NSLocalizedString(@"yds", @"Yard Unit"); unitString = NSLocalizedString(@"yds", @"Yard Unit");
} else { } else {
double milesDistance = CLLocationDistanceToMiles(distance);
distanceString = [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:milesDistance]]; distanceString = [self.numberFormatter stringFromNumber:[NSNumber numberWithDouble:milesDistance]];
unitString = (milesDistance > 1.0 && milesDistance < 1.1) ? NSLocalizedString(@"mile", @"Mile Unit (Singular)") : NSLocalizedString(@"miles", @"Mile Unit (Plural)"); unitString = (milesDistance > 1.0 && milesDistance < 1.1) ? NSLocalizedString(@"mile", @"Mile Unit (Singular)") : NSLocalizedString(@"miles", @"Mile Unit (Plural)");
} }
}
break; break;
} }
} }