Skip to content
Browse files

Merge branch 'experimental-0.7'

Conflicts:
	AFNetworking/AFHTTPClient.h
  • Loading branch information...
2 parents 14ca769 + a5387b8 commit ed60b11987fc9aa5c7b1f61c33acda417375570b @mattt mattt committed Oct 12, 2011
Showing with 7,751 additions and 836 deletions.
  1. +4 −4 AFNetworking.podspec
  2. +10 −0 AFNetworking.xcworkspace/contents.xcworkspacedata
  3. +117 −29 AFNetworking/AFHTTPClient.h
  4. +137 −54 AFNetworking/AFHTTPClient.m
  5. +26 −102 AFNetworking/AFHTTPRequestOperation.h
  6. +43 −348 AFNetworking/AFHTTPRequestOperation.m
  7. +16 −1 AFNetworking/AFImageCache.h
  8. +22 −1 AFNetworking/AFImageCache.m
  9. +58 −13 AFNetworking/AFImageRequestOperation.h
  10. +176 −27 AFNetworking/AFImageRequestOperation.m
  11. +25 −64 AFNetworking/AFJSONRequestOperation.h
  12. +90 −73 AFNetworking/AFJSONRequestOperation.m
  13. +7 −0 AFNetworking/AFNetworkActivityIndicatorManager.h
  14. +32 −4 AFNetworking/AFNetworkActivityIndicatorManager.m
  15. +40 −0 AFNetworking/AFNetworking.h
  16. +74 −0 AFNetworking/AFPropertyListRequestOperation.h
  17. +143 −0 AFNetworking/AFPropertyListRequestOperation.m
  18. +188 −0 AFNetworking/AFURLConnectionOperation.h
  19. +384 −0 AFNetworking/AFURLConnectionOperation.m
  20. +92 −0 AFNetworking/AFXMLRequestOperation.h
  21. +199 −0 AFNetworking/AFXMLRequestOperation.m
  22. +10 −4 AFNetworking/UIImageView+AFNetworking.h
  23. +23 −23 AFNetworking/UIImageView+AFNetworking.m
  24. +0 −5 ...ple/AFNetworking Example.xcodeproj/xcuserdata/mattt.xcuserdatad/xcdebugger/Breakpoints.xcbkptlist
  25. +418 −0 Mac Example/AFNetworking Mac Example.xcodeproj/project.pbxproj
  26. +75 −0 ...ng Mac Example.xcodeproj/xcuserdata/mattt.xcuserdatad/xcschemes/AFNetworking Mac Example.xcscheme
  27. +22 −0 ...FNetworking Mac Example.xcodeproj/xcuserdata/mattt.xcuserdatad/xcschemes/xcschememanagement.plist
  28. +32 −0 Mac Example/AppDelegate.h
  29. +33 −0 Mac Example/AppDelegate.m
  30. 0 {Example → Mac Example}/Classes/AFGowallaAPIClient.h
  31. +64 −0 Mac Example/Classes/AFGowallaAPIClient.m
  32. +30 −0 Mac Example/Classes/Controllers/NearbySpotsController.h
  33. +124 −0 Mac Example/Classes/Controllers/NearbySpotsController.m
  34. +43 −0 Mac Example/Classes/Models/Spot.h
  35. +76 −0 Mac Example/Classes/Models/Spot.m
  36. 0 {Example → Mac Example}/Images/placeholder-stamp.png
  37. +36 −0 Mac Example/Info.plist
  38. +7 −0 Mac Example/Prefix.pch
  39. 0 {Example → Mac Example}/Vendor/JSONKit/JSONKit.h
  40. 0 {Example → Mac Example}/Vendor/JSONKit/JSONKit.m
  41. 0 {Example → Mac Example}/Vendor/TTT/TTTLocationFormatter.h
  42. 0 {Example → Mac Example}/Vendor/TTT/TTTLocationFormatter.m
  43. +13 −0 Mac Example/en.lproj/Credits.rtf
  44. +1,042 −0 Mac Example/en.lproj/MainMenu.xib
  45. +29 −0 Mac Example/main.m
  46. +41 −38 README.md
  47. +32 −20 .../AFNetworking Example.xcodeproj → iOS Example/AFNetworking iOS Example.xcodeproj}/project.pbxproj
  48. +10 −19 ...ng iOS Example.xcodeproj/xcuserdata/mattt.xcuserdatad/xcschemes/AFNetworking iOS Example.xcscheme
  49. +2 −2 ...Networking iOS Example.xcodeproj}/xcuserdata/mattt.xcuserdatad/xcschemes/xcschememanagement.plist
  50. 0 {Example → iOS Example}/AppDelegate.h
  51. 0 {Example → iOS Example}/AppDelegate.m
  52. +31 −0 iOS Example/Classes/AFGowallaAPIClient.h
  53. +8 −1 {Example → iOS Example}/Classes/AFGowallaAPIClient.m
  54. 0 {Example → iOS Example}/Classes/Controllers/NearbySpotsViewController.h
  55. 0 {Example → iOS Example}/Classes/Controllers/NearbySpotsViewController.m
  56. +1 −3 {Example → iOS Example}/Classes/Models/Spot.h
  57. +1 −1 {Example → iOS Example}/Classes/Models/Spot.m
  58. 0 {Example → iOS Example}/Classes/Views/SpotTableViewCell.h
  59. +21 −0 {Example → iOS Example}/Classes/Views/SpotTableViewCell.m
  60. BIN iOS Example/Images/placeholder-stamp.png
  61. 0 {Example → iOS Example}/Images/placeholder-stamp@2x.png
  62. 0 {Example → iOS Example}/Info.plist
  63. 0 {Example → iOS Example}/Prefix.pch
  64. +251 −0 iOS Example/Vendor/JSONKit/JSONKit.h
  65. +3,011 −0 iOS Example/Vendor/JSONKit/JSONKit.m
  66. +83 −0 iOS Example/Vendor/TTT/TTTLocationFormatter.h
  67. +299 −0 iOS Example/Vendor/TTT/TTTLocationFormatter.m
  68. 0 {Example → iOS Example}/main.m
View
8 AFNetworking.podspec
@@ -1,13 +1,13 @@
Pod::Spec.new do
name 'AFNetworking'
- version '0.6.1'
- summary 'A delightful iOS networking library with NSOperations and block-based callbacks'
+ version '0.7.0'
+ summary 'A delightful iOS and OS X networking framework'
homepage 'https://github.com/gowalla/AFNetworking'
authors 'Mattt Thompson' => 'm@mattt.me', 'Scott Raymond' => 'sco@gowalla.com'
source :git => 'https://github.com/gowalla/AFNetworking.git',
- :tag => '0.6.1'
+ :tag => '0.7.0'
- platforms 'iOS'
+ platforms 'iOS', 'OSX'
sdk '>= 4.0'
source_files 'AFNetworking'
View
10 AFNetworking.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+ version = "1.0">
+ <FileRef
+ location = "group:iOS Example/AFNetworking iOS Example.xcodeproj">
+ </FileRef>
+ <FileRef
+ location = "group:Mac Example/AFNetworking Mac Example.xcodeproj">
+ </FileRef>
+</Workspace>
View
146 AFNetworking/AFHTTPClient.h
@@ -21,34 +21,53 @@
// THE SOFTWARE.
#import <Foundation/Foundation.h>
-#import "AFHTTPRequestOperation.h"
+@class AFHTTPRequestOperation;
+@protocol AFHTTPClientOperation;
@protocol AFMultipartFormData;
/**
- `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.
+ Method used to encode parameters into request body
+ */
+typedef enum {
+ AFFormURLParameterEncoding,
+ AFJSONParameterEncoding,
+ AFPropertyListParameterEncoding,
+} AFHTTPClientParameterEncoding;
+
+/**
+ `AFHTTPClient` captures the common patterns of communicating with an web application over HTTP. It encapsulates information like base URL, authorization credentials, and HTTP headers, and uses them to construct and manage the execution of HTTP request operations.
- In its default implementation, `AFHTTPClient` sets the following HTTP headers:
+ ## Automatic Content Parsing
- - `Accept: application/json`
- - `Accept-Encoding: gzip`
- - `Accept-Language: #{[NSLocale preferredLanguages]}, en-us;q=0.8`
- - `User-Agent: #{generated user agent}`
-
- You can override these HTTP headers or define new ones using `setDefaultHeader:value:`.
+ Instances of `AFHTTPClient` may specify which types of requests it expects and should handle by registering HTTP operation classes for automatic parsing. Registered classes will determine whether they can handle a particular request, and then construct a request operation accordingly in `enqueueHTTPRequestOperationWithRequest:success:failure`. See `AFHTTPClientOperation` for further details.
- # Subclassing Notes
+ ## Subclassing Notes
- It is strongly recommended that you create an `AFHTTPClient` subclass for each website or web application that your application communicates with, and in each subclass, defining a method that returns a singleton object that acts as single a shared HTTP client that holds authentication credentials and other configuration for the entire application.
+ In most cases, one should create an `AFHTTPClient` subclass for each website or web application that your application communicates with. It is often useful, also, to define a class method that returns a singleton shared HTTP client in each subclass, that persists authentication credentials and other configuration across the entire application.
## Methods to Override
- If an `AFHTTPClient` wishes to change the way request parameters are encoded, then the base implementation of `requestWithMethod:path:parameters:` should be overridden. Otherwise, it should be sufficient to take the `super` implementation, and configure the resulting `NSMutableURLRequest` object accordingly.
+ To change the behavior of all url request construction for an `AFHTTPClient` subclass, override `requestWithMethod:path:parameters`.
+
+ To change the behavior of all request operation construction for an `AFHTTPClient` subclass, override `enqueueHTTPRequestOperationWithRequest:success:failure`.
+
+ ## Default Headers
+
+ By default, `AFHTTPClient` sets the following HTTP headers:
+
+ - `Accept-Encoding: gzip`
+ - `Accept-Language: #{[NSLocale preferredLanguages]}, en-us;q=0.8`
+ - `User-Agent: #{generated user agent}`
+
+ You can override these HTTP headers or define new ones using `setDefaultHeader:value:`.
*/
@interface AFHTTPClient : NSObject {
@private
NSURL *_baseURL;
NSStringEncoding _stringEncoding;
+ AFHTTPClientParameterEncoding _parameterEncoding;
+ NSMutableArray *_registeredHTTPOperationClassNames;
NSMutableDictionary *_defaultHeaders;
NSOperationQueue *_operationQueue;
}
@@ -68,6 +87,11 @@
@property (nonatomic, assign) NSStringEncoding stringEncoding;
/**
+ The `AFHTTPClientParameterEncoding` value corresponding to how parameters are encoded into a request body. This is `AFFormURLParameterEncoding` by default.
+ */
+@property (nonatomic, assign) AFHTTPClientParameterEncoding parameterEncoding;
+
+/**
The operation queue which manages operations enqueued by the HTTP client.
*/
@property (readonly, nonatomic, retain) NSOperationQueue *operationQueue;
@@ -90,13 +114,39 @@
@param url The base URL for the HTTP client. This argument must not be nil.
- @discussion This is the designated initializer for `AFHTTPClient`
+ @discussion This is the designated initializer.
@return The newly-initialized HTTP client
*/
- (id)initWithBaseURL:(NSURL *)url;
///----------------------------------
+/// @name Managing HTTP Operations
+///----------------------------------
+
+/**
+ Attempts to register a class conforming to the `AFHTTPClientOperation` protocol, adding it to a chain to automatically generate request operations from a URL request.
+
+ @param The class conforming to the `AFHTTPClientOperation` protocol to register
+
+ @return `YES` if the registration is successful, `NO` otherwise. The only failure condition is if `operationClass` does not conform to the `AFHTTPCLientOperation` protocol.
+
+ @discussion When `enqueueHTTPRequestOperationWithRequest:success:failure` is invoked, each registered class is consulted in turn to see if it can handle the specific request. The first class to return `YES` when sent a `canProcessRequest:` message is used to generate an operation using `HTTPRequestOperationWithRequest:success:failure:`. There is no guarantee that all registered classes will be consulted. Classes are consulted in the reverse order of their registration. Attempting to register an already-registered class will move it to the top of the chain.
+
+ @see `AFHTTPClientOperation`
+ */
+- (BOOL)registerHTTPOperationClass:(Class)operationClass;
+
+/**
+ Unregisteres the specified class conforming to the `AFHTTPClientOperation` protocol.
+
+ @param The class conforming to the `AFHTTPClientOperation` protocol to unregister
+
+ @discussion After this method is invoked, `operationClass` is no longer consulted when `requestWithMethod:path:parameters` is invoked.
+ */
+- (void)unregisterHTTPOperationClass:(Class)operationClass;
+
+///----------------------------------
/// @name Managing HTTP Header Values
///----------------------------------
@@ -142,13 +192,17 @@
///-------------------------------
/**
- 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.
+ Creates an `NSMutableURLRequest` object with the specified HTTP method and path. By default, this method scans through the registered operation classes (in reverse order of when they were specified), until finding one that can handle the specified request.
+
+ 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. Otherwise, the parameters will be encoded according to the value of the `parameterEncoding` property, and set as the request 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
+
+ @see AFHTTPClientOperation
*/
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
path:(NSString *)path
@@ -164,6 +218,8 @@
@see AFMultipartFormData
+ @warning An exception will be raised if the specified method is not `POST`, `PUT` or `DELETE`.
+
@return An `NSMutableURLRequest` object
*/
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
@@ -179,13 +235,23 @@
/**
Creates and enqueues an `AFHTTPRequestOperation` to the HTTP client's operation queue.
+ In order to determine what kind of operation is enqueued, each registered subclass conforming to the `AFHTTPClient` protocol is consulted in turn to see if it can handle the specific request. The first class to return `YES` when sent a `canProcessRequest:` message is used to generate an operation using `HTTPRequestOperationWithRequest:success:failure:`.
+
@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 an 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.
+ @param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes a single argument, which is an 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. This block has no return value and takes a single argument, which is the `NSError` object describing the network or parsing error that occurred.
+
+ @see `AFHTTPClientOperation`
*/
-- (void)enqueueHTTPOperationWithRequest:(NSURLRequest *)request
- success:(void (^)(id object))success
- failure:(void (^)(NSHTTPURLResponse *response, NSError *error))failure;
+- (void)enqueueHTTPRequestOperationWithRequest:(NSURLRequest *)request
+ success:(void (^)(id object))success
+ failure:(void (^)(NSHTTPURLResponse *response, NSError *error))failure;
+/**
+ Enqueues an `AFHTTPRequestOperation` to the HTTP client's operation queue.
+
+ @param operation The HTTP request operation to be enqueued.
+ */
+- (void)enqueueHTTPRequestOperation:(AFHTTPRequestOperation *)operation;
///---------------------------------
/// @name Cancelling HTTP Operations
@@ -267,7 +333,38 @@
#pragma mark -
/**
+ The `AFHTTPClientOperation` protocol defines the methods used for the automatic content parsing functionality of `AFHTTPClient`.
+
+ @see `AFHTTPClient -registerHTTPOperationClass:`
+ */
+@protocol AFHTTPClientOperation
+
+/**
+ A Boolean value determining whether or not the class can process the specified request. For example, `AFJSONRequestOperation` may check to make sure the content type was `application/json` or the URL path extension was `.json`.
+
+ @param urlRequest The request that is determined to be supported or not supported for this class.
+ */
++ (BOOL)canProcessRequest:(NSURLRequest *)urlRequest;
+
+/**
+ Constructs and initializes an operation with success and failure callbacks.
+
+ @param urlRequest The request used by the operation connection.
+ @param success A block object to be executed when the operation finishes successfully. The block has no return value and takes a single argument, the response object from the request.
+ @param failure A block object to be executed when the operation finishes unsuccessfully. The block has no return value and takes two arguments: the response received from the server, and the error describing the network or parsing error that occurred.
+ */
++ (id)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest
+ success:(void (^)(id object))success
+ failure:(void (^)(NSHTTPURLResponse *response, NSError *error))failure;
+@end
+
+#pragma mark -
+
+/**
The `AFMultipartFormData` protocol defines the methods supported by the parameter in the block argument of `multipartFormRequestWithMethod:path:parameters:constructingBodyWithBlock:`.
+
+ @see `AFHTTPClient -multipartFormRequestWithMethod:path:parameters:constructingBodyWithBlock:`
+
*/
@protocol AFMultipartFormData
@@ -299,16 +396,6 @@
- (void)appendPartWithFileData:(NSData *)data mimeType:(NSString *)mimeType name:(NSString *)name;
/**
- Appends the HTTP header `Content-Disposition: file; filename=#{filename}"` and `Content-Type: #{mimeType}`, 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. This parameter must not be `nil`.
- @param mimeType The MIME type of the specified data. (For example, the MIME type for a JPEG image is image/jpeg.) For a list of valid MIME types, see http://www.iana.org/assignments/media-types/. This parameter must not be `nil`.
- @param fileName The filename to be associated with the file contents. This parameter must not be `nil`.
- @param error If an error occurs, upon return contains an `NSError` object that describes the problem.
- */
-- (void)appendPartWithFile:(NSURL *)fileURL mimeType:(NSString *)mimeType fileName:(NSString *)fileName error:(NSError **)error;
-
-/**
Appends encoded data to the form data.
@param data The data to be encoded and appended to the form data.
@@ -322,3 +409,4 @@
*/
- (void)appendString:(NSString *)string;
@end
+
View
191 AFNetworking/AFHTTPClient.m
@@ -20,8 +20,18 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
+#import <Foundation/Foundation.h>
+
#import "AFHTTPClient.h"
-#import "AFJSONRequestOperation.h"
+#import "AFHTTPRequestOperation.h"
+
+#import <Availability.h>
+
+#if __IPHONE_OS_VERSION_MIN_REQUIRED
+#import <UIKit/UIKit.h>
+#endif
+
+#import "JSONKit.h"
static NSString * const kAFMultipartFormLineDelimiter = @"\r\n"; // CRLF
static NSString * const kAFMultipartFormBoundary = @"Boundary+0xAbCdEfGbOuNdArY";
@@ -40,6 +50,8 @@ - (id)initWithStringEncoding:(NSStringEncoding)encoding;
#pragma mark -
+static NSUInteger const kAFHTTPClientDefaultMaxConcurrentOperationCount = 4;
+
static NSString * AFBase64EncodedStringFromString(NSString *string) {
NSData *data = [NSData dataWithBytes:[string UTF8String] length:[string length]];
NSUInteger length = [data length];
@@ -69,21 +81,66 @@ - (id)initWithStringEncoding:(NSStringEncoding)encoding;
return [[[NSString alloc] initWithData:mutableData encoding:NSASCIIStringEncoding] autorelease];
}
-static NSString * AFURLEncodedStringFromStringWithEncoding(NSString *string, NSStringEncoding encoding) {
+static NSString * AFURLEncodedStringFromString(NSString *string) {
static NSString * const kAFLegalCharactersToBeEscaped = @"?!@#$^&%*+,:;='\"`<>()[]{}/\\|~ ";
- return [(NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, (CFStringRef)kAFLegalCharactersToBeEscaped, CFStringConvertNSStringEncodingToEncoding(encoding)) autorelease];
+ return [(NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)string, NULL, (CFStringRef)kAFLegalCharactersToBeEscaped, CFStringConvertNSStringEncodingToEncoding(NSUTF8StringEncoding)) autorelease];
+}
+
+static NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
+ NSMutableArray *mutableParameterComponents = [NSMutableArray array];
+ for (id key in [parameters allKeys]) {
+ NSString *component = [NSString stringWithFormat:@"%@=%@", AFURLEncodedStringFromString([key description]), AFURLEncodedStringFromString([[parameters valueForKey:key] description])];
+ [mutableParameterComponents addObject:component];
+ }
+
+ return [mutableParameterComponents componentsJoinedByString:@"&"];
+}
+
+static NSString * AFJSONStringFromParameters(NSDictionary *parameters) {
+ NSString *JSONString = nil;
+
+#if __IPHONE_OS_VERSION_MIN_REQUIRED > __IPHONE_4_3 || __MAC_OS_X_VERSION_MIN_REQUIRED > __MAC_10_6
+ if ([NSJSONSerialization class]) {
+ NSError *error = nil;
+ NSData *JSONData = [NSJSONSerialization dataWithJSONObject:parameters options:0 error:&error];
+ if (!error) {
+ JSONString = [[[NSString alloc] initWithData:JSONData encoding:NSUTF8StringEncoding] autorelease];
+ }
+ } else {
+ JSONString = [parameters JSONString];
+ }
+#else
+ JSONString = [parameters JSONString];
+#endif
+
+ return JSONString;
+}
+
+static NSString * AFPropertyListStringFromParameters(NSDictionary *parameters) {
+ NSString *propertyListString = nil;
+ NSError *error = nil;
+
+ NSData *propertyListData = [NSPropertyListSerialization dataWithPropertyList:parameters format:NSPropertyListXMLFormat_v1_0 options:0 error:&error];
+ if (!error) {
+ propertyListString = [[[NSString alloc] initWithData:propertyListData encoding:NSUTF8StringEncoding] autorelease];
+ }
+
+ return propertyListString;
}
@interface AFHTTPClient ()
@property (readwrite, nonatomic, retain) NSURL *baseURL;
+@property (readwrite, nonatomic, retain) NSMutableArray *registeredHTTPOperationClassNames;
@property (readwrite, nonatomic, retain) NSMutableDictionary *defaultHeaders;
@property (readwrite, nonatomic, retain) NSOperationQueue *operationQueue;
@end
@implementation AFHTTPClient
@synthesize baseURL = _baseURL;
@synthesize stringEncoding = _stringEncoding;
+@synthesize parameterEncoding = _parameterEncoding;
+@synthesize registeredHTTPOperationClassNames = _registeredHTTPOperationClassNames;
@synthesize defaultHeaders = _defaultHeaders;
@synthesize operationQueue = _operationQueue;
@@ -100,35 +157,61 @@ - (id)initWithBaseURL:(NSURL *)url {
self.baseURL = url;
self.stringEncoding = NSUTF8StringEncoding;
+ self.parameterEncoding = AFJSONParameterEncoding;
- self.defaultHeaders = [NSMutableDictionary dictionary];
+ self.registeredHTTPOperationClassNames = [NSMutableArray array];
- // Accept HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
- [self setDefaultHeader:@"Accept" value:@"application/json"];
+ self.defaultHeaders = [NSMutableDictionary dictionary];
// 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)]];
+
+#if __IPHONE_OS_VERSION_MIN_REQUIRED
+ // 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)]];
+#elif __MAC_OS_X_VERSION_MIN_REQUIRED
+ [self setDefaultHeader:@"User-Agent" value:[NSString stringWithFormat:@"%@/%@ (%@)", [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleIdentifierKey], [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleVersionKey], @"unknown"]];
+#endif
self.operationQueue = [[[NSOperationQueue alloc] init] autorelease];
- [self.operationQueue setMaxConcurrentOperationCount:2];
+ [self.operationQueue setMaxConcurrentOperationCount:kAFHTTPClientDefaultMaxConcurrentOperationCount];
return self;
}
- (void)dealloc {
[_baseURL release];
+ [_registeredHTTPOperationClassNames release];
[_defaultHeaders release];
[_operationQueue release];
[super dealloc];
}
+#pragma mark -
+
+- (BOOL)registerHTTPOperationClass:(Class)operationClass {
+ if (![operationClass conformsToProtocol:@protocol(AFHTTPClientOperation)]) {
+ return NO;
+ }
+
+ NSString *className = NSStringFromClass(operationClass);
+ [self.registeredHTTPOperationClassNames removeObject:className];
+ [self.registeredHTTPOperationClassNames insertObject:className atIndex:0];
+
+ return YES;
+}
+
+- (void)unregisterHTTPOperationClass:(Class)operationClass {
+ NSString *className = NSStringFromClass(operationClass);
+ [self.registeredHTTPOperationClassNames removeObject:className];
+}
+
+#pragma mark -
+
- (NSString *)defaultValueForHeader:(NSString *)header {
return [self.defaultHeaders valueForKey:header];
}
@@ -156,31 +239,34 @@ - (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];
+ NSURL *url = [self.baseURL URLByAppendingPathComponent:path];
+ NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] initWithURL:url] autorelease];
+ [request setHTTPMethod:method];
+ [request setAllHTTPHeaderFields:self.defaultHeaders];
- 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 (parameters) {
if ([method isEqualToString:@"GET"]) {
- url = [NSURL URLWithString:[[url absoluteString] stringByAppendingFormat:[path rangeOfString:@"?"].location == NSNotFound ? @"?%@" : @"&%@", queryString]];
+ url = [NSURL URLWithString:[[url absoluteString] stringByAppendingFormat:[path rangeOfString:@"?"].location == NSNotFound ? @"?%@" : @"&%@", AFQueryStringFromParameters(parameters)]];
+ [request setURL:url];
} else {
NSString *charset = (NSString *)CFStringConvertEncodingToIANACharSetName(CFStringConvertNSStringEncodingToEncoding(self.stringEncoding));
- [headers setValue:[NSString stringWithFormat:@"application/x-www-form-urlencoded; charset=%@", charset] forKey:@"Content-Type"];
- [request setHTTPBody:[queryString dataUsingEncoding:self.stringEncoding]];
+ switch (self.parameterEncoding) {
+ case AFFormURLParameterEncoding:;
+ [request setValue:[NSString stringWithFormat:@"application/x-www-form-urlencoded; charset=%@", charset] forHTTPHeaderField:@"Content-Type"];
+ [request setHTTPBody:[AFQueryStringFromParameters(parameters) dataUsingEncoding:self.stringEncoding]];
+ break;
+ case AFJSONParameterEncoding:;
+ [request setValue:[NSString stringWithFormat:@"application/json; charset=%@", charset] forHTTPHeaderField:@"Content-Type"];
+ [request setHTTPBody:[AFJSONStringFromParameters(parameters) dataUsingEncoding:self.stringEncoding]];
+ break;
+ case AFPropertyListParameterEncoding:;
+ [request setValue:[NSString stringWithFormat:@"application/x-plist; charset=%@", charset] forHTTPHeaderField:@"Content-Type"];
+ [request setHTTPBody:[AFPropertyListStringFromParameters(parameters) dataUsingEncoding:self.stringEncoding]];
+ break;
+ }
}
}
- [request setURL:url];
- [request setHTTPMethod:method];
- [request setAllHTTPHeaderFields:headers];
-
return request;
}
@@ -224,20 +310,28 @@ - (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
return request;
}
-- (void)enqueueHTTPOperationWithRequest:(NSURLRequest *)urlRequest
- success:(void (^)(id object))success
- failure:(void (^)(NSHTTPURLResponse *response, NSError *error))failure
+- (void)enqueueHTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest
+ success:(void (^)(id object))success
+ failure:(void (^)(NSHTTPURLResponse *response, NSError *error))failure
{
- AFJSONRequestOperation *operation = [AFJSONRequestOperation operationWithRequest:urlRequest success:^(id JSON) {
- if (success) {
- success(JSON);
+ AFHTTPRequestOperation *operation = nil;
+ NSString *className = nil;
+ NSEnumerator *enumerator = [self.registeredHTTPOperationClassNames reverseObjectEnumerator];
+ while (!operation && (className = [enumerator nextObject])) {
+ Class class = NSClassFromString(className);
+ if (class && [class canProcessRequest:urlRequest]) {
+ operation = [class HTTPRequestOperationWithRequest:urlRequest success:success failure:failure];
}
- } failure:^(NSHTTPURLResponse *response, NSError *error) {
- if (failure) {
- failure(response, error);
- }
- }];
+ }
+ if (!operation) {
+ operation = [AFHTTPRequestOperation HTTPRequestOperationWithRequest:urlRequest success:success failure:failure];
+ }
+
+ [self enqueueHTTPRequestOperation:operation];
+}
+
+- (void)enqueueHTTPRequestOperation:(AFHTTPRequestOperation *)operation {
[self.operationQueue addOperation:operation];
}
@@ -257,7 +351,7 @@ - (void)getPath:(NSString *)path
failure:(void (^)(NSHTTPURLResponse *response, NSError *error))failure
{
NSURLRequest *request = [self requestWithMethod:@"GET" path:path parameters:parameters];
- [self enqueueHTTPOperationWithRequest:request success:success failure:failure];
+ [self enqueueHTTPRequestOperationWithRequest:request success:success failure:failure];
}
- (void)postPath:(NSString *)path
@@ -266,7 +360,7 @@ - (void)postPath:(NSString *)path
failure:(void (^)(NSHTTPURLResponse *response, NSError *error))failure
{
NSURLRequest *request = [self requestWithMethod:@"POST" path:path parameters:parameters];
- [self enqueueHTTPOperationWithRequest:request success:success failure:failure];
+ [self enqueueHTTPRequestOperationWithRequest:request success:success failure:failure];
}
- (void)putPath:(NSString *)path
@@ -275,7 +369,7 @@ - (void)putPath:(NSString *)path
failure:(void (^)(NSHTTPURLResponse *response, NSError *error))failure
{
NSURLRequest *request = [self requestWithMethod:@"PUT" path:path parameters:parameters];
- [self enqueueHTTPOperationWithRequest:request success:success failure:failure];
+ [self enqueueHTTPRequestOperationWithRequest:request success:success failure:failure];
}
- (void)deletePath:(NSString *)path
@@ -284,7 +378,7 @@ - (void)deletePath:(NSString *)path
failure:(void (^)(NSHTTPURLResponse *response, NSError *error))failure
{
NSURLRequest *request = [self requestWithMethod:@"DELETE" path:path parameters:parameters];
- [self enqueueHTTPOperationWithRequest:request success:success failure:failure];
+ [self enqueueHTTPRequestOperationWithRequest:request success:success failure:failure];
}
@end
@@ -361,17 +455,6 @@ - (void)appendPartWithFileData:(NSData *)data mimeType:(NSString *)mimeType name
[self appendPartWithHeaders:mutableHeaders body:data];
}
-- (void)appendPartWithFile:(NSURL *)fileURL mimeType:(NSString *)mimeType fileName:(NSString *)fileName error:(NSError **)error {
- NSData *data = [NSData dataWithContentsOfFile:[fileURL absoluteString] options:0 error:error];
- if (data) {
- NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
- [mutableHeaders setValue:[NSString stringWithFormat:@"file; filename=\"%@\"", fileName] forKey:@"Content-Disposition"];
- [mutableHeaders setValue:mimeType forKey:@"Content-Type"];
-
- [self appendPartWithHeaders:mutableHeaders body:data];
- }
-}
-
- (void)appendData:(NSData *)data {
[self.mutableData appendData:data];
}
View
128 AFNetworking/AFHTTPRequestOperation.h
@@ -21,131 +21,55 @@
// THE SOFTWARE.
#import <Foundation/Foundation.h>
+#import "AFURLConnectionOperation.h"
+#import "AFHTTPClient.h"
/**
- Indicates an error occured in AFNetworking.
-
- @discussion Error codes for AFNetworkingErrorDomain correspond to codes in NSURLErrorDomain.
+ `AFHTTPRequestOperation` is a subclass of `AFURLConnectionOperation` for requests using the HTTP or HTTPS protocols. It encapsulates the concept of acceptable status codes and content types, which determine the success or failure of a request.
*/
-extern NSString * const AFNetworkingErrorDomain;
+@interface AFHTTPRequestOperation : AFURLConnectionOperation <AFHTTPClientOperation> {
+@private
+ NSIndexSet *_acceptableStatusCodes;
+ NSSet *_acceptableContentTypes;
+ NSError *_HTTPError;
+}
-/**
- Posted when an operation begins executing.
- */
-extern NSString * const AFHTTPOperationDidStartNotification;
+///----------------------------------------------
+/// @name Getting HTTP URL Connection Information
+///----------------------------------------------
/**
- Posted when an operation finishes.
+ The last HTTP response received by the operation's connection.
*/
-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`.
-
- ### `NSURLConnection` Delegate Methods
-
- Notably, `AFHTTPRequestOperation` does not implement any of the authentication challenge-related `NSURLConnection` delegate methods.
-
- `AFHTTPRequestOperation` does implement the following `NSURLConnection` delegate methods:
-
- - `connection:didReceiveResponse:`
- - `connection:didReceiveData:`
- - `connectionDidFinishLoading:`
- - `connection:didFailWithError:`
- - `connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:`
- - `connection:willCacheResponse:`
-
- If you overwrite any of the above methods, be sure to make the call to `super` first, or else it may cause unexpected results.
-
- @see NSOperation
- @see NSURLConnection
- */
-@interface AFHTTPRequestOperation : NSOperation {
-@private
- NSSet *_runLoopModes;
-
- NSURLConnection *_connection;
- NSURLRequest *_request;
- NSHTTPURLResponse *_response;
- NSError *_error;
-
- NSData *_responseBody;
- NSInteger _totalBytesRead;
- NSMutableData *_dataAccumulator;
- NSOutputStream *_outputStream;
-}
-
-@property (nonatomic, retain) NSSet *runLoopModes;
-
-@property (readonly, nonatomic, retain) NSURLRequest *request;
@property (readonly, nonatomic, retain) NSHTTPURLResponse *response;
-@property (readonly, nonatomic, retain) NSError *error;
-@property (readonly, nonatomic, retain) NSData *responseBody;
-@property (readonly) NSString *responseString;
-///---------------------------------------
-/// @name Creating HTTP Request Operations
-///---------------------------------------
+///----------------------------------------------------------
+/// @name Managing And Checking For Acceptable HTTP Responses
+///----------------------------------------------------------
/**
- 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.
+ Returns an `NSIndexSet` object containing the ranges of acceptable HTTP status codes. When non-`nil`, the operation will set the `error` property to an error in `AFErrorDomain`. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
- @return A new HTTP request operation
+ By default, this is the range 200 to 299, inclusive.
*/
-+ (AFHTTPRequestOperation *)operationWithRequest:(NSURLRequest *)urlRequest
- completion:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSData *data, NSError *error))completion;
+@property (nonatomic, retain) NSIndexSet *acceptableStatusCodes;
/**
- Creates and returns a streaming `AFHTTPRequestOperation` object and sets the specified input stream, output stream, 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`. If the request method is `GET`, it 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 data 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 streaming HTTP request operation
+ A Boolean value that corresponds to whether the status code of the response is within the specified set of acceptable status codes. Returns `YES` if `acceptableStatusCodes` is `nil`.
*/
-+ (AFHTTPRequestOperation *)streamingOperationWithRequest:(NSURLRequest *)urlRequest
- inputStream:(NSInputStream *)inputStream
- outputStream:(NSOutputStream *)outputStream
- completion:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))completion;
-
-///---------------------------------
-/// @name Setting Progress Callbacks
-///---------------------------------
+@property (readonly) BOOL hasAcceptableStatusCode;
/**
- Sets a callback to be called when an undetermined number of bytes have been downloaded from the server.
+ Returns an `NSSet` object containing the acceptable MIME types. When non-`nil`, the operation will set the `error` property to an error in `AFErrorDomain`. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17
- @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 three arguments: the number of bytes written since the last time the upload progress block was called, 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
+ By default, this is `nil`.
*/
-- (void)setUploadProgressBlock:(void (^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))block;
+@property (nonatomic, retain) NSSet *acceptableContentTypes;
/**
- 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 three arguments: the number of bytes read since the last time the upload progress block was called, 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
+ A Boolean value that corresponds to whether the MIME type of the response is among the specified set of acceptable content types. Returns `YES` if `acceptableContentTypes` is `nil`.
*/
-- (void)setDownloadProgressBlock:(void (^)(NSInteger bytesRead, NSInteger totalBytesRead, NSInteger totalBytesExpectedToRead))block;
+@property (readonly) BOOL hasAcceptableContentType;
@end
View
391 AFNetworking/AFHTTPRequestOperation.m
@@ -22,381 +22,76 @@
#import "AFHTTPRequestOperation.h"
-static NSUInteger const kAFHTTPMinimumInitialDataCapacity = 1024;
-static NSUInteger const kAFHTTPMaximumInitialDataCapacity = 1024 * 1024 * 8;
-
-typedef enum {
- AFHTTPOperationReadyState = 1,
- AFHTTPOperationExecutingState = 2,
- AFHTTPOperationFinishedState = 3,
- AFHTTPOperationCancelledState = 4,
-} AFHTTPOperationState;
-
-NSString * const AFNetworkingErrorDomain = @"com.alamofire.networking.error";
-
-NSString * const AFHTTPOperationDidStartNotification = @"com.alamofire.networking.http-operation.start";
-NSString * const AFHTTPOperationDidFinishNotification = @"com.alamofire.networking.http-operation.finish";
-
-typedef void (^AFHTTPRequestOperationProgressBlock)(NSInteger bytes, NSInteger totalBytes, NSInteger totalBytesExpected);
-typedef void (^AFHTTPRequestOperationCompletionBlock)(NSURLRequest *request, NSHTTPURLResponse *response, NSData *data, NSError *error);
-
-static inline NSString * AFKeyPathFromOperationState(AFHTTPOperationState state) {
- switch (state) {
- case AFHTTPOperationReadyState:
- return @"isReady";
- case AFHTTPOperationExecutingState:
- return @"isExecuting";
- case AFHTTPOperationFinishedState:
- return @"isFinished";
- default:
- return @"state";
- }
-}
-
-static inline BOOL AFHTTPOperationStateTransitionIsValid(AFHTTPOperationState from, AFHTTPOperationState to) {
- switch (from) {
- case AFHTTPOperationReadyState:
- switch (to) {
- case AFHTTPOperationExecutingState:
- return YES;
- default:
- return NO;
- }
- case AFHTTPOperationExecutingState:
- switch (to) {
- case AFHTTPOperationReadyState:
- return NO;
- default:
- return YES;
- }
- case AFHTTPOperationFinishedState:
- return NO;
- default:
- return YES;
- }
-}
-
@interface AFHTTPRequestOperation ()
-@property (readwrite, nonatomic, assign) AFHTTPOperationState state;
-@property (readwrite, nonatomic, assign, getter = isCancelled) BOOL cancelled;
-@property (readwrite, nonatomic, retain) NSURLConnection *connection;
-@property (readwrite, nonatomic, retain) NSURLRequest *request;
-@property (readwrite, nonatomic, retain) NSHTTPURLResponse *response;
@property (readwrite, nonatomic, retain) NSError *error;
-@property (readwrite, nonatomic, retain) NSData *responseBody;
-@property (readwrite, nonatomic, assign) NSInteger totalBytesRead;
-@property (readwrite, nonatomic, retain) NSMutableData *dataAccumulator;
-@property (readwrite, nonatomic, retain) NSOutputStream *outputStream;
-@property (readwrite, nonatomic, copy) AFHTTPRequestOperationProgressBlock uploadProgress;
-@property (readwrite, nonatomic, copy) AFHTTPRequestOperationProgressBlock downloadProgress;
-@property (readwrite, nonatomic, copy) AFHTTPRequestOperationCompletionBlock completion;
-
-- (void)operationDidStart;
-- (void)finish;
@end
@implementation AFHTTPRequestOperation
-@synthesize state = _state;
-@synthesize cancelled = _cancelled;
-@synthesize connection = _connection;
-@synthesize runLoopModes = _runLoopModes;
-@synthesize request = _request;
-@synthesize response = _response;
-@synthesize error = _error;
-@synthesize responseBody = _responseBody;
-@synthesize totalBytesRead = _totalBytesRead;
-@synthesize dataAccumulator = _dataAccumulator;
-@synthesize outputStream = _outputStream;
-@synthesize uploadProgress = _uploadProgress;
-@synthesize downloadProgress = _downloadProgress;
-@synthesize completion = _completion;
-
-static NSThread *_networkRequestThread = nil;
-
-+ (void)networkRequestThreadEntryPoint:(id)__unused object {
- do {
- NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
- [[NSRunLoop currentRunLoop] run];
- [pool drain];
- } while (YES);
-}
-
-+ (NSThread *)networkRequestThread {
- static dispatch_once_t oncePredicate;
-
- dispatch_once(&oncePredicate, ^{
- _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
- [_networkRequestThread start];
- });
-
- return _networkRequestThread;
-}
-
-+ (AFHTTPRequestOperation *)operationWithRequest:(NSURLRequest *)urlRequest
- completion:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSData *data, NSError *error))completion
-{
- AFHTTPRequestOperation *operation = [[[self alloc] init] autorelease];
- operation.request = urlRequest;
- operation.completion = completion;
-
- return operation;
-}
+@synthesize acceptableStatusCodes = _acceptableStatusCodes;
+@synthesize acceptableContentTypes = _acceptableContentTypes;
+@synthesize error = _HTTPError;
-+ (AFHTTPRequestOperation *)streamingOperationWithRequest:(NSURLRequest *)urlRequest
- inputStream:(NSInputStream *)inputStream
- outputStream:(NSOutputStream *)outputStream
- completion:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))completion
-{
- NSMutableURLRequest *mutableURLRequest = [[urlRequest mutableCopy] autorelease];
- if (inputStream) {
- [mutableURLRequest setHTTPBodyStream:inputStream];
- if ([[mutableURLRequest HTTPMethod] isEqualToString:@"GET"]) {
- [mutableURLRequest setHTTPMethod:@"POST"];
- }
- }
-
- AFHTTPRequestOperation *operation = [self operationWithRequest:mutableURLRequest completion:^(NSURLRequest *request, NSHTTPURLResponse *response, __unused NSData *data, NSError *error) {
- if (completion) {
- completion(request, response, error);
- }
- }];
-
- operation.outputStream = outputStream;
-
- return operation;
-}
-
-- (id)init {
- self = [super init];
+- (id)initWithRequest:(NSURLRequest *)request {
+ self = [super initWithRequest:request];
if (!self) {
- return nil;
+ return nil;
}
-
- self.runLoopModes = [NSSet setWithObject:NSRunLoopCommonModes];
- self.state = AFHTTPOperationReadyState;
-
+ self.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)];
+
return self;
}
- (void)dealloc {
- [_runLoopModes release];
-
- [_request release];
- [_response release];
- [_responseBody release];
- [_dataAccumulator release];
- [_outputStream release]; _outputStream = nil;
-
- [_connection release]; _connection = nil;
-
- [_uploadProgress release];
- [_downloadProgress release];
- [_completion release];
+ [_acceptableStatusCodes release];
+ [_acceptableContentTypes release];
+ [_HTTPError release];
[super dealloc];
}
-- (void)setUploadProgressBlock:(void (^)(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite))block {
- self.uploadProgress = block;
-}
-
-- (void)setDownloadProgressBlock:(void (^)(NSInteger bytesRead, NSInteger totalBytesRead, NSInteger totalBytesExpectedToRead))block {
- self.downloadProgress = block;
-}
-
-- (void)setState:(AFHTTPOperationState)state {
- if (self.state == state) {
- return;
- }
-
- if (!AFHTTPOperationStateTransitionIsValid(self.state, state)) {
- return;
- }
-
- NSString *oldStateKey = AFKeyPathFromOperationState(self.state);
- NSString *newStateKey = AFKeyPathFromOperationState(state);
-
- [self willChangeValueForKey:newStateKey];
- [self willChangeValueForKey:oldStateKey];
- _state = state;
- [self didChangeValueForKey:oldStateKey];
- [self didChangeValueForKey:newStateKey];
-
- switch (state) {
- case AFHTTPOperationExecutingState:
- [[NSNotificationCenter defaultCenter] postNotificationName:AFHTTPOperationDidStartNotification object:self];
- break;
- case AFHTTPOperationFinishedState:
- [[NSNotificationCenter defaultCenter] postNotificationName:AFHTTPOperationDidFinishNotification object:self];
- break;
- default:
- break;
- }
-}
-
-- (void)setCancelled:(BOOL)cancelled {
- [self willChangeValueForKey:@"isCancelled"];
- _cancelled = cancelled;
- [self didChangeValueForKey:@"isCancelled"];
-
- if ([self isCancelled]) {
- self.state = AFHTTPOperationFinishedState;
- }
-}
-
-- (NSString *)responseString {
- if (!self.response || !self.responseBody) {
- return nil;
- }
-
- NSStringEncoding textEncoding = CFStringConvertEncodingToNSStringEncoding(CFStringConvertIANACharSetNameToEncoding((CFStringRef)self.response.textEncodingName));
-
- return [[[NSString alloc] initWithData:self.responseBody encoding:textEncoding] autorelease];
-}
-
-#pragma mark - NSOperation
-
-- (BOOL)isReady {
- return self.state == AFHTTPOperationReadyState;
-}
-
-- (BOOL)isExecuting {
- return self.state == AFHTTPOperationExecutingState;
-}
-
-- (BOOL)isFinished {
- return self.state == AFHTTPOperationFinishedState;
-}
-
-- (BOOL)isConcurrent {
- return YES;
-}
-
-- (void)start {
- if (![self isReady]) {
- return;
- }
-
- self.state = AFHTTPOperationExecutingState;
-
- [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:YES modes:[self.runLoopModes allObjects]];
-}
-
-- (void)operationDidStart {
- self.connection = [[[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO] autorelease];
-
- NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
- for (NSString *runLoopMode in self.runLoopModes) {
- [self.connection scheduleInRunLoop:runLoop forMode:runLoopMode];
- [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode];
- }
-
- [self.connection start];
-}
-
-- (void)cancel {
- if ([self isFinished]) {
- return;
- }
-
- [super cancel];
-
- self.cancelled = YES;
-
- [self.connection cancel];
-}
-
-- (void)finish {
- self.state = AFHTTPOperationFinishedState;
-
- if ([self isCancelled]) {
- return;
- }
-
- if (self.completion) {
- self.completion(self.request, self.response, self.responseBody, self.error);
- }
-}
-
-#pragma mark - NSURLConnection
-
-- (void)connection:(NSURLConnection *)__unused connection
-didReceiveResponse:(NSURLResponse *)response
-{
- self.response = (NSHTTPURLResponse *)response;
-
- if (self.outputStream) {
- [self.outputStream open];
- } else {
-
- NSUInteger maxCapacity = MAX((NSUInteger)llabs(response.expectedContentLength), kAFHTTPMinimumInitialDataCapacity);
- NSUInteger capacity = MIN(maxCapacity, kAFHTTPMaximumInitialDataCapacity);
- self.dataAccumulator = [NSMutableData dataWithCapacity:capacity];
- }
-}
-
-- (void)connection:(NSURLConnection *)__unused connection
- didReceiveData:(NSData *)data
-{
- self.totalBytesRead += [data length];
-
- if (self.outputStream) {
- if ([self.outputStream hasSpaceAvailable]) {
- const uint8_t *dataBuffer = [data bytes];
- [self.outputStream write:&dataBuffer[0] maxLength:[data length]];
+- (NSHTTPURLResponse *)response {
+ return (NSHTTPURLResponse *)[super response];
+}
+
+- (NSError *)error {
+ if (self.response) {
+ if (![self hasAcceptableStatusCode]) {
+ NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
+ [userInfo setValue:[NSString stringWithFormat:NSLocalizedString(@"Expected status code %@, got %d", nil), self.acceptableStatusCodes, [self.response statusCode]] forKey:NSLocalizedDescriptionKey];
+ [userInfo setValue:[self.request URL] forKey:NSURLErrorFailingURLErrorKey];
+
+ self.error = [[[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorBadServerResponse userInfo:userInfo] autorelease];
+ } else if (![self hasAcceptableContentType]) {
+ NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
+ [userInfo setValue:[NSString stringWithFormat:NSLocalizedString(@"Expected content type %@, got %@", nil), self.acceptableContentTypes, [self.response MIMEType]] forKey:NSLocalizedDescriptionKey];
+ [userInfo setValue:[self.request URL] forKey:NSURLErrorFailingURLErrorKey];
+
+ self.error = [[[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:userInfo] autorelease];
}
- } else {
- [self.dataAccumulator appendData:data];
}
- if (self.downloadProgress) {
- self.downloadProgress([data length], self.totalBytesRead, (NSInteger)self.response.expectedContentLength);
- }
+ return [super error];
}
-- (void)connectionDidFinishLoading:(NSURLConnection *)__unused connection {
- if (self.outputStream) {
- [self.outputStream close];
- } else {
- self.responseBody = [NSData dataWithData:self.dataAccumulator];
- [_dataAccumulator release]; _dataAccumulator = nil;
- }
-
- [self finish];
+- (BOOL)hasAcceptableStatusCode {
+ return !self.acceptableStatusCodes || [self.acceptableStatusCodes containsIndex:[self.response statusCode]];
}
-- (void)connection:(NSURLConnection *)__unused connection
- didFailWithError:(NSError *)error
-{
- self.error = error;
-
- if (self.outputStream) {
- [self.outputStream close];
- } else {
- [_dataAccumulator release]; _dataAccumulator = nil;
- }
-
- [self finish];
+- (BOOL)hasAcceptableContentType {
+ return !self.acceptableContentTypes || [self.acceptableContentTypes containsObject:[self.response MIMEType]];
}
-- (void)connection:(NSURLConnection *)__unused connection
- didSendBodyData:(NSInteger)bytesWritten
- totalBytesWritten:(NSInteger)totalBytesWritten
-totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
-{
- if (self.uploadProgress) {
- self.uploadProgress(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
- }
+#pragma mark - AFHTTPClientOperation
+
++ (BOOL)canProcessRequest:(NSURLRequest *)request {
+ return NO;
}
-- (NSCachedURLResponse *)connection:(NSURLConnection *)__unused connection
- willCacheResponse:(NSCachedURLResponse *)cachedResponse
++ (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest
+ success:(void (^)(id object))success
+ failure:(void (^)(NSHTTPURLResponse *response, NSError *error))failure
{
- if ([self isCancelled]) {
- return nil;
- }
-
- return cachedResponse;
-}
+ return nil;
+}
@end
View
17 AFNetworking/AFImageCache.h
@@ -23,8 +23,10 @@
#import <Foundation/Foundation.h>
#import "AFImageRequestOperation.h"
+#import <Availability.h>
+
/**
- `AFImageCache` is a subclass of `NSCache` that stores and retrieves images from cache.
+ `AFImageCache` is an `NSCache` that stores and retrieves images from cache.
@discussion `AFImageCache` is used to cache images for successful `AFImageRequestOperations` with the proper cache policy.
*/
@@ -45,8 +47,14 @@
@return The image associated with the URL and cache name, or `nil` if not image exists.
*/
+
+#if __IPHONE_OS_VERSION_MIN_REQUIRED
- (UIImage *)cachedImageForURL:(NSURL *)url
cacheName:(NSString *)cacheName;
+#elif __MAC_OS_X_VERSION_MIN_REQUIRED
+- (NSImage *)cachedImageForURL:(NSURL *)url
+ cacheName:(NSString *)cacheName;
+#endif
/**
Stores an image into cache, associated with a given URL and cache name.
@@ -55,8 +63,15 @@
@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.
*/
+
+#if __IPHONE_OS_VERSION_MIN_REQUIRED
- (void)cacheImage:(UIImage *)image
forURL:(NSURL *)url
cacheName:(NSString *)cacheName;
+#elif __MAC_OS_X_VERSION_MIN_REQUIRED
+- (void)cacheImage:(NSImage *)image
+ forURL:(NSURL *)url
+ cacheName:(NSString *)cacheName;
+#endif
@end
View
23 AFNetworking/AFImageCache.m
@@ -39,21 +39,42 @@ + (AFImageCache *)sharedImageCache {
return _sharedImageCache;
}
+#if __IPHONE_OS_VERSION_MIN_REQUIRED
- (UIImage *)cachedImageForURL:(NSURL *)url
cacheName:(NSString *)cacheName
{
return [self objectForKey:AFImageCacheKeyFromURLAndCacheName(url, cacheName)];
}
+#elif __MAC_OS_X_VERSION_MIN_REQUIRED
+- (NSImage *)cachedImageForURL:(NSURL *)url
+ cacheName:(NSString *)cacheName
+{
+ return [self objectForKey:AFImageCacheKeyFromURLAndCacheName(url, cacheName)];
+}
+#endif
+#if __IPHONE_OS_VERSION_MIN_REQUIRED
- (void)cacheImage:(UIImage *)image
forURL:(NSURL *)url
cacheName:(NSString *)cacheName
{
if (!image) {
return;
}
-
+
+ [self setObject:image forKey:AFImageCacheKeyFromURLAndCacheName(url, cacheName)];
+}
+#elif __MAC_OS_X_VERSION_MIN_REQUIRED
+- (void)cacheImage:(NSImage *)image
+ forURL:(NSURL *)url
+ cacheName:(NSString *)cacheName
+{
+ if (!image) {
+ return;
+ }
+
[self setObject:image forKey:AFImageCacheKeyFromURLAndCacheName(url, cacheName)];
}
+#endif
@end
View
71 AFNetworking/AFImageRequestOperation.h
@@ -21,27 +21,64 @@
// THE SOFTWARE.
#import <Foundation/Foundation.h>
-#import <UIKit/UIKit.h>
#import "AFHTTPRequestOperation.h"
+#import <Availability.h>
+
+#if __IPHONE_OS_VERSION_MIN_REQUIRED
+#import <UIKit/UIKit.h>
+#elif __MAC_OS_X_VERSION_MIN_REQUIRED
+#import <Cocoa/Cocoa.h>
+#endif
+
/**
- `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.
+ `AFImageRequestOperation` is a subclass of `AFHTTPRequestOperation` for downloading an processing images.
- @see NSOperation
- @see AFHTTPRequestOperation
+ ## Acceptable Content Types
+
+ By default, `AFImageRequestOperation` accepts the following MIME types, which correspond to the image formats supported by UIImage or NSImage:
+
+ - `image/tiff`
+ - `image/jpeg`
+ - `image/gif`
+ - `image/png`
+ - `image/ico`
+ - `image/x-icon`
+ - `image/bmp`
+ - `image/x-bmp`
+ - `image/x-xbitmap`
+ - `image/x-win-bitmap`
*/
-@interface AFImageRequestOperation : AFHTTPRequestOperation
+@interface AFImageRequestOperation : AFHTTPRequestOperation {
+@private
+#if __IPHONE_OS_VERSION_MIN_REQUIRED
+ UIImage *_responseImage;
+#elif __MAC_OS_X_VERSION_MIN_REQUIRED
+ NSImage *_responseImage;
+#endif
+}
+
+#if __IPHONE_OS_VERSION_MIN_REQUIRED
+@property (readonly, nonatomic, retain) UIImage *responseImage;
+#elif __MAC_OS_X_VERSION_MIN_REQUIRED
+@property (readonly, nonatomic, retain) NSImage *responseImage;
+#endif
/**
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.
+ @param success A block object to be executed when the request finishes successfully. 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;
+#if __IPHONE_OS_VERSION_MIN_REQUIRED
++ (AFImageRequestOperation *)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest
+ success:(void (^)(UIImage *image))success;
+#elif __MAC_OS_X_VERSION_MIN_REQUIRED
++ (AFImageRequestOperation *)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest
+ success:(void (^)(NSImage *image))success;
+#endif
/**
Creates and returns an `AFImageRequestOperation` object and sets the specified success callback.
@@ -54,10 +91,18 @@
@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;
+#if __IPHONE_OS_VERSION_MIN_REQUIRED
++ (AFImageRequestOperation *)imageRequestOperationWithRequest:(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;
+#elif __MAC_OS_X_VERSION_MIN_REQUIRED
++ (AFImageRequestOperation *)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest
+ imageProcessingBlock:(NSImage *(^)(NSImage *))imageProcessingBlock
+ cacheName:(NSString *)cacheNameOrNil
+ success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSImage *image))success
+ failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure;
+#endif
@end
View
203 AFNetworking/AFImageRequestOperation.m
@@ -26,63 +26,212 @@
static dispatch_queue_t af_image_request_operation_processing_queue;
static dispatch_queue_t image_request_operation_processing_queue() {
if (af_image_request_operation_processing_queue == NULL) {
- af_image_request_operation_processing_queue = dispatch_queue_create("com.alamofire.image-request.processing", 0);
+ af_image_request_operation_processing_queue = dispatch_queue_create("com.alamofire.networking.image-request.processing", 0);
}
return af_image_request_operation_processing_queue;
}
+@interface AFImageRequestOperation ()
+#if __IPHONE_OS_VERSION_MIN_REQUIRED
+@property (readwrite, nonatomic, retain) UIImage *responseImage;
+#elif __MAC_OS_X_VERSION_MIN_REQUIRED
+@property (readwrite, nonatomic, retain) NSImage *responseImage;
+#endif
+
++ (NSSet *)defaultAcceptableContentTypes;
++ (NSSet *)defaultAcceptablePathExtensions;
+@end
+
@implementation AFImageRequestOperation
+@synthesize responseImage = _responseImage;
-+ (AFImageRequestOperation *)operationWithRequest:(NSURLRequest *)urlRequest
- success:(void (^)(UIImage *image))success
+#if __IPHONE_OS_VERSION_MIN_REQUIRED
++ (AFImageRequestOperation *)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest
+ success:(void (^)(UIImage *image))success
+{
+ return [self imageRequestOperationWithRequest:urlRequest imageProcessingBlock:nil cacheName:nil success:^(NSURLRequest __unused *request, NSHTTPURLResponse __unused *response, UIImage *image) {
+ if (success) {
+ success(image);
+ }
+ } failure:nil];
+}
+#elif __MAC_OS_X_VERSION_MIN_REQUIRED
++ (AFImageRequestOperation *)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest
+ success:(void (^)(NSImage *image))success
{
- return [self operationWithRequest:urlRequest imageProcessingBlock:nil cacheName:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
+ return [self imageRequestOperationWithRequest:urlRequest imageProcessingBlock:nil cacheName:nil success:^(NSURLRequest __unused *request, NSHTTPURLResponse __unused *response, NSImage *image) {
if (success) {
success(image);
}
} failure:nil];
}
+#endif
+
-+ (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
+#if __IPHONE_OS_VERSION_MIN_REQUIRED
++ (AFImageRequestOperation *)imageRequestOperationWithRequest:(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
{
- return (AFImageRequestOperation *)[self operationWithRequest:urlRequest completion:^(NSURLRequest *request, NSHTTPURLResponse *response, NSData *data, NSError *error) {
+ AFImageRequestOperation *operation = [[[AFImageRequestOperation alloc] initWithRequest:urlRequest] autorelease];
+
+ operation.completionBlock = ^ {
+ if ([operation isCancelled]) {
+ return;
+ }
+
dispatch_async(image_request_operation_processing_queue(), ^(void) {
- if (error) {
+ if (operation.error) {
if (failure) {
dispatch_async(dispatch_get_main_queue(), ^(void) {
- failure(request, response, error);
+ failure(operation.request, operation.response, operation.error);
+ });
+ }
+ } else {
+ UIImage *image = operation.responseImage;
+
+ if (imageProcessingBlock) {
+ image = imageProcessingBlock(image);
+ }
+
+ if (success) {
+ dispatch_async(dispatch_get_main_queue(), ^(void) {
+ success(operation.request, operation.response, image);
});
}
- } else {
- UIImage *image = nil;
- if ([[UIScreen mainScreen] scale] == 2.0) {
- CGImageRef imageRef = [[UIImage imageWithData:data] CGImage];
- image = [UIImage imageWithCGImage:imageRef scale:2.0 orientation:UIImageOrientationUp];
- } else {
- image = [UIImage imageWithData:data];
+
+ if ([operation.request cachePolicy] != NSURLCacheStorageNotAllowed) {
+ [[AFImageCache sharedImageCache] cacheImage:image forURL:[operation.request URL] cacheName:cacheNameOrNil];
+ }
+ }
+ });
+ };
+
+ return operation;
+}
+#elif __MAC_OS_X_VERSION_MIN_REQUIRED
++ (AFImageRequestOperation *)imageRequestOperationWithRequest:(NSURLRequest *)urlRequest
+ imageProcessingBlock:(NSImage *(^)(NSImage *))imageProcessingBlock
+ cacheName:(NSString *)cacheNameOrNil
+ success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSImage *image))success
+ failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure
+{
+ AFImageRequestOperation *operation = [[[AFImageRequestOperation alloc] initWithRequest:urlRequest] autorelease];
+
+ operation.completionBlock = ^ {
+ if ([operation isCancelled]) {
+ return;
+ }
+
+ dispatch_async(image_request_operation_processing_queue(), ^(void) {
+ if (operation.error) {
+ if (failure) {
+ dispatch_async(dispatch_get_main_queue(), ^(void) {
+ failure(operation.request, operation.response, operation.error);
+ });
}
+ } else {
+ NSImage *image = operation.responseImage;
if (imageProcessingBlock) {
image = imageProcessingBlock(image);
}
- dispatch_async(dispatch_get_main_queue(), ^(void) {
- if (success) {
- success(request, response, image);
- }
- });
+ if (success) {
+ dispatch_async(dispatch_get_main_queue(), ^(void) {
+ success(operation.request, operation.response, image);
+ });
+ }
- if ([request cachePolicy] != NSURLCacheStorageNotAllowed) {
- [[AFImageCache sharedImageCache] cacheImage:image forURL:[request URL] cacheName:cacheNameOrNil];
+ if ([operation.request cachePolicy] != NSURLCacheStorageNotAllowed) {
+ [[AFImageCache sharedImageCache] cacheImage:image forURL:[operation.request URL] cacheName:cacheNameOrNil];
}
}
- });
+ });
+ };
+
+ return operation;
+}
+#endif
+
++ (NSSet *)defaultAcceptableContentTypes {
+ return [NSSet setWithObjects:@"image/tiff", @"image/jpeg", @"image/gif", @"image/png", @"image/ico", @"image/x-icon" @"image/bmp", @"image/x-bmp", @"image/x-xbitmap", @"image/x-win-bitmap", nil];
+}
+
++ (NSSet *)defaultAcceptablePathExtensions {
+ return [NSSet setWithObjects:@"tif", @"tiff", @"jpg", @"jpeg", @"gif", @"png", @"ico", @"bmp", @"cur", nil];
+}
+
+- (id)initWithRequest:(NSURLRequest *)urlRequest {
+ self = [super initWithRequest:urlRequest];
+ if (!self) {
+ return nil;
+ }
+
+ self.acceptableContentTypes = [[self class] defaultAcceptableContentTypes];
+
+ return self;
+}
+
+- (void)dealloc {
+ [_responseImage release];
+ [super dealloc];
+}
+
+#if __IPHONE_OS_VERSION_MIN_REQUIRED
+- (UIImage *)responseImage {
+ if (!_responseImage && [self isFinished]) {
+ if ([[UIScreen mainScreen] scale] == 2.0) {
+ CGImageRef imageRef = [[UIImage imageWithData:self.responseData] CGImage];
+ self.responseImage = [UIImage imageWithCGImage:imageRef scale:2.0 orientation:UIImageOrientationUp];
+ } else {
+ self.responseImage = [UIImage imageWithData:self.responseData];
+ }
+ }
+
+ return _responseImage;
+}
+#elif __MAC_OS_X_VERSION_MIN_REQUIRED
+- (NSImage *)responseImage {
+ if (!_responseImage && [self isFinished]) {
+ self.responseImage = [[[NSImage alloc] initWithData:self.responseData] autorelease];
+ }
+
+ return _responseImage;
+}
+#endif
+
+#pragma mark - AFHTTPClientOperation
+
++ (BOOL)canProcessRequest:(NSURLRequest *)request {
+ return [[self defaultAcceptableContentTypes] containsObject:[request valueForHTTPHeaderField:@"Accept"]] || [[self defaultAcceptablePathExtensions] containsObject:[[request URL] pathExtension]];
+}
+
+#if __IPHONE_OS_VERSION_MIN_REQUIRED
++ (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest
+ success:(void (^)(id object))success
+ failure:(void (^)(NSHTTPURLResponse *response, NSError *error))failure
+{
+ return [self imageRequestOperationWithRequest:urlRequest imageProcessingBlock:nil cacheName:nil success:^(NSURLRequest __unused *request, NSHTTPURLResponse __unused *response, UIImage *image) {
+ success(image);
+ } failure:^(NSURLRequest __unused *request, NSHTTPURLResponse *response, NSError *error) {
+ failure(response, error);
+ }];
+}
+#elif __MAC_OS_X_VERSION_MIN_REQUIRED
++ (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest
+ success:(void (^)(id object))success
+ failure:(void (^)(NSHTTPURLResponse *response, NSError *error))failure
+{
+ return [self imageRequestOperationWithRequest:urlRequest imageProcessingBlock:nil cacheName:nil success:^(NSURLRequest __unused *request, NSHTTPURLResponse __unused *response, NSImage *image) {
+ success(image);
+ } failure:^(NSURLRequest __unused *request, NSHTTPURLResponse *response, NSError *error) {
+ failure(response, error);
}];
}
+#endif
@end
View
89 AFNetworking/AFJSONRequestOperation.h
@@ -24,83 +24,44 @@
#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.
+ `AFJSONRequestOperation` is a subclass of `AFHTTPRequestOperation` for downloading and working with JSON response data.
- @see NSOperation
- @see AFHTTPRequestOperation
- */
-@interface AFJSONRequestOperation : AFHTTPRequestOperation
-
-///---------------------------------------
-/// @name Creating JSON Request Operations
-///---------------------------------------
-
-/**
- Creates and returns an `AFJSONRequestOperation` object and sets the specified success callback.
+ ## Acceptable Content Types
- @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:
+ By default, `AFJSONRequestOperation` accepts the following MIME types, which includes the official standard, `application/json`, as well as other commonly-used types:
- @return A new JSON request operation
+ - `application/json`
+ - `text/json`
*/
-+ (AFJSONRequestOperation *)operationWithRequest:(NSURLRequest *)urlRequest
- success:(void (^)(id JSON))success;
+@interface AFJSONRequestOperation : AFHTTPRequestOperation {
+@private
+ id _responseJSON;
+ NSError *_JSONError;
+}
-/**
- 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 two arguments: the response from the server, and 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
- failure:(void (^)(NSHTTPURLResponse *response, NSError *error))failure;
+///----------------------------
+/// @name Getting Response Data
+///----------------------------
/**
- 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
+ A JSON object constructed from the response data. If an error occurs while parsing, `nil` will be returned, and the `error` property will be set to the error.
*/
-+ (AFJSONRequestOperation *)operationWithRequest:(NSURLRequest *)urlRequest
- acceptableStatusCodes:(NSIndexSet *)acceptableStatusCodes
- acceptableContentTypes:(NSSet *)acceptableContentTypes
- success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id JSON))success
- failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure;
-
+@property (readonly, nonatomic, retain) id responseJSON;
///----------------------------------
-/// @name Getting Default HTTP Values
+/// @name Creating Request Operations
///----------------------------------
/**
- 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;
-
-/**
- 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.
+ Creates and returns an `AFJSONRequestOperation` object and sets the specified success and failure callbacks.
- By default, this contains `application/json`, `application/x-javascript`, `text/javascript`, `text/x-javascript`, `text/x-json`, `text/json`, and `text/plain`
+ @param urlRequest The request object to be loaded asynchronously during execution of the operation
+ @param success A block object to be executed when the operation 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 JSON object created from the response data of request.
+ @param failure A block object to be executed when the 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
*/
-+ (NSSet *)defaultAcceptableContentTypes;
-
++ (AFJSONRequestOperation *)JSONRequestOperationWithRequest:(NSURLRequest *)urlRequest
+ success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id JSON))success
+ failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure;
@end
View
163 AFNetworking/AFJSONRequestOperation.m
@@ -21,115 +21,132 @@
// THE SOFTWARE.
#import "AFJSONRequestOperation.h"
-#import "JSONKit.h"
#include <Availability.h>
+#import "JSONKit.h"
+
static dispatch_queue_t af_json_request_operation_processing_queue;
static dispatch_queue_t json_request_operation_processing_queue() {
if (af_json_request_operation_processing_queue == NULL) {
- af_json_request_operation_processing_queue = dispatch_queue_create("com.alamofire.json-request.processing", 0);
+ af_json_request_operation_processing_queue = dispatch_queue_create("com.alamofire.networking.json-request.processing", 0);
}
return af_json_request_operation_processing_queue;
}
-@implementation AFJSONRequestOperation
+@interface AFJSONRequestOperation ()
+@property (readwrite, nonatomic, retain) id responseJSON;
+@property (readwrite, nonatomic, retain) NSError *error;
-+ (AFJSONRequestOperation *)operationWithRequest:(NSURLRequest *)urlRequest
- success:(void (^)(id JSON))success
-{
- return [self operationWithRequest:urlRequest success:success failure:nil];
-}
++ (NSSet *)defaultAcceptableContentTypes;
++ (NSSet *)defaultAcceptablePathExtensions;
+@end
-+ (AFJSONRequestOperation *)operationWithRequest:(NSURLRequest *)urlRequest
- success:(void (^)(id JSON))success
- failure:(void (^)(NSHTTPURLResponse *response, NSError *error))failure
-{
- return [self operationWithRequest:urlRequest acceptableStatusCodes:[self defaultAcceptableStatusCodes] acceptableContentTypes:[self defaultAcceptableContentTypes] success:^(NSURLRequest __unused *request, NSHTTPURLResponse __unused *response, id JSON) {
- if (success) {
- success(JSON);
- }
- } failure:^(NSURLRequest __unused *request, NSHTTPURLResponse *response, NSError *error) {
- if (failure) {
- failure(response, error);
- }
- }];
-}
+@implementation AFJSONRequestOperation
+@synthesize responseJSON = _responseJSON;
+@synthesize error = _JSONError;
-+ (AFJSONRequestOperation *)operationWithRequest:(NSURLRequest *)urlRequest
- acceptableStatusCodes:(NSIndexSet *)acceptableStatusCodes
- acceptableContentTypes:(NSSet *)acceptableContentTypes
- success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id JSON))success
- failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure
++ (AFJSONRequestOperation *)JSONRequestOperationWithRequest:(NSURLRequest *)urlRequest
+ success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id JSON))success
+ failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error))failure
{
- return (AFJSONRequestOperation *)[self operationWithRequest:urlRequest completion:^(NSURLRequest *request, NSHTTPURLResponse *response, NSData *data, NSError *error) {
- if (!error) {
- if (acceptableStatusCodes && ![acceptableStatusCodes containsIndex:[response statusCode]]) {
- NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
- [userInfo setValue:[NSString stringWithFormat:NSLocalizedString(@"Expected status code %@, got %d", nil), acceptableStatusCodes, [response statusCode]] forKey:NSLocalizedDescriptionKey];
- [userInfo setValue:[request URL] forKey:NSURLErrorFailingURLErrorKey];
-
- error = [[[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorBadServerResponse userInfo:userInfo] autorelease];
- }
-
- if (acceptableContentTypes && ![acceptableContentTypes containsObject:[response MIMEType]]) {
- NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
- [userInfo setValue:[NSString stringWithFormat:NSLocalizedString(@"Expected content type %@, got %@", nil), acceptableContentTypes, [response MIMEType]] forKey:NSLocalizedDescriptionKey];
- [userInfo setValue:[request URL] forKey:NSURLErrorFailingURLErrorKey];
-
- error = [[[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:userInfo] autorelease];
- }
+ AFJSONRequestOperation *operation = [[[self alloc] initWithRequest:urlRequest] autorelease];
+ operation.completionBlock = ^ {
+ if ([operation isCancelled]) {
+ return;
}
- if (error) {
+ if (operation.error) {
if (failure) {
- dispatch_async(dispatch_get_main_queue(), ^{
- failure(request, response, error);
- });
- }
- } else if ([data length] == 0) {
- if (success) {
- dispatch_async(dispatch_get_main_queue(), ^{
- success(request, response, nil);
+ dispatch_async(dispatch_get_main_queue(), ^(void) {
+ failure(operation.request, operation.response, operation.error);
});
}
} else {
dispatch_async(json_request_operation_processing_queue(), ^(void) {
- id JSON = nil;
- NSError *JSONError = nil;
-#if __IPHONE_OS_VERSION_MIN_REQUIRED > __IPHONE_4_3
- if ([NSJSONSerialization class]) {
- JSON = [NSJSONSerialization JSONObjectWithData:data options:0 error:&JSONError];
- } else {
- JSON = [[JSONDecoder decoder] objectWithData:data error:&JSONError];
- }
-#else
- JSON = [[JSONDecoder decoder] objectWithData:data error:&JSONError];
-#endif
+ NSError *error = nil;
+ id JSON = operation.responseJSON;
+ operation.error = error;
dispatch_async(dispatch_get_main_queue(), ^(void) {
- if (JSONError) {
+ if (operation.error) {
if (failure) {
- failure(request, response, JSONError);
+ failure(operation.request, operation.response, operation.error);
}
} else {
if (success) {
- success(request, response, JSON);
+ success(operation.request, operation.response, JSON);
}
}
- });
+ });
});
}
- }];
+ };
+
+ return operation;
+}
+
++ (NSSet *)defaultAcceptableContentTypes {
+ return [NSSet setWithObjects:@"application/json", @"text/json", nil];
}
-+ (NSIndexSet *)defaultAcceptableStatusCodes {
- return [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)];
++ (NSSet *)defaultAcceptablePathExtensions {
+ return [NSSet setWithObjects:@"json", nil];
}
-+ (NSSet *)defaultAcceptableContentTypes {
- return [NSSet setWithObjects:@"application/json", @"application/x-javascript", @"text/javascript", @"text/x-javascript", @"text/x-json", @"text/json", @"text/plain", nil];
+- (id)initWithRequest:(NSURLRequest *)urlRequest {
+ self = [super initWithRequest:urlRequest];
+ if (!self) {
+ return nil;
+ }
+
+ self.acceptableContentTypes = [[self class] defaultAcceptableContentTypes];
+
+ return self;
+}
+
+- (void)dealloc {
+ [_responseJSON release];
+ [_JSONError release];
+ [super dealloc];
+}
+
+- (id)responseJSON {
+ if (!_responseJSON && [self isFinished]) {
+ NSError *error = nil;
+
+#if __IPHONE_OS_VERSION_MIN_REQUIRED > __IPHONE_4_3 || __MAC_OS_X_VERSION_MIN_REQUIRED > __MAC_10_6
+ if ([NSJSONSerialization class]) {
+ self.responseJSON = [NSJSONSerialization JSONObjectWithData:self.responseData options:0 error:&error];
+ } else {
+ self.responseJSON = [[JSONDecoder decoder] objectWithData:self.responseData error:&error];
+ }
+#else
+ self.responseJSON = [[JSONDecoder decoder] objectWithData:self.responseData error:&error];
+#endif
+
+ self.error = error;
+ }
+
+ return _responseJSON;
+}
+
+#pragma mark - AFHTTPClientOperation
+
++ (BOOL)canProcessRequest:(NSURLRequest *)request {
+ return [[self defaultAcceptableContentTypes] containsObject:[request valueForHTTPHeaderField:@"Accept"]] || [[self defaultAcceptablePathExtensions] containsObject:[[request URL] pathExtension]];
+}
+
++ (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest
+ success:(void (^)(id object))success
+ failure:(void (^)(NSHTTPURLResponse *response, NSError *error))failure
+{
+ return [self JSONRequestOperationWithRequest:urlRequest success:^(NSURLRequest __unused *request, NSHTTPURLResponse __unused *response, id JSON) {
+ success(JSON);
+ } failure:^(NSURLRequest __unused *request, NSHTTPURLResponse *response, NSError *error) {
+ failure(response, error);
+ }];
}
@end
View
7 AFNetworking/AFNetworkActivityIndicatorManager.h
@@ -22,13 +22,19 @@
#import <Foundation/Foundation.h>
+#import <Availability.h>
+
+#if __IPHONE_OS_VERSION_MIN_REQUIRED
+#import <UIKit/UIKit.h>
+
/**
`AFNetworkActivityIndicatorManager` manages the state of the network activity indicator in the status bar. When enabled, it will listen for notifications indicating that a network request operation has started or finished, and start or stop animating the indicator accordingly. 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 {
@private
NSInteger _activityCount;
BOOL _enabled;
+ NSTimer *_activityIndicatorVisibilityTimer;
}
/**
@@ -56,3 +62,4 @@
- (void)decrementActivityCount;
@end
+#endif
View
36 AFNetworking/AFNetworkActivityIndicatorManager.m
@@ -24,13 +24,22 @@
#import "AFHTTPRequestOperation.h"
+#if __IPHONE_OS_VERSION_MIN_REQUIRED
+static NSTimeInterval const kAFNetworkActivityIndicatorInvisibilityDelay = 0.25;
+
@interface AFNetworkActivityIndicatorManager ()
@property (readwrite, nonatomic, assign) NSInteger activityCount;
+@property (readwrite, nonatomic, retain) NSTimer *activityIndicatorVisibilityTimer;
+@property (readonly, getter = isNetworkActivityIndicatorVisible) BOOL networkActivityIndicatorVisible;
+
+- (void)updateNetworkActivityIndicatorVisibility;
@end
@implementation AFNetworkActivityIndicatorManager
@synthesize activityCount = _activityCount;
+@synthesize activityIndicatorVisibilityTimer = _activityIndicatorVisibilityTimer;
@synthesize enabled = _enabled;
+@dynamic networkActivityIndicatorVisible;
+ (AFNetworkActivityIndicatorManager *)sharedManager {
static AFNetworkActivityIndicatorManager *_sharedManager = nil;
@@ -48,15 +57,18 @@ - (id)init {
return nil;
}
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(incrementActivityCount) name:AFHTTPOperationDidStartNotification object:nil];
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(decrementActivityCount) name:AFHTTPOperationDidFinishNotification object:nil];
-
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(incrementActivityCount) name:AFNetworkingOperationDidStartNotification object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(decrementActivityCount) name:AFNetworkingOperationDidFinishNotification object:nil];
+
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
+ [_activityIndicatorVisibilityTimer invalidate];
+ [_activityIndicatorVisibilityTimer release]; _activityIndicatorVisibilityTimer = nil;
+
[super dealloc];
}
@@ -66,10 +78,25 @@ - (void)setActivityCount:(NSInteger)activityCount {
[self didChangeValueForKey:@"activityCount"];
if (self.enabled) {
- [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:self.activityCount > 0];
+ // Delay hiding of activity indicator for a short interval, to avoid flickering
+ if (![self isNetworkActivityIndicatorVisible]) {
+ [self.activityIndicatorVisibilityTimer invalidate];
+ self.activityIndicatorVisibilityTimer = [NSTimer timerWithTimeInterval:kAFNetworkActivityIndicatorInvisibilityDelay target:self selector:@selector(updateNetworkActivityIndicatorVisibility) userInfo:nil repeats:NO];
+ [[NSRunLoop currentRunLoop] addTimer:self.activityIndicatorVisibilityTimer forMode:NSRunLoopCommonModes];
+ } else {
+ [self updateNetworkActivityIndicatorVisibility];
+ }
}
}
+- (BOOL)isNetworkActivityIndicatorVisible {
+ return self.activityCount > 0;
+}
+
+- (void)updateNetworkActivityIndicatorVisibility {
+ [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:[self isNetworkActivityIndicatorVisible]];
+}
+
- (void)incrementActivityCount {
@synchronized(self) {
self.activityCount += 1;
@@ -83,3 +110,4 @@ - (void)decrementActivityCount {
}
@end
+#endif
View
40 AFNetworking/AFNetworking.h
@@ -0,0 +1,40 @@
+// 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