diff --git a/AFNetworking.podspec b/AFNetworking.podspec index 3e4dda5888..f8ffa440a9 100644 --- a/AFNetworking.podspec +++ b/AFNetworking.podspec @@ -1,12 +1,12 @@ Pod::Spec.new do |s| s.name = 'AFNetworking' - s.version = '0.10.0' + s.version = '0.10.1' s.license = 'MIT' - s.summary = 'A delightful iOS and OS X networking framework' + s.summary = 'A delightful iOS and OS X networking framework.' s.homepage = 'https://github.com/AFNetworking/AFNetworking' - s.authors = {'Mattt Thompson' => 'm@mattt.me', 'Scott Raymond' => 'sco@gowalla.com'} - s.source = { :git => 'https://github.com/AFNetworking/AFNetworking.git', :tag => '0.10.0' } + s.authors = {'Mattt Thompson' => 'm@mattt.me', 'Scott Raymond' => 'sco@scottraymond.net'} + s.source = { :git => 'https://github.com/AFNetworking/AFNetworking.git', :tag => '0.10.1' } s.source_files = 'AFNetworking' - s.clean_paths = ['iOS Example', 'Mac Example', 'AFNetworking.xcworkspace'] s.framework = 'SystemConfiguration' + s.prefix_header_contents = "#import " end diff --git a/AFNetworking/AFHTTPClient.h b/AFNetworking/AFHTTPClient.h index 249eef9d35..0ab2de7bcb 100644 --- a/AFNetworking/AFHTTPClient.h +++ b/AFNetworking/AFHTTPClient.h @@ -23,74 +23,13 @@ #import @class AFHTTPRequestOperation; -@protocol AFHTTPClientOperation; -@protocol AFMultipartFormData; - -/** - Posted when network reachability changes. - The notification object is an `NSNumber` object containing the boolean value for the current network reachability. - This notification contains no information in the `userInfo` dictionary. - - @warning In order for network reachability to be monitored, include the `SystemConfiguration` framework in the active target's "Link Binary With Library" build phase, and add `#import ` to the header prefix of the project (Prefix.pch). - */ -#ifdef _SYSTEMCONFIGURATION_H -extern NSString * const AFNetworkingReachabilityDidChangeNotification; -#endif - -/** - Specifies network reachability of the client to its `baseURL` domain. - */ -#ifdef _SYSTEMCONFIGURATION_H -typedef enum { - AFNetworkReachabilityStatusUnknown = -1, - AFNetworkReachabilityStatusNotReachable = 0, - AFNetworkReachabilityStatusReachableViaWWAN = 1, - AFNetworkReachabilityStatusReachableViaWiFi = 2, -} AFNetworkReachabilityStatus; -#endif - -/** - Specifies the method used to encode parameters into request body. - */ -typedef enum { - AFFormURLParameterEncoding, - AFJSONParameterEncoding, - AFPropertyListParameterEncoding, -} AFHTTPClientParameterEncoding; - -/** - Returns a string, replacing certain characters with the equivalent percent escape sequence based on the specified encoding. - - @param string The string to URL encode - @param encoding The encoding to use for the replacement. If you are uncertain of the correct encoding, you should use UTF-8 (NSUTF8StringEncoding), which is the encoding designated by RFC 3986 as the correct encoding for use in URLs. - - @discussion The characters escaped are all characters that are not legal URL characters (based on RFC 3986), including any whitespace, punctuation, or special characters. - - @return A URL-encoded string. If it does not need to be modified (no percent escape sequences are missing), this function may merely return string argument. - */ -extern NSString * AFURLEncodedStringFromStringWithEncoding(NSString *string, NSStringEncoding encoding); - -/** - Returns a query string constructed by a set of parameters, using the specified encoding. - - @param parameters The parameters used to construct the query string - @param encoding The encoding to use in constructing the query string. If you are uncertain of the correct encoding, you should use UTF-8 (NSUTF8StringEncoding), which is the encoding designated by RFC 3986 as the correct encoding for use in URLs. - - @discussion Query strings are constructed by collecting each key-value pair, URL-encoding a string representation of the key-value pair, and then joining the components with "&". - - - If a key-value pair has a an `NSArray` for its value, each member of the array will be represented in the format `key[]=value1&key[]value2`. Otherwise, the key-value pair will be formatted as "key=value". String representations of both keys and values are derived using the `-description` method. The constructed query string does not include the ? character used to delimit the query component. - - @return A URL-encoded query string - */ -extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *parameters, NSStringEncoding encoding); /** `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. ## Automatic Content Parsing - 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. + 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`. ## Subclassing Notes @@ -107,32 +46,58 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete By default, `AFHTTPClient` sets the following HTTP headers: - `Accept-Encoding: gzip` - - `Accept-Language: ([NSLocale preferredLanguages]), en-us;q=0.8` + - `Accept-Language: (comma-delimited preferred languages), en-us;q=0.8` - `User-Agent: (generated user agent)` You can override these HTTP headers or define new ones using `setDefaultHeader:value:`. ## URL Construction Using Relative Paths - Both `requestWithMethod:path:parameters` and `multipartFormRequestWithMethod:path:parameters:constructingBodyWithBlock:` construct URLs from the path relative to the `baseURL`, using `NSURL +URLWithString:relativeToURL:`. Below are a few examples of how `baseURL` and relative paths interract: + Both `-requestWithMethod:path:parameters:` and `-multipartFormRequestWithMethod:path:parameters:constructingBodyWithBlock:` construct URLs from the path relative to the `-baseURL`, using `NSURL +URLWithString:relativeToURL:`. Below are a few examples of how `baseURL` and relative paths interact: - NSURL *baseURL = [NSURL URLWithString:@"http://example.com/v1/"]; - [NSURL URLWithString:@"foo" relativeToURL:baseURL]; // http://example.com/v1/foo - [NSURL URLWithString:@"foo?bar=baz" relativeToURL:baseURL]; // http://example.com/v1/foo?bar=baz - [NSURL URLWithString:@"/foo" relativeToURL:baseURL]; // http://example.com/foo - [NSURL URLWithString:@"foo/" relativeToURL:baseURL]; // http://example.com/v1/foo - [NSURL URLWithString:@"/foo/" relativeToURL:baseURL]; // http://example.com/foo/ - [NSURL URLWithString:@"http://example2.com/" relativeToURL:baseURL]; // http://example2.com/ + NSURL *baseURL = [NSURL URLWithString:@"http://example.com/v1/"]; + [NSURL URLWithString:@"foo" relativeToURL:baseURL]; // http://example.com/v1/foo + [NSURL URLWithString:@"foo?bar=baz" relativeToURL:baseURL]; // http://example.com/v1/foo?bar=baz + [NSURL URLWithString:@"/foo" relativeToURL:baseURL]; // http://example.com/foo + [NSURL URLWithString:@"foo/" relativeToURL:baseURL]; // http://example.com/v1/foo + [NSURL URLWithString:@"/foo/" relativeToURL:baseURL]; // http://example.com/foo/ + [NSURL URLWithString:@"http://example2.com/" relativeToURL:baseURL]; // http://example2.com/ + + Also important to note is that a trailing slash will be added to any `baseURL` without one, which would otherwise cause unexpected behavior when constructing URLs using paths without a leading slash. + ## NSCoding / NSCopying Conformance + + `AFHTTPClient` conforms to the `NSCoding` and `NSCopying` protocols, allowing operations to be archived to disk, and copied in memory, respectively. There are a few minor caveats to keep in mind, however: + + - Archives and copies of HTTP clients will be initialized with an empty operation queue. + - NSCoding cannot serialize / deserialize block properties, so an archive of an HTTP client will not include any reachability callback block that may be set. */ -@interface AFHTTPClient : NSObject + +#ifdef _SYSTEMCONFIGURATION_H +typedef enum { + AFNetworkReachabilityStatusUnknown = -1, + AFNetworkReachabilityStatusNotReachable = 0, + AFNetworkReachabilityStatusReachableViaWWAN = 1, + AFNetworkReachabilityStatusReachableViaWiFi = 2, +} AFNetworkReachabilityStatus; +#endif + +typedef enum { + AFFormURLParameterEncoding, + AFJSONParameterEncoding, + AFPropertyListParameterEncoding, +} AFHTTPClientParameterEncoding; + +@protocol AFMultipartFormData; + +@interface AFHTTPClient : NSObject ///--------------------------------------- /// @name Accessing HTTP Client Properties ///--------------------------------------- /** - The url used as the base for paths specified in methods such as `getPath:parameteres:success:failure` + The url used as the base for paths specified in methods such as `getPath:parameters:success:failure` */ @property (readonly, nonatomic) NSURL *baseURL; @@ -156,7 +121,7 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete /** The reachability status from the device to the current `baseURL` of the `AFHTTPClient`. - @warning This property requires the `SystemConfiguration` framework. Add it in the active target's "Link Binary With Library" build phase, and add `#import ` to the header prefix of the project (Prefix.pch). + @warning This property requires the `SystemConfiguration` framework. Add it in the active target's "Link Binary With Library" build phase, and add `#import ` to the header prefix of the project (`Prefix.pch`). */ #ifdef _SYSTEMCONFIGURATION_H @property (readonly, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus; @@ -195,7 +160,7 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete @param block A block object to be executed when the network availability of the `baseURL` host changes.. This block has no return value and takes a single argument which represents the various reachability states from the device to the `baseURL`. - @warning This method requires the `SystemConfiguration` framework. Add it in the active target's "Link Binary With Library" build phase, and add `#import ` to the header prefix of the project (Prefix.pch). + @warning This method requires the `SystemConfiguration` framework. Add it in the active target's "Link Binary With Library" build phase, and add `#import ` to the header prefix of the project (`Prefix.pch`). */ #ifdef _SYSTEMCONFIGURATION_H - (void)setReachabilityStatusChangeBlock:(void (^)(AFNetworkReachabilityStatus status))block; @@ -208,22 +173,18 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete /** Attempts to register a subclass of `AFHTTPRequestOperation`, adding it to a chain to automatically generate request operations from a URL request. - @param The subclass of `AFHTTPRequestOperation` to register + @param operationClass The subclass of `AFHTTPRequestOperation` to register - @return `YES` if the registration is successful, `NO` otherwise. The only failure condition is if `operationClass` does is not a subclass of `AFHTTPRequestOperation`. + @return `YES` if the registration is successful, `NO` otherwise. The only failure condition is if `operationClass` is not a subclass of `AFHTTPRequestOperation`. @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 create an operation using `initWithURLRequest:` and do `setCompletionBlockWithSuccess: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 list. - - @see `AFHTTPClientOperation` */ - (BOOL)registerHTTPOperationClass:(Class)operationClass; /** - Unregisters the specified subclass of `AFHTTPRequestOperation`. - - @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. + Unregisters the specified subclass of `AFHTTPRequestOperation` from the chain of classes consulted when `-requestWithMethod:path:parameters` is called. + + @param operationClass The subclass of `AFHTTPRequestOperation` to register */ - (void)unregisterHTTPOperationClass:(Class)operationClass; @@ -317,7 +278,7 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete @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. This block has no return value and takes two arguments: the created request operation and the 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 two arguments:, the created request operation and the `NSError` object describing the network or parsing error that occurred. + @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 response data. This block has no return value and takes two arguments:, the created request operation and the `NSError` object describing the network or parsing error that occurred. */ - (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)request success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success @@ -338,7 +299,7 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete Cancels all operations in the HTTP client's operation queue whose URLs match the specified HTTP method and path. @param method The HTTP method to match for the cancelled requests, such as `GET`, `POST`, `PUT`, or `DELETE`. If `nil`, all request operations with URLs matching the path will be cancelled. - @param url The path to match for the cancelled requests. + @param path The path to match for the cancelled requests. */ - (void)cancelAllHTTPOperationsWithMethod:(NSString *)method path:(NSString *)path; @@ -356,7 +317,7 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete @discussion Operations are created by passing the specified `NSURLRequest` objects in `requests`, using `-HTTPRequestOperationWithRequest:success:failure:`, with `nil` for both the `success` and `failure` parameters. */ - (void)enqueueBatchOfHTTPRequestOperationsWithRequests:(NSArray *)requests - progressBlock:(void (^)(NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations))progressBlock + progressBlock:(void (^)(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations))progressBlock completionBlock:(void (^)(NSArray *operations))completionBlock; /** @@ -367,7 +328,7 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete @param completionBlock A block object to be executed upon the completion of all of the request operations in the batch. This block has no return value and takes a single argument: the batched request operations. */ - (void)enqueueBatchOfHTTPRequestOperations:(NSArray *)operations - progressBlock:(void (^)(NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations))progressBlock + progressBlock:(void (^)(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations))progressBlock completionBlock:(void (^)(NSArray *operations))completionBlock; ///--------------------------- @@ -380,9 +341,9 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete @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. This block has no return value and takes two arguments: the created request operation and the 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 two arguments:, the created request operation and the `NSError` object describing the network or parsing error that occurred. + @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 response data. This block has no return value and takes two arguments:, the created request operation and the `NSError` object describing the network or parsing error that occurred. - @see HTTPRequestOperationWithRequest:success:failure + @see -HTTPRequestOperationWithRequest:success:failure: */ - (void)getPath:(NSString *)path parameters:(NSDictionary *)parameters @@ -395,9 +356,9 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete @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. This block has no return value and takes two arguments: the created request operation and the 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 two arguments:, the created request operation and the `NSError` object describing the network or parsing error that occurred. + @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 response data. This block has no return value and takes two arguments:, the created request operation and the `NSError` object describing the network or parsing error that occurred. - @see HTTPRequestOperationWithRequest:success:failure + @see -HTTPRequestOperationWithRequest:success:failure: */ - (void)postPath:(NSString *)path parameters:(NSDictionary *)parameters @@ -410,9 +371,9 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete @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. This block has no return value and takes two arguments: the created request operation and the 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 two arguments:, the created request operation and the `NSError` object describing the network or parsing error that occurred. + @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 response data. This block has no return value and takes two arguments:, the created request operation and the `NSError` object describing the network or parsing error that occurred. - @see HTTPRequestOperationWithRequest:success:failure + @see -HTTPRequestOperationWithRequest:success:failure: */ - (void)putPath:(NSString *)path parameters:(NSDictionary *)parameters @@ -423,11 +384,11 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete 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 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. This block has no return value and takes two arguments: the created request operation and the 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 two arguments:, the created request operation and the `NSError` object describing the network or parsing error that occurred. + @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 response data. This block has no return value and takes two arguments:, the created request operation and the `NSError` object describing the network or parsing error that occurred. - @see HTTPRequestOperationWithRequest:success:failure + @see -HTTPRequestOperationWithRequest:success:failure: */ - (void)deletePath:(NSString *)path parameters:(NSDictionary *)parameters @@ -440,9 +401,9 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete @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. This block has no return value and takes two arguments: the created request operation and the 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 two arguments:, the created request operation and the `NSError` object describing the network or parsing error that occurred. + @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 response data. This block has no return value and takes two arguments:, the created request operation and the `NSError` object describing the network or parsing error that occurred. - @see HTTPRequestOperationWithRequest:success:failure + @see -HTTPRequestOperationWithRequest:success:failure: */ - (void)patchPath:(NSString *)path parameters:(NSDictionary *)parameters @@ -450,12 +411,99 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure; @end -#pragma mark - +///---------------- +/// @name Constants +///---------------- + +/** + ### Network Reachability + + The following constants are provided by `AFHTTPClient` as possible network reachability statuses. + + enum { + AFNetworkReachabilityStatusUnknown, + AFNetworkReachabilityStatusNotReachable, + AFNetworkReachabilityStatusReachableViaWWAN, + AFNetworkReachabilityStatusReachableViaWiFi, + } + + `AFNetworkReachabilityStatusUnknown` + The `baseURL` host reachability is not known. + + `AFNetworkReachabilityStatusNotReachable` + The `baseURL` host cannot be reached. + + `AFNetworkReachabilityStatusReachableViaWWAN` + The `baseURL` host can be reached via a cellular connection, such as EDGE or GPRS. + + `AFNetworkReachabilityStatusReachableViaWiFi` + The `baseURL` host can be reached via a Wi-Fi connection. + + ### Keys for Notification UserInfo Dictionary + + Strings that are used as keys in a `userInfo` dictionary in a network reachability status change notification. + + `AFNetworkingReachabilityNotificationStatusItem` + A key in the userInfo dictionary in a `AFNetworkingReachabilityDidChangeNotification` notification. + The corresponding value is an `NSNumber` object representing the `AFNetworkReachabilityStatus` value for the current reachability status. + + ### Parameter Encoding + + The following constants are provided by `AFHTTPClient` as possible methods for serializing parameters into query string or message body values. + + enum { + AFFormURLParameterEncoding, + AFJSONParameterEncoding, + AFPropertyListParameterEncoding, + } + + `AFFormURLParameterEncoding` + Parameters are encoded into field/key pairs in the URL query string for `GET` `HEAD` and `DELETE` requests, and in the message body otherwise. + + `AFJSONParameterEncoding` + Parameters are encoded into JSON in the message body. + + `AFPropertyListParameterEncoding` + Parameters are encoded into a property list in the message body. + */ + +///---------------- +/// @name Functions +///---------------- /** - The `AFMultipartFormData` protocol defines the methods supported by the parameter in the block argument of `multipartFormRequestWithMethod:path:parameters:constructingBodyWithBlock:`. + Returns a query string constructed by a set of parameters, using the specified encoding. + + @param parameters The parameters used to construct the query string + @param encoding The encoding to use in constructing the query string. If you are uncertain of the correct encoding, you should use UTF-8 (`NSUTF8StringEncoding`), which is the encoding designated by RFC 3986 as the correct encoding for use in URLs. + + @discussion Query strings are constructed by collecting each key-value pair, percent escaping a string representation of the key-value pair, and then joining the pairs with "&". - @see `AFHTTPClient -multipartFormRequestWithMethod:path:parameters:constructingBodyWithBlock:` + If a query string pair has a an `NSArray` for its value, each member of the array will be represented in the format `field[]=value1&field[]value2`. Otherwise, the pair will be formatted as "field=value". String representations of both keys and values are derived using the `-description` method. The constructed query string does not include the ? character used to delimit the query component. + + @return A percent-escaped query string + */ +extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *parameters, NSStringEncoding encoding); + +///-------------------- +/// @name Notifications +///-------------------- + +/** + Posted when network reachability changes. + This notification assigns no notification object. The `userInfo` dictionary contains an `NSNumber` object under the `AFNetworkingReachabilityNotificationStatusItem` key, representing the `AFNetworkReachabilityStatus` value for the current network reachability. + + @warning In order for network reachability to be monitored, include the `SystemConfiguration` framework in the active target's "Link Binary With Library" build phase, and add `#import ` to the header prefix of the project (`Prefix.pch`). + */ +#ifdef _SYSTEMCONFIGURATION_H +extern NSString * const AFNetworkingReachabilityDidChangeNotification; +extern NSString * const AFNetworkingReachabilityNotificationStatusItem; +#endif + +#pragma mark - + +/** + The `AFMultipartFormData` protocol defines the methods supported by the parameter in the block argument of `-multipartFormRequestWithMethod:path:parameters:constructingBodyWithBlock:`. */ @protocol AFMultipartFormData @@ -465,7 +513,8 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete @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; +- (void)appendPartWithHeaders:(NSDictionary *)headers + body:(NSData *)body; /** Appends the HTTP headers `Content-Disposition: form-data; name=#{name}"`, followed by the encoded data and the multipart form boundary. @@ -473,17 +522,21 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete @param data The data to be encoded and appended to the form data. @param name The name to be associated with the specified data. This parameter must not be `nil`. */ -- (void)appendPartWithFormData:(NSData *)data name:(NSString *)name; +- (void)appendPartWithFormData:(NSData *)data + name:(NSString *)name; /** Appends the HTTP header `Content-Disposition: file; filename=#{filename}; name=#{name}"` and `Content-Type: #{mimeType}`, followed by the encoded file 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. This parameter must not be `nil`. + @param fileName The filename to be associated with the specified 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 specified data. This parameter must not be `nil`. */ -- (void)appendPartWithFileData:(NSData *)data name:(NSString *)name fileName:(NSString *)fileName mimeType:(NSString *)mimeType; +- (void)appendPartWithFileData:(NSData *)data + name:(NSString *)name + fileName:(NSString *)fileName + mimeType:(NSString *)mimeType; /** Appends the HTTP header `Content-Disposition: file; filename=#{generated filename}; name=#{name}"` and `Content-Type: #{generated mimeType}`, followed by the encoded file data and the multipart form boundary. @@ -496,7 +549,9 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete @discussion The filename and MIME type for this data in the form will be automatically generated, using `NSURLResponse` `-suggestedFilename` and `-MIMEType`, respectively. */ -- (BOOL)appendPartWithFileURL:(NSURL *)fileURL name:(NSString *)name error:(NSError **)error; +- (BOOL)appendPartWithFileURL:(NSURL *)fileURL + name:(NSString *)name + error:(NSError **)error; /** Appends encoded data to the form data. @@ -513,4 +568,3 @@ extern NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *paramete - (void)appendString:(NSString *)string; @end - diff --git a/AFNetworking/AFHTTPClient.m b/AFNetworking/AFHTTPClient.m index 933ff110e2..bcc2bbc0f9 100644 --- a/AFNetworking/AFHTTPClient.m +++ b/AFNetworking/AFHTTPClient.m @@ -1,17 +1,17 @@ // 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 @@ -40,11 +40,9 @@ #import #endif -NSString * const AFNetworkingReachabilityDidChangeNotification = @"com.alamofire.networking.reachability.change"; - @interface AFMultipartFormData : NSObject -- (id)initWithURLRequest:(NSMutableURLRequest *)request +- (id)initWithURLRequest:(NSMutableURLRequest *)request stringEncoding:(NSStringEncoding)encoding; - (NSMutableURLRequest *)requestByFinalizingMultipartFormData; @@ -54,6 +52,9 @@ - (NSMutableURLRequest *)requestByFinalizingMultipartFormData; #pragma mark - #ifdef _SYSTEMCONFIGURATION_H +NSString * const AFNetworkingReachabilityDidChangeNotification = @"com.alamofire.networking.reachability.change"; +NSString * const AFNetworkingReachabilityNotificationStatusItem = @"AFNetworkingReachabilityNotificationStatusItem"; + typedef SCNetworkReachabilityRef AFNetworkReachabilityRef; typedef void (^AFNetworkReachabilityStatusBlock)(AFNetworkReachabilityStatus status); #else @@ -62,8 +63,6 @@ - (NSMutableURLRequest *)requestByFinalizingMultipartFormData; typedef void (^AFCompletionBlock)(void); -static NSUInteger const kAFHTTPClientDefaultMaxConcurrentOperationCount = 4; - static NSString * AFBase64EncodedStringFromString(NSString *string) { NSData *data = [NSData dataWithBytes:[string UTF8String] length:[string lengthOfBytesUsingEncoding:NSUTF8StringEncoding]]; NSUInteger length = [data length]; @@ -77,7 +76,7 @@ - (NSMutableURLRequest *)requestByFinalizingMultipartFormData; for (NSUInteger j = i; j < (i + 3); j++) { value <<= 8; if (j < length) { - value |= (0xFF & input[j]); + value |= (0xFF & input[j]); } } @@ -93,91 +92,78 @@ - (NSMutableURLRequest *)requestByFinalizingMultipartFormData; return [[NSString alloc] initWithData:mutableData encoding:NSASCIIStringEncoding]; } -NSString * AFURLEncodedStringFromStringWithEncoding(NSString *string, NSStringEncoding encoding) { - static NSString * const kAFLegalCharactersToBeEscaped = @"?!@#$^&%*+=,:;'\"`<>()[]{}/\\|~ "; +static NSString * AFPercentEscapedQueryStringPairMemberFromStringWithEncoding(NSString *string, NSStringEncoding encoding) { + // Escape characters that are legal in URIs, but have unintentional semantic significance when used in a query string parameter + static NSString * const kAFLegalCharactersToBeEscaped = @":/.?&=;+!@$()~"; return (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (__bridge CFStringRef)string, NULL, (__bridge CFStringRef)kAFLegalCharactersToBeEscaped, CFStringConvertNSStringEncodingToEncoding(encoding)); } #pragma mark - -@interface AFQueryStringComponent : NSObject +@interface AFQueryStringPair : NSObject +@property (readwrite, nonatomic, retain) id field; +@property (readwrite, nonatomic, retain) id value; -@property (readwrite, nonatomic) id key; -@property (readwrite, nonatomic) id value; - -- (id)initWithKey:(id)key value:(id)value; +- (id)initWithField:(id)field value:(id)value; - (NSString *)URLEncodedStringValueWithEncoding:(NSStringEncoding)stringEncoding; @end -@implementation AFQueryStringComponent -@synthesize key = _key; +@implementation AFQueryStringPair +@synthesize field = _field; @synthesize value = _value; -- (id)initWithKey:(id)key value:(id)value { +- (id)initWithField:(id)field value:(id)value { self = [super init]; if (!self) { return nil; } - - _key = key; - _value = value; + + self.field = field; + self.value = value; return self; } - (NSString *)URLEncodedStringValueWithEncoding:(NSStringEncoding)stringEncoding { - return [NSString stringWithFormat:@"%@=%@", self.key, AFURLEncodedStringFromStringWithEncoding([self.value description], stringEncoding)]; + return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedQueryStringPairMemberFromStringWithEncoding(self.field, stringEncoding), AFPercentEscapedQueryStringPairMemberFromStringWithEncoding([self.value description], stringEncoding)]; } @end #pragma mark - -extern NSArray * AFQueryStringComponentsFromKeyAndValue(NSString *key, id value); -extern NSArray * AFQueryStringComponentsFromKeyAndDictionaryValue(NSString *key, NSDictionary *value); -extern NSArray * AFQueryStringComponentsFromKeyAndArrayValue(NSString *key, NSArray *value); +extern NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary); +extern NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value); NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *parameters, NSStringEncoding stringEncoding) { - NSMutableArray *mutableComponents = [NSMutableArray array]; - for (AFQueryStringComponent *component in AFQueryStringComponentsFromKeyAndValue(nil, parameters)) { - [mutableComponents addObject:[component URLEncodedStringValueWithEncoding:stringEncoding]]; + NSMutableArray *mutablePairs = [NSMutableArray array]; + for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) { + [mutablePairs addObject:[pair URLEncodedStringValueWithEncoding:stringEncoding]]; } - return [mutableComponents componentsJoinedByString:@"&"]; + return [mutablePairs componentsJoinedByString:@"&"]; } -NSArray * AFQueryStringComponentsFromKeyAndValue(NSString *key, id value) { +NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) { + return AFQueryStringPairsFromKeyAndValue(nil, dictionary); +} + +NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) { NSMutableArray *mutableQueryStringComponents = [NSMutableArray array]; if([value isKindOfClass:[NSDictionary class]]) { - [mutableQueryStringComponents addObjectsFromArray:AFQueryStringComponentsFromKeyAndDictionaryValue(key, value)]; + [value enumerateKeysAndObjectsUsingBlock:^(id nestedKey, id nestedValue, BOOL *stop) { + [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)]; + }]; } else if([value isKindOfClass:[NSArray class]]) { - [mutableQueryStringComponents addObjectsFromArray:AFQueryStringComponentsFromKeyAndArrayValue(key, value)]; + [value enumerateObjectsUsingBlock:^(id nestedValue, NSUInteger idx, BOOL *stop) { + [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)]; + }]; } else { - [mutableQueryStringComponents addObject:[[AFQueryStringComponent alloc] initWithKey:key value:value]]; - } - - return mutableQueryStringComponents; -} - -NSArray * AFQueryStringComponentsFromKeyAndDictionaryValue(NSString *key, NSDictionary *value){ - NSMutableArray *mutableQueryStringComponents = [NSMutableArray array]; - - [value enumerateKeysAndObjectsUsingBlock:^(id nestedKey, id nestedValue, BOOL *stop) { - [mutableQueryStringComponents addObjectsFromArray:AFQueryStringComponentsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)]; - }]; - - return mutableQueryStringComponents; -} - -NSArray * AFQueryStringComponentsFromKeyAndArrayValue(NSString *key, NSArray *value) { - NSMutableArray *mutableQueryStringComponents = [NSMutableArray array]; - - [value enumerateObjectsUsingBlock:^(id nestedValue, NSUInteger idx, BOOL *stop) { - [mutableQueryStringComponents addObjectsFromArray:AFQueryStringComponentsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)]; - }]; + [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]]; + } return mutableQueryStringComponents; } @@ -245,6 +231,11 @@ - (id)initWithBaseURL:(NSURL *)url { return nil; } + // Ensure terminal slash for baseURL path, so that NSURL +URLWithString:relativeToURL: works as expected + if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) { + url = [url URLByAppendingPathComponent:@""]; + } + self.baseURL = url; self.stringEncoding = NSUTF8StringEncoding; @@ -253,19 +244,16 @@ - (id)initWithBaseURL:(NSURL *)url { self.registeredHTTPOperationClassNames = [NSMutableArray array]; 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]]; + // 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]]; #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)]]; + [self setDefaultHeader:@"User-Agent" value:[NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleExecutableKey] ?: [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleIdentifierKey], (__bridge id)CFBundleGetValueForInfoDictionaryKey(CFBundleGetMainBundle(), kCFBundleVersionKey) ?: [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], ([[UIScreen mainScreen] respondsToSelector:@selector(scale)] ? [[UIScreen mainScreen] scale] : 1.0f)]]; #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"]]; + [self setDefaultHeader:@"User-Agent" value:[NSString stringWithFormat:@"%@/%@ (Mac OS X %@)", [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleExecutableKey] ?: [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleIdentifierKey], [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"] ?: [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleVersionKey], [[NSProcessInfo processInfo] operatingSystemVersionString]]]; #endif #ifdef _SYSTEMCONFIGURATION_H @@ -274,7 +262,7 @@ - (id)initWithBaseURL:(NSURL *)url { #endif self.operationQueue = [[NSOperationQueue alloc] init]; - [self.operationQueue setMaxConcurrentOperationCount:kAFHTTPClientDefaultMaxConcurrentOperationCount]; + [self.operationQueue setMaxConcurrentOperationCount:NSOperationQueueDefaultMaxConcurrentOperationCount]; return self; } @@ -320,14 +308,14 @@ static AFNetworkReachabilityStatus AFNetworkReachabilityStatusForFlags(SCNetwork return status; } -static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) { +static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) { AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags); AFNetworkReachabilityStatusBlock block = (__bridge AFNetworkReachabilityStatusBlock)info; if (block) { block(status); } - [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingReachabilityDidChangeNotification object:[NSNumber numberWithInt:status]]; + [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingReachabilityDidChangeNotification object:nil userInfo:[NSDictionary dictionaryWithObject:[NSNumber numberWithInteger:status] forKey:AFNetworkingReachabilityNotificationStatusItem]]; } static const void * AFNetworkReachabilityRetainCallback(const void *info) { @@ -425,10 +413,10 @@ - (void)clearAuthorizationHeader { #pragma mark - -- (NSMutableURLRequest *)requestWithMethod:(NSString *)method - path:(NSString *)path - parameters:(NSDictionary *)parameters -{ +- (NSMutableURLRequest *)requestWithMethod:(NSString *)method + path:(NSString *)path + parameters:(NSDictionary *)parameters +{ NSURL *url = [NSURL URLWithString:path relativeToURL:self.baseURL]; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url]; [request setHTTPMethod:method]; @@ -439,7 +427,7 @@ - (NSMutableURLRequest *)requestWithMethod:(NSString *)method [request setHTTPShouldUsePipelining:YES]; } - if (parameters) { + if (parameters) { if ([method isEqualToString:@"GET"] || [method isEqualToString:@"HEAD"] || [method isEqualToString:@"DELETE"]) { url = [NSURL URLWithString:[[url absoluteString] stringByAppendingFormat:[path rangeOfString:@"?"].location == NSNotFound ? @"?%@" : @"&%@", AFQueryStringFromParametersWithEncoding(parameters, self.stringEncoding)]]; [request setURL:url]; @@ -474,16 +462,16 @@ - (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method __block AFMultipartFormData *formData = [[AFMultipartFormData alloc] initWithURLRequest:request stringEncoding:self.stringEncoding]; if (parameters) { - for (AFQueryStringComponent *component in AFQueryStringComponentsFromKeyAndValue(nil, parameters)) { + for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) { NSData *data = nil; - if ([component.value isKindOfClass:[NSData class]]) { - data = component.value; + if ([pair.value isKindOfClass:[NSData class]]) { + data = pair.value; } else { - data = [[component.value description] dataUsingEncoding:self.stringEncoding]; + data = [[pair.value description] dataUsingEncoding:self.stringEncoding]; } if (data) { - [formData appendPartWithFormData:data name:[component.key description]]; + [formData appendPartWithFormData:data name:[pair.field description]]; } } } @@ -495,7 +483,7 @@ - (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method return [formData requestByFinalizingMultipartFormData]; } -- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest +- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)urlRequest success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure { @@ -536,8 +524,8 @@ - (void)cancelAllHTTPOperationsWithMethod:(NSString *)method path:(NSString *)pa } } -- (void)enqueueBatchOfHTTPRequestOperationsWithRequests:(NSArray *)requests - progressBlock:(void (^)(NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations))progressBlock +- (void)enqueueBatchOfHTTPRequestOperationsWithRequests:(NSArray *)requests + progressBlock:(void (^)(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations))progressBlock completionBlock:(void (^)(NSArray *operations))completionBlock { NSMutableArray *mutableOperations = [NSMutableArray array]; @@ -549,8 +537,8 @@ - (void)enqueueBatchOfHTTPRequestOperationsWithRequests:(NSArray *)requests [self enqueueBatchOfHTTPRequestOperations:mutableOperations progressBlock:progressBlock completionBlock:completionBlock]; } -- (void)enqueueBatchOfHTTPRequestOperations:(NSArray *)operations - progressBlock:(void (^)(NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations))progressBlock +- (void)enqueueBatchOfHTTPRequestOperations:(NSArray *)operations + progressBlock:(void (^)(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations))progressBlock completionBlock:(void (^)(NSArray *operations))completionBlock { __block dispatch_group_t dispatchGroup = dispatch_group_create(); @@ -563,8 +551,6 @@ - (void)enqueueBatchOfHTTPRequestOperations:(NSArray *)operations dispatch_release(dispatchGroup); }]; - NSPredicate *finishedOperationPredicate = [NSPredicate predicateWithFormat:@"isFinished == YES"]; - for (AFHTTPRequestOperation *operation in operations) { AFCompletionBlock originalCompletionBlock = [operation.completionBlock copy]; operation.completionBlock = ^{ @@ -574,8 +560,15 @@ - (void)enqueueBatchOfHTTPRequestOperations:(NSArray *)operations originalCompletionBlock(); } + __block NSUInteger numberOfFinishedOperations = 0; + [operations enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { + if ([(NSOperation *)obj isFinished]) { + numberOfFinishedOperations++; + } + }]; + if (progressBlock) { - progressBlock([[operations filteredArrayUsingPredicate:finishedOperationPredicate] count], [operations count]); + progressBlock(numberOfFinishedOperations, [operations count]); } dispatch_group_leave(dispatchGroup); @@ -592,8 +585,8 @@ - (void)enqueueBatchOfHTTPRequestOperations:(NSArray *)operations #pragma mark - -- (void)getPath:(NSString *)path - parameters:(NSDictionary *)parameters +- (void)getPath:(NSString *)path + parameters:(NSDictionary *)parameters success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure { @@ -602,8 +595,8 @@ - (void)getPath:(NSString *)path [self enqueueHTTPRequestOperation:operation]; } -- (void)postPath:(NSString *)path - parameters:(NSDictionary *)parameters +- (void)postPath:(NSString *)path + parameters:(NSDictionary *)parameters success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure { @@ -612,8 +605,8 @@ - (void)postPath:(NSString *)path [self enqueueHTTPRequestOperation:operation]; } -- (void)putPath:(NSString *)path - parameters:(NSDictionary *)parameters +- (void)putPath:(NSString *)path + parameters:(NSDictionary *)parameters success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure { @@ -622,8 +615,8 @@ - (void)putPath:(NSString *)path [self enqueueHTTPRequestOperation:operation]; } -- (void)deletePath:(NSString *)path - parameters:(NSDictionary *)parameters +- (void)deletePath:(NSString *)path + parameters:(NSDictionary *)parameters success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure { @@ -632,8 +625,8 @@ - (void)deletePath:(NSString *)path [self enqueueHTTPRequestOperation:operation]; } -- (void)patchPath:(NSString *)path - parameters:(NSDictionary *)parameters +- (void)patchPath:(NSString *)path + parameters:(NSDictionary *)parameters success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure { @@ -642,6 +635,47 @@ - (void)patchPath:(NSString *)path [self enqueueHTTPRequestOperation:operation]; } +#pragma mark - NSCoding + +- (id)initWithCoder:(NSCoder *)aDecoder { + NSURL *baseURL = [aDecoder decodeObjectForKey:@"baseURL"]; + + self = [self initWithBaseURL:baseURL]; + if (!self) { + return nil; + } + + self.stringEncoding = [aDecoder decodeIntegerForKey:@"stringEncoding"]; + self.parameterEncoding = [aDecoder decodeIntegerForKey:@"parameterEncoding"]; + self.registeredHTTPOperationClassNames = [aDecoder decodeObjectForKey:@"registeredHTTPOperationClassNames"]; + self.defaultHeaders = [aDecoder decodeObjectForKey:@"defaultHeaders"]; + + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [aCoder encodeObject:self.baseURL forKey:@"baseURL"]; + [aCoder encodeInteger:self.stringEncoding forKey:@"stringEncoding"]; + [aCoder encodeInteger:self.parameterEncoding forKey:@"parameterEncoding"]; + [aCoder encodeObject:self.registeredHTTPOperationClassNames forKey:@"registeredHTTPOperationClassNames"]; + [aCoder encodeObject:self.defaultHeaders forKey:@"defaultHeaders"]; +} + +#pragma mark - NSCopying + +- (id)copyWithZone:(NSZone *)zone { + AFHTTPClient *HTTPClient = [[[self class] allocWithZone:zone] initWithBaseURL:self.baseURL]; + + HTTPClient.stringEncoding = self.stringEncoding; + HTTPClient.parameterEncoding = self.parameterEncoding; + HTTPClient.registeredHTTPOperationClassNames = [self.registeredHTTPOperationClassNames copyWithZone:zone]; + HTTPClient.defaultHeaders = [self.defaultHeaders copyWithZone:zone]; +#ifdef _SYSTEMCONFIGURATION_H + HTTPClient.networkReachabilityStatusBlock = self.networkReachabilityStatusBlock; +#endif + return HTTPClient; +} + @end #pragma mark - @@ -656,7 +690,7 @@ - (void)patchPath:(NSString *)path NSError *error = nil; if(![[NSFileManager defaultManager] createDirectoryAtPath:multipartTemporaryFilePath withIntermediateDirectories:YES attributes:nil error:&error]) { - NSLog(@"Failed to create multipary temporary file directory at %@", multipartTemporaryFilePath); + NSLog(@"Failed to create multipart temporary file directory at %@", multipartTemporaryFilePath); } }); @@ -682,8 +716,8 @@ - (void)patchPath:(NSString *)path @interface AFMultipartFormData () @property (readwrite, nonatomic) NSMutableURLRequest *request; @property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding; -@property (readwrite, nonatomic) NSOutputStream *outputStream; -@property (readwrite, nonatomic, copy) NSString *temporaryFilePath; +@property (readwrite, nonatomic, strong) NSOutputStream *outputStream; +@property (readwrite, nonatomic, copy) NSString *temporaryFilePath; @end @implementation AFMultipartFormData @@ -692,8 +726,8 @@ @implementation AFMultipartFormData @synthesize outputStream = _outputStream; @synthesize temporaryFilePath = _temporaryFilePath; -- (id)initWithURLRequest:(NSMutableURLRequest *)request - stringEncoding:(NSStringEncoding)encoding +- (id)initWithURLRequest:(NSMutableURLRequest *)request + stringEncoding:(NSStringEncoding)encoding { self = [super init]; if (!self) { @@ -703,7 +737,7 @@ - (id)initWithURLRequest:(NSMutableURLRequest *)request self.request = request; self.stringEncoding = encoding; - self.temporaryFilePath = [AFMultipartTemporaryFileDirectoryPath() stringByAppendingPathComponent:[NSString stringWithFormat:@"%u", [[self.request URL] hash]]]; + self.temporaryFilePath = [AFMultipartTemporaryFileDirectoryPath() stringByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]]; self.outputStream = [NSOutputStream outputStreamToFileAtPath:self.temporaryFilePath append:NO]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; @@ -728,7 +762,7 @@ - (NSMutableURLRequest *)requestByFinalizingMultipartFormData { return self.request; } - + [self appendData:[AFMultipartFormFinalBoundary() dataUsingEncoding:self.stringEncoding]]; [self.request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", kAFMultipartFormBoundary] forHTTPHeaderField:@"Content-Type"]; @@ -750,8 +784,8 @@ - (void)appendBoundary { } } -- (void)appendPartWithHeaders:(NSDictionary *)headers - body:(NSData *)body +- (void)appendPartWithHeaders:(NSDictionary *)headers + body:(NSData *)body { [self appendBoundary]; @@ -763,8 +797,8 @@ - (void)appendPartWithHeaders:(NSDictionary *)headers [self appendData:body]; } -- (void)appendPartWithFormData:(NSData *)data - name:(NSString *)name +- (void)appendPartWithFormData:(NSData *)data + name:(NSString *)name { NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary]; [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"", name] forKey:@"Content-Disposition"]; @@ -772,11 +806,11 @@ - (void)appendPartWithFormData:(NSData *)data [self appendPartWithHeaders:mutableHeaders body:data]; } -- (void)appendPartWithFileData:(NSData *)data - name:(NSString *)name - fileName:(NSString *)fileName +- (void)appendPartWithFileData:(NSData *)data + name:(NSString *)name + fileName:(NSString *)fileName mimeType:(NSString *)mimeType -{ +{ NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary]; [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"]; [mutableHeaders setValue:mimeType forKey:@"Content-Type"]; @@ -784,16 +818,16 @@ - (void)appendPartWithFileData:(NSData *)data [self appendPartWithHeaders:mutableHeaders body:data]; } -- (BOOL)appendPartWithFileURL:(NSURL *)fileURL - name:(NSString *)name - error:(NSError **)error +- (BOOL)appendPartWithFileURL:(NSURL *)fileURL + name:(NSString *)name + error:(NSError **)error { if (![fileURL isFileURL]) { NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; [userInfo setValue:fileURL forKey:NSURLErrorFailingURLErrorKey]; [userInfo setValue:NSLocalizedString(@"Expected URL to be a file URL", nil) forKey:NSLocalizedFailureReasonErrorKey]; if (error != NULL) { - *error = [[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorBadURL userInfo:userInfo]; + *error = [[NSError alloc] initWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:userInfo]; } return NO; @@ -822,7 +856,7 @@ - (void)appendData:(NSData *)data { if ([data length] == 0) { return; } - + if ([self.outputStream hasSpaceAvailable]) { const uint8_t *dataBuffer = (uint8_t *) [data bytes]; [self.outputStream write:&dataBuffer[0] maxLength:[data length]]; diff --git a/AFNetworking/AFHTTPRequestOperation.h b/AFNetworking/AFHTTPRequestOperation.h index 52de1f750b..b7207f1369 100644 --- a/AFNetworking/AFHTTPRequestOperation.h +++ b/AFNetworking/AFHTTPRequestOperation.h @@ -23,13 +23,6 @@ #import #import "AFURLConnectionOperation.h" -/** - Returns a set of MIME types detected in an HTTP `Accept` or `Content-Type` header. - */ -extern NSSet * AFContentTypesFromHTTPHeader(NSString *string); - -extern NSString * AFCreateIncompleteDownloadDirectoryPath(void); - /** `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. */ @@ -44,30 +37,6 @@ extern NSString * AFCreateIncompleteDownloadDirectoryPath(void); */ @property (readonly, nonatomic, strong) NSHTTPURLResponse *response; -/** - Set a target file for the response, will stream directly into this destination. - Defaults to nil, which will use a memory stream. Will create a new outputStream on change. - - Note: Changing this while the request is not in ready state will be ignored. - */ -@property (nonatomic, copy) NSString *responseFilePath; - - -/** - Expected total length. This is different than expectedContentLength if the file is resumed. - On regular requests, this is equal to self.response.expectedContentLength unless we resume a request. - - Note: this can also be -1 if the file size is not sent (*) - */ -@property (assign, readonly) long long totalContentLength; - -/** - Indicator for the file offset on partial/resumed downloads. - This is greater than zero if the file download is resumed. - */ -@property (assign, readonly) long long offsetContentLength; - - ///---------------------------------------------------------- /// @name Managing And Checking For Acceptable HTTP Responses ///---------------------------------------------------------- @@ -85,16 +54,16 @@ extern NSString * AFCreateIncompleteDownloadDirectoryPath(void); /** The callback dispatch queue on success. If `NULL` (default), the main queue is used. */ -@property (nonatomic) dispatch_queue_t successCallbackQueue; +@property (nonatomic, assign) dispatch_queue_t successCallbackQueue; /** The callback dispatch queue on failure. If `NULL` (default), the main queue is used. */ -@property (nonatomic) dispatch_queue_t failureCallbackQueue; +@property (nonatomic, assign) dispatch_queue_t failureCallbackQueue; -///------------------------------------------------------------- -/// @name Managing Accceptable HTTP Status Codes & Content Types -///------------------------------------------------------------- +///------------------------------------------------------------ +/// @name Managing Acceptable HTTP Status Codes & Content Types +///------------------------------------------------------------ /** Returns an `NSIndexSet` object containing the ranges of acceptable HTTP status codes. When non-`nil`, the operation will set the `error` property to an error in `AFErrorDomain`. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html @@ -104,7 +73,7 @@ extern NSString * AFCreateIncompleteDownloadDirectoryPath(void); + (NSIndexSet *)acceptableStatusCodes; /** - Adds status codes to the set of acceptable HTTP status codes returned by `+acceptableStatusCodes` in subsequent calls by this class and its descendents. + Adds status codes to the set of acceptable HTTP status codes returned by `+acceptableStatusCodes` in subsequent calls by this class and its descendants. @param statusCodes The status codes to be added to the set of acceptable HTTP status codes */ @@ -118,7 +87,7 @@ extern NSString * AFCreateIncompleteDownloadDirectoryPath(void); + (NSSet *)acceptableContentTypes; /** - Adds content types to the set of acceptable MIME types returned by `+acceptableContentTypes` in subsequent calls by this class and its descendents. + Adds content types to the set of acceptable MIME types returned by `+acceptableContentTypes` in subsequent calls by this class and its descendants. @param contentTypes The content types to be added to the set of acceptable MIME types */ @@ -144,7 +113,7 @@ extern NSString * AFCreateIncompleteDownloadDirectoryPath(void); Sets the `completionBlock` property with a block that executes either the specified success or failure block, depending on the state of the request on completion. If `error` returns a value, which can be caused by an unacceptable status code or content type, then `failure` is executed. Otherwise, `success` is executed. @param success The block to be executed on the completion of a successful request. This block has no return value and takes two arguments: the receiver operation and the object constructed from the response data of the request. - @param failure The block to be executed on the completion of an unsuccessful request. This block has no return value and takes two arguments: the receiver operation and the error that occured during the request. + @param failure The block to be executed on the completion of an unsuccessful request. This block has no return value and takes two arguments: the receiver operation and the error that occurred during the request. @discussion This method should be overridden in subclasses in order to specify the response object passed into the success block. */ @@ -152,3 +121,13 @@ extern NSString * AFCreateIncompleteDownloadDirectoryPath(void); failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure; @end + +///---------------- +/// @name Functions +///---------------- + +/** + Returns a set of MIME types detected in an HTTP `Accept` or `Content-Type` header. + */ +extern NSSet * AFContentTypesFromHTTPHeader(NSString *string); + diff --git a/AFNetworking/AFHTTPRequestOperation.m b/AFNetworking/AFHTTPRequestOperation.m index dd3fa40f30..398a9b8415 100644 --- a/AFNetworking/AFHTTPRequestOperation.m +++ b/AFNetworking/AFHTTPRequestOperation.m @@ -30,8 +30,6 @@ #define AF_CAST_TO_BLOCK __bridge void * #endif -NSString * const kAFNetworkingIncompleteDownloadDirectoryName = @"Incomplete"; - NSSet * AFContentTypesFromHTTPHeader(NSString *string) { static NSCharacterSet *_skippedCharacterSet = nil; static dispatch_once_t onceToken; @@ -83,11 +81,11 @@ static void AFSwizzleClassMethodWithClassAndSelectorUsingBlock(Class klass, SEL } if (range.length == 1) { - [string appendFormat:@"%u", range.location]; + [string appendFormat:@"%lu", (long)range.location]; } else { NSUInteger firstIndex = range.location; NSUInteger lastIndex = firstIndex + range.length - 1; - [string appendFormat:@"%u-%u", firstIndex, lastIndex]; + [string appendFormat:@"%lu-%lu", (long)firstIndex, (long)lastIndex]; } range.location = nextIndex; @@ -97,23 +95,6 @@ static void AFSwizzleClassMethodWithClassAndSelectorUsingBlock(Class klass, SEL return string; } -NSString * AFCreateIncompleteDownloadDirectoryPath(void) { - static NSString *incompleteDownloadPath; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - NSString *tempDirectory = NSTemporaryDirectory(); - incompleteDownloadPath = [tempDirectory stringByAppendingPathComponent:kAFNetworkingIncompleteDownloadDirectoryName]; - - NSError *error = nil; - NSFileManager *fileMan = [[NSFileManager alloc] init]; - if(![fileMan createDirectoryAtPath:incompleteDownloadPath withIntermediateDirectories:YES attributes:nil error:&error]) { - NSLog(@"Failed to create incomplete downloads directory at %@", incompleteDownloadPath); - } - }); - - return incompleteDownloadPath; -} - #pragma mark - @interface AFHTTPRequestOperation () @@ -126,7 +107,6 @@ @interface AFHTTPRequestOperation () @implementation AFHTTPRequestOperation @synthesize HTTPError = _HTTPError; -@synthesize responseFilePath = _responseFilePath; @synthesize successCallbackQueue = _successCallbackQueue; @synthesize failureCallbackQueue = _failureCallbackQueue; @synthesize totalContentLength = _totalContentLength; @@ -148,18 +128,24 @@ - (void)dealloc { - (NSError *)error { if (self.response && !self.HTTPError) { - if (![self hasAcceptableStatusCode]) { + if (![self hasAcceptableStatusCode] || ![self hasAcceptableContentType]) { NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; - [userInfo setValue:[NSString stringWithFormat:NSLocalizedString(@"Expected status code in (%@), got %d", nil), AFStringFromIndexSet([[self class] acceptableStatusCodes]), [self.response statusCode]] forKey:NSLocalizedDescriptionKey]; + [userInfo setValue:self.responseString forKey:NSLocalizedRecoverySuggestionErrorKey]; [userInfo setValue:[self.request URL] forKey:NSURLErrorFailingURLErrorKey]; + [userInfo setValue:self.request forKey:AFNetworkingOperationFailingURLRequestErrorKey]; + [userInfo setValue:self.response forKey:AFNetworkingOperationFailingURLResponseErrorKey]; - self.HTTPError = [[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorBadServerResponse userInfo:userInfo]; - } else if ([self.responseData length] > 0 && ![self hasAcceptableContentType]) { // Don't invalidate content type if there is no content - NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; - [userInfo setValue:[NSString stringWithFormat:NSLocalizedString(@"Expected content type %@, got %@", nil), [[self class] acceptableContentTypes], [self.response MIMEType]] forKey:NSLocalizedDescriptionKey]; - [userInfo setValue:[self.request URL] forKey:NSURLErrorFailingURLErrorKey]; - - self.HTTPError = [[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:userInfo]; + if (![self hasAcceptableStatusCode]) { + NSUInteger statusCode = ([self.response isKindOfClass:[NSHTTPURLResponse class]]) ? (NSUInteger)[self.response statusCode] : 200; + [userInfo setValue:[NSString stringWithFormat:NSLocalizedString(@"Expected status code in (%@), got %d", nil), AFStringFromIndexSet([[self class] acceptableStatusCodes]), statusCode] forKey:NSLocalizedDescriptionKey]; + self.HTTPError = [[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorBadServerResponse userInfo:userInfo]; + } else if (![self hasAcceptableContentType]) { + // Don't invalidate content type if there is no content + if ([self.responseData length] > 0) { + [userInfo setValue:[NSString stringWithFormat:NSLocalizedString(@"Expected content type %@, got %@", nil), [[self class] acceptableContentTypes], [self.response MIMEType]] forKey:NSLocalizedDescriptionKey]; + self.HTTPError = [[NSError alloc] initWithDomain:AFNetworkingErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:userInfo]; + } + } } } @@ -189,11 +175,28 @@ - (void)pause { } - (BOOL)hasAcceptableStatusCode { - return ![[self class] acceptableStatusCodes] || [[[self class] acceptableStatusCodes] containsIndex:[self.response statusCode]]; + if (!self.response) { + return NO; + } + + NSUInteger statusCode = ([self.response isKindOfClass:[NSHTTPURLResponse class]]) ? (NSUInteger)[self.response statusCode] : 200; + return ![[self class] acceptableStatusCodes] || [[[self class] acceptableStatusCodes] containsIndex:statusCode]; } - (BOOL)hasAcceptableContentType { - return ![[self class] acceptableContentTypes] || [[[self class] acceptableContentTypes] containsObject:[self.response MIMEType]]; + if (!self.response) { + return NO; + } + + // According to RFC 2616: + // Any HTTP/1.1 message containing an entity-body SHOULD include a Content-Type header field defining the media type of that body. If and only if the media type is not given by a Content-Type field, the recipient MAY attempt to guess the media type via inspection of its content and/or the name extension(s) of the URI used to identify the resource. If the media type remains unknown, the recipient SHOULD treat it as type "application/octet-stream". + // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html + NSString *contentType = [self.response MIMEType]; + if (!contentType) { + contentType = @"application/octet-stream"; + } + + return ![[self class] acceptableContentTypes] || [[[self class] acceptableContentTypes] containsObject:contentType]; } - (void)setSuccessCallbackQueue:(dispatch_queue_t)successCallbackQueue { @@ -252,18 +255,6 @@ - (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operatio #pragma clang diagnostic pop } -- (void)setResponseFilePath:(NSString *)responseFilePath { - if ([self isReady] && responseFilePath != _responseFilePath) { - _responseFilePath = responseFilePath; - - if (responseFilePath) { - self.outputStream = [NSOutputStream outputStreamToFileAtPath:responseFilePath append:NO]; - }else { - self.outputStream = [NSOutputStream outputStreamToMemory]; - } - } -} - #pragma mark - AFHTTPRequestOperation + (NSIndexSet *)acceptableStatusCodes { @@ -305,30 +296,38 @@ - (void)connection:(NSURLConnection *)connection { self.response = (NSHTTPURLResponse *)response; - // 206 = Partial Content. + // Set Content-Range header if status code of response is 206 (Partial Content) + // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.7 long long totalContentLength = self.response.expectedContentLength; long long fileOffset = 0; - if ([self.response statusCode] != 206) { + NSUInteger statusCode = ([self.response isKindOfClass:[NSHTTPURLResponse class]]) ? (NSUInteger)[self.response statusCode] : 200; + if (statusCode == 206) { + NSString *contentRange = [self.response.allHeaderFields valueForKey:@"Content-Range"]; + if ([contentRange hasPrefix:@"bytes"]) { + NSArray *byteRanges = [contentRange componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@" -/"]]; + if ([byteRanges count] == 4) { + fileOffset = [[byteRanges objectAtIndex:1] longLongValue]; + totalContentLength = [[byteRanges objectAtIndex:2] longLongValue] ?: -1; // if this is "*", it's converted to 0, but -1 is default. + } + } + } else { if ([self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey]) { [self.outputStream setProperty:[NSNumber numberWithInteger:0] forKey:NSStreamFileCurrentOffsetKey]; } else { if ([[self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey] length] > 0) { self.outputStream = [NSOutputStream outputStreamToMemory]; + + NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; + for (NSString *runLoopMode in self.runLoopModes) { + [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode]; + } } } - }else { - NSString *contentRange = [self.response.allHeaderFields valueForKey:@"Content-Range"]; - if ([contentRange hasPrefix:@"bytes"]) { - NSArray *bytes = [contentRange componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@" -/"]]; - if ([bytes count] == 4) { - fileOffset = [[bytes objectAtIndex:1] longLongValue]; - totalContentLength = [[bytes objectAtIndex:2] longLongValue] ?: -1; // if this is *, it's converted to 0, but -1 is default. - } - } - } + self.offsetContentLength = MAX(fileOffset, 0); self.totalContentLength = totalContentLength; + [self.outputStream open]; } diff --git a/AFNetworking/AFImageRequestOperation.h b/AFNetworking/AFImageRequestOperation.h index c9c5f22725..6c29fe6e51 100644 --- a/AFNetworking/AFImageRequestOperation.h +++ b/AFNetworking/AFImageRequestOperation.h @@ -62,15 +62,11 @@ #if __IPHONE_OS_VERSION_MIN_REQUIRED /** - The scale factor used when interpreting the image data to construct `responseImage`. Specifying a scale factor of 1.0 results in an image whose size matches the pixel-based dimensions of the image. Applying a different scale factor changes the size of the image as reported by the size property. This is set to the value of `[[UIScreen mainScreen] scale]` by default, which automatically scales images for retina displays, for instance. + The scale factor used when interpreting the image data to construct `responseImage`. Specifying a scale factor of 1.0 results in an image whose size matches the pixel-based dimensions of the image. Applying a different scale factor changes the size of the image as reported by the size property. This is set to the value of scale of the main screen by default, which automatically scales images for retina displays, for instance. */ @property (nonatomic, assign) CGFloat imageScale; #endif -/** - An image constructed from the response data. If an error occurs during the request, `nil` will be returned, and the `error` property will be set to the error. - */ - /** Creates and returns an `AFImageRequestOperation` object and sets the specified success callback. diff --git a/AFNetworking/AFImageRequestOperation.m b/AFNetworking/AFImageRequestOperation.m index e19237e6ba..cac1307ffc 100644 --- a/AFNetworking/AFImageRequestOperation.m +++ b/AFNetworking/AFImageRequestOperation.m @@ -157,9 +157,12 @@ - (UIImage *)responseImage { } - (void)setImageScale:(CGFloat)imageScale { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wfloat-equal" if (imageScale == _imageScale) { return; } +#pragma clang diagnostic pop _imageScale = imageScale; @@ -178,7 +181,7 @@ - (NSImage *)responseImage { } #endif -#pragma mark - AFHTTPClientOperation +#pragma mark - AFHTTPRequestOperation + (NSSet *)acceptableContentTypes { return [NSSet setWithObjects:@"image/tiff", @"image/jpeg", @"image/gif", @"image/png", @"image/ico", @"image/x-icon", @"image/bmp", @"image/x-bmp", @"image/x-xbitmap", @"image/x-win-bitmap", nil]; diff --git a/AFNetworking/AFJSONRequestOperation.h b/AFNetworking/AFJSONRequestOperation.h index bca959c44d..ed82e6a963 100644 --- a/AFNetworking/AFJSONRequestOperation.h +++ b/AFNetworking/AFJSONRequestOperation.h @@ -55,7 +55,7 @@ @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. + @param failure A block object to be executed when the operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response 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 */ diff --git a/AFNetworking/AFNetworkActivityIndicatorManager.m b/AFNetworking/AFNetworkActivityIndicatorManager.m index dda196cdd9..5672773406 100644 --- a/AFNetworking/AFNetworkActivityIndicatorManager.m +++ b/AFNetworking/AFNetworkActivityIndicatorManager.m @@ -25,14 +25,15 @@ #import "AFHTTPRequestOperation.h" #if __IPHONE_OS_VERSION_MIN_REQUIRED -static NSTimeInterval const kAFNetworkActivityIndicatorInvisibilityDelay = 0.25; +static NSTimeInterval const kAFNetworkActivityIndicatorInvisibilityDelay = 0.17; @interface AFNetworkActivityIndicatorManager () -@property (readwrite, atomic, assign) NSInteger activityCount; -@property (readwrite, nonatomic) NSTimer *activityIndicatorVisibilityTimer; +@property (readwrite, assign) NSInteger activityCount; +@property (readwrite, nonatomic, retain) NSTimer *activityIndicatorVisibilityTimer; @property (readonly, getter = isNetworkActivityIndicatorVisible) BOOL networkActivityIndicatorVisible; - (void)updateNetworkActivityIndicatorVisibility; +- (void)updateNetworkActivityIndicatorVisibilityDelayed; @end @implementation AFNetworkActivityIndicatorManager @@ -51,6 +52,10 @@ + (AFNetworkActivityIndicatorManager *)sharedManager { return _sharedManager; } ++ (NSSet *)keyPathsForValuesAffectingIsNetworkActivityIndicatorVisible { + return [NSSet setWithObject:@"activityCount"]; +} + - (id)init { self = [super init]; if (!self) { @@ -76,9 +81,9 @@ - (void)updateNetworkActivityIndicatorVisibilityDelayed { 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]; + [[NSRunLoop mainRunLoop] addTimer:self.activityIndicatorVisibilityTimer forMode:NSRunLoopCommonModes]; } else { - [self updateNetworkActivityIndicatorVisibility]; + [self performSelectorOnMainThread:@selector(updateNetworkActivityIndicatorVisibility) withObject:nil waitUntilDone:NO modes:[NSArray arrayWithObject:NSRunLoopCommonModes]]; } } } @@ -88,9 +93,7 @@ - (BOOL)isNetworkActivityIndicatorVisible { } - (void)updateNetworkActivityIndicatorVisibility { - dispatch_async(dispatch_get_main_queue(), ^{ - [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:[self isNetworkActivityIndicatorVisible]]; - }); + [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:[self isNetworkActivityIndicatorVisible]]; } // Not exposed, but used if activityCount is set via KVC. @@ -123,10 +126,6 @@ - (void)decrementActivityCount { [self updateNetworkActivityIndicatorVisibilityDelayed]; } -+ (NSSet *)keyPathsForValuesAffectingIsNetworkActivityIndicatorVisible { - return [NSSet setWithObject:@"activityCount"]; -} - @end #endif diff --git a/AFNetworking/AFURLConnectionOperation.h b/AFNetworking/AFURLConnectionOperation.h index 398a474ad6..ed367e0335 100644 --- a/AFNetworking/AFURLConnectionOperation.h +++ b/AFNetworking/AFURLConnectionOperation.h @@ -22,23 +22,6 @@ #import -/** - Indicates an error occured in AFNetworking. - - @discussion Error codes for AFNetworkingErrorDomain correspond to codes in NSURLErrorDomain. - */ -extern NSString * const AFNetworkingErrorDomain; - -/** - Posted when an operation begins executing. - */ -extern NSString * const AFNetworkingOperationDidStartNotification; - -/** - Posted when an operation finishes. - */ -extern NSString * const AFNetworkingOperationDidFinishNotification; - /** `AFURLConnectionOperation` is an `NSOperation` that implements NSURLConnection delegate methods. @@ -61,7 +44,7 @@ extern NSString * const AFNetworkingOperationDidFinishNotification; - `connection:canAuthenticateAgainstProtectionSpace:` - `connection:didReceiveAuthenticationChallenge:` - If any of these methods are overriden in a subclass, they _must_ call the `super` implementation first. + If any of these methods are overridden in a subclass, they _must_ call the `super` implementation first. ## Class Constructors @@ -71,11 +54,24 @@ extern NSString * const AFNetworkingOperationDidFinishNotification; The built-in `completionBlock` provided by `NSOperation` allows for custom behavior to be executed after the request finishes. It is a common pattern for class constructors in subclasses to take callback block parameters, and execute them conditionally in the body of its `completionBlock`. Make sure to handle cancelled operations appropriately when setting a `completionBlock` (e.g. returning early before parsing response data). See the implementation of any of the `AFHTTPRequestOperation` subclasses for an example of this. - @warning Subclasses are strongly discouraged from overriding `setCompletionBlock:`, as `AFURLConnectionOperation`'s implementation includes a workaround to mitigate retain cycles, and what Apple rather ominously refers to as "The Deallocation Problem" (See http://developer.apple.com/library/ios/technotes/tn2109/_index.html#//apple_ref/doc/uid/DTS40010274-CH1-SUBSECTION11) + Subclasses are strongly discouraged from overriding `setCompletionBlock:`, as `AFURLConnectionOperation`'s implementation includes a workaround to mitigate retain cycles, and what Apple rather ominously refers to as ["The Deallocation Problem"](http://developer.apple.com/library/ios/#technotes/tn2109/). + + ## NSCoding & NSCopying Conformance + + `AFURLConnectionOperation` conforms to the `NSCoding` and `NSCopying` protocols, allowing operations to be archived to disk, and copied in memory, respectively. However, because of the intrinsic limitations of capturing the exact state of an operation at a particular moment, there are some important caveats to keep in mind: + + ### NSCoding Caveats - @warning Attempting to load a `file://` URL in iOS 4 may result in an `NSInvalidArgumentException`, caused by the connection returning `NSURLResponse` rather than `NSHTTPURLResponse`, which is the behavior as of iOS 5. + - Encoded operations do not include any block or stream properties. Be sure to set `completionBlock`, `outputStream`, and any callback blocks as necessary when using `-initWithCoder:` or `NSKeyedUnarchiver`. + - Operations are paused on `encodeWithCoder:`. If the operation was encoded while paused or still executing, its archived state will return `YES` for `isReady`. Otherwise, the state of an operation when encoding will remain unchanged. + + ### NSCopying Caveats + + - `-copy` and `-copyWithZone:` return a new operation with the `NSURLRequest` of the original. So rather than an exact copy of the operation at that particular instant, the copying mechanism returns a completely new instance, which can be useful for retrying operations. + - A copy of an operation will not include the `outputStream` of the original. + - Operation copies do not include `completionBlock`. `completionBlock` often strongly captures a reference to `self`, which, perhaps surprisingly, would otherwise point to the _original_ operation when copied. */ -@interface AFURLConnectionOperation : NSOperation +@interface AFURLConnectionOperation : NSOperation ///------------------------------- /// @name Accessing Run Loop Modes @@ -84,7 +80,7 @@ extern NSString * const AFNetworkingOperationDidFinishNotification; /** The run loop modes in which the operation will run on the network thread. By default, this is a single-member set containing `NSRunLoopCommonModes`. */ -@property (nonatomic) NSSet *runLoopModes; +@property (nonatomic, strong) NSSet *runLoopModes; ///----------------------------------------- /// @name Getting URL Connection Information @@ -101,7 +97,7 @@ extern NSString * const AFNetworkingOperationDidFinishNotification; @property (readonly, nonatomic, strong) NSURLResponse *response; /** - The error, if any, that occured in the lifecycle of the request. + The error, if any, that occurred in the lifecycle of the request. */ @property (readonly, nonatomic, strong) NSError *error; @@ -130,14 +126,14 @@ extern NSString * const AFNetworkingOperationDidFinishNotification; @discussion This property acts as a proxy to the `HTTPBodyStream` property of `request`. */ -@property (nonatomic) NSInputStream *inputStream; +@property (nonatomic, strong) NSInputStream *inputStream; /** The output stream that is used to write data received until the request is finished. @discussion By default, data is accumulated into a buffer that is stored into `responseData` upon completion of the request. When `outputStream` is set, the data will not be accumulated into an internal buffer, and as a result, the `responseData` property of the completed request will be `nil`. The output stream will be scheduled in the network thread runloop upon being set. */ -@property (nonatomic) NSOutputStream *outputStream; +@property (nonatomic, strong) NSOutputStream *outputStream; ///------------------------------------------------------ /// @name Initializing an AFURLConnectionOperation Object @@ -159,7 +155,7 @@ extern NSString * const AFNetworkingOperationDidFinishNotification; /** Pauses the execution of the request operation. - @discussion A paused operation returns `NO` for `-isReady`, `-isExecuting`, and `-isFinished`. As such, it will remain in an `NSOperationQueue` until it is either cancelled or resumed. Pausing a finished or cancelled operation has no effect. + @discussion A paused operation returns `NO` for `-isReady`, `-isExecuting`, and `-isFinished`. As such, it will remain in an `NSOperationQueue` until it is either cancelled or resumed. Pausing a finished, cancelled, or paused operation has no effect. */ - (void)pause; @@ -197,20 +193,16 @@ extern NSString * const AFNetworkingOperationDidFinishNotification; /** 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 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 + @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 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, and will execute on the main thread. */ -- (void)setUploadProgressBlock:(void (^)(NSInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))block; +- (void)setUploadProgressBlock:(void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))block; /** 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 three arguments: the number of bytes read since the last time the download 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 + @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 read since the last time the download 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, and will execute on the main thread. */ -- (void)setDownloadProgressBlock:(void (^)(NSInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead))block; +- (void)setDownloadProgressBlock:(void (^)(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead))block; ///------------------------------------------------- /// @name Setting NSURLConnection Delegate Callbacks @@ -250,3 +242,52 @@ extern NSString * const AFNetworkingOperationDidFinishNotification; - (void)setCacheResponseBlock:(NSCachedURLResponse * (^)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse))block; @end + +///---------------- +/// @name Constants +///---------------- + +/** + ## User info dictionary keys + + These keys may exist in the user info dictionary, in addition to those defined for NSError. + + - `NSString * const AFNetworkingOperationFailingURLRequestErrorKey` + - `NSString * const AFNetworkingOperationFailingURLResponseErrorKey` + + ### Constants + + `AFNetworkingOperationFailingURLRequestErrorKey` + The corresponding value is an `NSURLRequest` containing the request of the operation associated with an error. This key is only present in the `AFNetworkingErrorDomain`. + + `AFNetworkingOperationFailingURLResponseErrorKey` + The corresponding value is an `NSURLResponse` containing the response of the operation associated with an error. This key is only present in the `AFNetworkingErrorDomain`. + + ## Error Domains + + The following error domain is predefined. + + - `NSString * const AFNetworkingErrorDomain` + + ### Constants + + `AFNetworkingErrorDomain` + AFNetworking errors. Error codes for `AFNetworkingErrorDomain` correspond to codes in `NSURLErrorDomain`. + */ +extern NSString * const AFNetworkingErrorDomain; +extern NSString * const AFNetworkingOperationFailingURLRequestErrorKey; +extern NSString * const AFNetworkingOperationFailingURLResponseErrorKey; + +///-------------------- +/// @name Notifications +///-------------------- + +/** + Posted when an operation begins executing. + */ +extern NSString * const AFNetworkingOperationDidStartNotification; + +/** + Posted when an operation finishes. + */ +extern NSString * const AFNetworkingOperationDidFinishNotification; diff --git a/AFNetworking/AFURLConnectionOperation.m b/AFNetworking/AFURLConnectionOperation.m index 8ff123a44d..d4cb8ddbbb 100644 --- a/AFNetworking/AFURLConnectionOperation.m +++ b/AFNetworking/AFURLConnectionOperation.m @@ -26,10 +26,10 @@ #endif typedef enum { - AFHTTPOperationPausedState = -1, - AFHTTPOperationReadyState = 1, - AFHTTPOperationExecutingState = 2, - AFHTTPOperationFinishedState = 3, + AFOperationPausedState = -1, + AFOperationReadyState = 1, + AFOperationExecutingState = 2, + AFOperationFinishedState = 3, } _AFOperationState; typedef signed short AFOperationState; @@ -40,17 +40,16 @@ typedef id AFBackgroundTaskIdentifier; #endif -static NSUInteger const kAFHTTPMinimumInitialDataCapacity = 1024; -static NSUInteger const kAFHTTPMaximumInitialDataCapacity = 1024 * 1024 * 8; - static NSString * const kAFNetworkingLockName = @"com.alamofire.networking.operation.lock"; -NSString * const AFNetworkingErrorDomain = @"com.alamofire.networking.error"; +NSString * const AFNetworkingErrorDomain = @"AFNetworkingErrorDomain"; +NSString * const AFNetworkingOperationFailingURLRequestErrorKey = @"AFNetworkingOperationFailingURLRequestErrorKey"; +NSString * const AFNetworkingOperationFailingURLResponseErrorKey = @"AFNetworkingOperationFailingURLResponseErrorKey"; NSString * const AFNetworkingOperationDidStartNotification = @"com.alamofire.networking.operation.start"; NSString * const AFNetworkingOperationDidFinishNotification = @"com.alamofire.networking.operation.finish"; -typedef void (^AFURLConnectionOperationProgressBlock)(NSInteger bytes, long long totalBytes, long long totalBytesExpected); +typedef void (^AFURLConnectionOperationProgressBlock)(NSUInteger bytes, long long totalBytes, long long totalBytesExpected); typedef BOOL (^AFURLConnectionOperationAuthenticationAgainstProtectionSpaceBlock)(NSURLConnection *connection, NSURLProtectionSpace *protectionSpace); typedef void (^AFURLConnectionOperationAuthenticationChallengeBlock)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge); typedef NSCachedURLResponse * (^AFURLConnectionOperationCacheResponseBlock)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse); @@ -58,13 +57,13 @@ static inline NSString * AFKeyPathFromOperationState(AFOperationState state) { switch (state) { - case AFHTTPOperationReadyState: + case AFOperationReadyState: return @"isReady"; - case AFHTTPOperationExecutingState: + case AFOperationExecutingState: return @"isExecuting"; - case AFHTTPOperationFinishedState: + case AFOperationFinishedState: return @"isFinished"; - case AFHTTPOperationPausedState: + case AFOperationPausedState: return @"isPaused"; default: return @"state"; @@ -73,28 +72,28 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperationState toState, BOOL isCancelled) { switch (fromState) { - case AFHTTPOperationReadyState: + case AFOperationReadyState: switch (toState) { - case AFHTTPOperationPausedState: - case AFHTTPOperationExecutingState: + case AFOperationPausedState: + case AFOperationExecutingState: return YES; - case AFHTTPOperationFinishedState: + case AFOperationFinishedState: return isCancelled; default: return NO; } - case AFHTTPOperationExecutingState: + case AFOperationExecutingState: switch (toState) { - case AFHTTPOperationPausedState: - case AFHTTPOperationFinishedState: + case AFOperationPausedState: + case AFOperationFinishedState: return YES; default: return NO; } - case AFHTTPOperationFinishedState: + case AFOperationFinishedState: return NO; - case AFHTTPOperationPausedState: - return toState == AFHTTPOperationReadyState; + case AFOperationPausedState: + return toState == AFOperationReadyState; default: return YES; } @@ -121,6 +120,7 @@ @interface AFURLConnectionOperation () - (void)operationDidStart; - (void)finish; +- (void)cancelConnection; @end @implementation AFURLConnectionOperation @@ -179,8 +179,12 @@ - (id)initWithRequest:(NSURLRequest *)urlRequest { self.request = urlRequest; self.outputStream = [NSOutputStream outputStreamToMemory]; - - self.state = AFHTTPOperationReadyState; + NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; + for (NSString *runLoopMode in self.runLoopModes) { + [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode]; + } + + self.state = AFOperationReadyState; return self; } @@ -230,6 +234,10 @@ - (void)setInputStream:(NSInputStream *)inputStream { } - (void)setOutputStream:(NSOutputStream *)outputStream { + if (_outputStream == outputStream) { + return; + } + [self willChangeValueForKey:@"outputStream"]; if (_outputStream) { @@ -237,11 +245,6 @@ - (void)setOutputStream:(NSOutputStream *)outputStream { } _outputStream = outputStream; [self didChangeValueForKey:@"outputStream"]; - - NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; - for (NSString *runLoopMode in self.runLoopModes) { - [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode]; - } } #if __IPHONE_OS_VERSION_MIN_REQUIRED @@ -264,11 +267,11 @@ - (void)setShouldExecuteAsBackgroundTaskWithExpirationHandler:(void (^)(void))ha } #endif -- (void)setUploadProgressBlock:(void (^)(NSInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))block { +- (void)setUploadProgressBlock:(void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))block { self.uploadProgress = block; } -- (void)setDownloadProgressBlock:(void (^)(NSInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead))block { +- (void)setDownloadProgressBlock:(void (^)(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead))block { self.downloadProgress = block; } @@ -301,10 +304,10 @@ - (void)setState:(AFOperationState)state { [self didChangeValueForKey:newStateKey]; switch (state) { - case AFHTTPOperationExecutingState: + case AFOperationExecutingState: [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidStartNotification object:self]; break; - case AFHTTPOperationFinishedState: + case AFOperationFinishedState: [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidFinishNotification object:self]; break; default: @@ -330,20 +333,24 @@ - (NSString *)responseString { } - (void)pause { - if ([self isPaused]) { + if ([self isPaused] || [self isFinished] || [self isCancelled]) { return; } [self.lock lock]; - self.state = AFHTTPOperationPausedState; - [self.connection performSelector:@selector(cancel) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; - [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidFinishNotification object:self]; + if ([self isExecuting]) { + [self.connection performSelector:@selector(cancel) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; + [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidFinishNotification object:self]; + } + + self.state = AFOperationPausedState; + [self.lock unlock]; } - (BOOL)isPaused { - return self.state == AFHTTPOperationPausedState; + return self.state == AFOperationPausedState; } - (void)resume { @@ -352,7 +359,7 @@ - (void)resume { } [self.lock lock]; - self.state = AFHTTPOperationReadyState; + self.state = AFOperationReadyState; [self start]; [self.lock unlock]; @@ -361,15 +368,15 @@ - (void)resume { #pragma mark - NSOperation - (BOOL)isReady { - return self.state == AFHTTPOperationReadyState && [super isReady]; + return self.state == AFOperationReadyState && [super isReady]; } - (BOOL)isExecuting { - return self.state == AFHTTPOperationExecutingState; + return self.state == AFOperationExecutingState; } - (BOOL)isFinished { - return self.state == AFHTTPOperationFinishedState; + return self.state == AFOperationFinishedState; } - (BOOL)isConcurrent { @@ -379,7 +386,7 @@ - (BOOL)isConcurrent { - (void)start { [self.lock lock]; if ([self isReady]) { - self.state = AFHTTPOperationExecutingState; + self.state = AFOperationExecutingState; [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; } @@ -405,7 +412,7 @@ - (void)operationDidStart { } - (void)finish { - self.state = AFHTTPOperationFinishedState; + self.state = AFOperationFinishedState; } - (void)cancel { @@ -495,7 +502,7 @@ - (void)connection:(NSURLConnection *)connection - (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request - redirectResponse:(NSURLResponse *)redirectResponse; + redirectResponse:(NSURLResponse *)redirectResponse { if (self.redirectResponse) { return self.redirectResponse(connection, request, redirectResponse); @@ -510,7 +517,9 @@ - (void)connection:(NSURLConnection *)__unused connection totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite { if (self.uploadProgress) { - self.uploadProgress(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite); + dispatch_async(dispatch_get_main_queue(), ^{ + self.uploadProgress(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite); + }); } } @@ -533,7 +542,9 @@ - (void)connection:(NSURLConnection *)__unused connection } if (self.downloadProgress) { - self.downloadProgress((long long)[data length], self.totalBytesRead, self.response.expectedContentLength); + dispatch_async(dispatch_get_main_queue(), ^{ + self.downloadProgress([data length], self.totalBytesRead, self.response.expectedContentLength); + }); } } @@ -573,4 +584,61 @@ - (NSCachedURLResponse *)connection:(NSURLConnection *)connection } } +#pragma mark - NSCoding + +- (id)initWithCoder:(NSCoder *)aDecoder { + NSURLRequest *request = [aDecoder decodeObjectForKey:@"request"]; + + self = [self initWithRequest:request]; + if (!self) { + return nil; + } + + self.state = [aDecoder decodeIntegerForKey:@"state"]; + self.cancelled = [aDecoder decodeBoolForKey:@"isCancelled"]; + self.response = [aDecoder decodeObjectForKey:@"response"]; + self.error = [aDecoder decodeObjectForKey:@"error"]; + self.responseData = [aDecoder decodeObjectForKey:@"responseData"]; + self.totalBytesRead = [[aDecoder decodeObjectForKey:@"totalBytesRead"] longLongValue]; + + return self; +} + +- (void)encodeWithCoder:(NSCoder *)aCoder { + [self pause]; + + [aCoder encodeObject:self.request forKey:@"request"]; + + switch (self.state) { + case AFOperationExecutingState: + case AFOperationPausedState: + [aCoder encodeInteger:AFOperationReadyState forKey:@"state"]; + break; + default: + [aCoder encodeInteger:self.state forKey:@"state"]; + break; + } + + [aCoder encodeBool:[self isCancelled] forKey:@"isCancelled"]; + [aCoder encodeObject:self.response forKey:@"response"]; + [aCoder encodeObject:self.error forKey:@"error"]; + [aCoder encodeObject:self.responseData forKey:@"responseData"]; + [aCoder encodeObject:[NSNumber numberWithLongLong:self.totalBytesRead] forKey:@"totalBytesRead"]; +} + +#pragma mark - NSCopying + +- (id)copyWithZone:(NSZone *)zone { + AFURLConnectionOperation *operation = [[[self class] allocWithZone:zone] initWithRequest:self.request]; + + operation.uploadProgress = self.uploadProgress; + operation.downloadProgress = self.downloadProgress; + operation.authenticationAgainstProtectionSpace = self.authenticationAgainstProtectionSpace; + operation.authenticationChallenge = self.authenticationChallenge; + operation.cacheResponse = self.cacheResponse; + operation.redirectResponse = self.redirectResponse; + + return operation; +} + @end diff --git a/AFNetworking/AFXMLRequestOperation.h b/AFNetworking/AFXMLRequestOperation.h index ca16d423e0..10a0f89205 100644 --- a/AFNetworking/AFXMLRequestOperation.h +++ b/AFNetworking/AFXMLRequestOperation.h @@ -77,7 +77,7 @@ @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 XML document 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 XML. 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. + @param failure A block object to be executed when the operation finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data as XML. 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 XML request operation */ diff --git a/AFNetworking/UIImageView+AFNetworking.m b/AFNetworking/UIImageView+AFNetworking.m index d694c35e7f..334c70055c 100644 --- a/AFNetworking/UIImageView+AFNetworking.m +++ b/AFNetworking/UIImageView+AFNetworking.m @@ -59,10 +59,11 @@ - (void)af_setImageRequestOperation:(AFImageRequestOperation *)imageRequestOpera + (NSOperationQueue *)af_sharedImageRequestOperationQueue { static NSOperationQueue *_af_imageRequestOperationQueue = nil; - if (!_af_imageRequestOperationQueue) { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ _af_imageRequestOperationQueue = [[NSOperationQueue alloc] init]; - [_af_imageRequestOperationQueue setMaxConcurrentOperationCount:8]; - } + [_af_imageRequestOperationQueue setMaxConcurrentOperationCount:NSOperationQueueDefaultMaxConcurrentOperationCount]; + }); return _af_imageRequestOperationQueue; } @@ -86,9 +87,10 @@ - (void)setImageWithURL:(NSURL *)url { - (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholderImage { - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:30.0]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; [request setHTTPShouldHandleCookies:NO]; [request setHTTPShouldUsePipelining:YES]; + [request addValue:@"image/*" forHTTPHeaderField:@"Accept"]; [self setImageWithURLRequest:request placeholderImage:placeholderImage success:nil failure:nil]; } diff --git a/Example/AFNetworking iOS Example.xcodeproj/project.pbxproj b/Example/AFNetworking iOS Example.xcodeproj/project.pbxproj index 7cb72b6002..f193529e37 100644 --- a/Example/AFNetworking iOS Example.xcodeproj/project.pbxproj +++ b/Example/AFNetworking iOS Example.xcodeproj/project.pbxproj @@ -186,8 +186,8 @@ children = ( F8DA09E31396AC040057D0CC /* main.m */, F8129C3815910830009BFE23 /* Prefix.pch */, - F8129C7215910C37009BFE23 /* AppDelegate.m */, F8129C7315910C37009BFE23 /* AppDelegate.h */, + F8129C7215910C37009BFE23 /* AppDelegate.m */, F8E4696C1395739D00DB05C8 /* iOS-Info.plist */, ); name = "Supporting Files"; diff --git a/Example/AppDelegate.m b/Example/AppDelegate.m index 22ea8489eb..c5b56a1c58 100644 --- a/Example/AppDelegate.m +++ b/Example/AppDelegate.m @@ -71,7 +71,11 @@ - (void)applicationDidFinishLaunching:(NSNotification *)notification { [self.window makeKeyAndOrderFront:self]; - [Tweet publicTimelineTweetsWithBlock:^(NSArray *tweets) { + [Tweet publicTimelineTweetsWithBlock:^(NSArray *tweets, NSError *error) { + if (error) { + [[NSAlert alertWithMessageText:NSLocalizedString(@"Error", nil) defaultButton:NSLocalizedString(@"OK", nil) alternateButton:nil otherButton:nil informativeTextWithFormat:@"%@",[error localizedDescription]] runModal]; + } + self.tweetsArrayController.content = tweets; }]; diff --git a/Example/Classes/Controllers/PublicTimelineViewController.m b/Example/Classes/Controllers/PublicTimelineViewController.m index 966b607096..aad2d63328 100644 --- a/Example/Classes/Controllers/PublicTimelineViewController.m +++ b/Example/Classes/Controllers/PublicTimelineViewController.m @@ -41,8 +41,10 @@ - (void)reload:(id)sender { [_activityIndicatorView startAnimating]; self.navigationItem.rightBarButtonItem.enabled = NO; - [Tweet publicTimelineTweetsWithBlock:^(NSArray *tweets) { - if (tweets) { + [Tweet publicTimelineTweetsWithBlock:^(NSArray *tweets, NSError *error) { + if (error) { + [[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Error", nil) message:[error localizedDescription] delegate:nil cancelButtonTitle:nil otherButtonTitles:NSLocalizedString(@"OK", nil), nil] show]; + } else { _tweets = tweets; [self.tableView reloadData]; } diff --git a/Example/Classes/Models/Tweet.h b/Example/Classes/Models/Tweet.h index 9954a2bf0a..2a41c4b7fe 100644 --- a/Example/Classes/Models/Tweet.h +++ b/Example/Classes/Models/Tweet.h @@ -33,6 +33,6 @@ - (id)initWithAttributes:(NSDictionary *)attributes; -+ (void)publicTimelineTweetsWithBlock:(void (^)(NSArray *tweets))block; ++ (void)publicTimelineTweetsWithBlock:(void (^)(NSArray *tweets, NSError *error))block; @end diff --git a/Example/Classes/Models/Tweet.m b/Example/Classes/Models/Tweet.m index 683e25e697..5e59e1995d 100644 --- a/Example/Classes/Models/Tweet.m +++ b/Example/Classes/Models/Tweet.m @@ -46,7 +46,7 @@ - (id)initWithAttributes:(NSDictionary *)attributes { #pragma mark - -+ (void)publicTimelineTweetsWithBlock:(void (^)(NSArray *tweets))block { ++ (void)publicTimelineTweetsWithBlock:(void (^)(NSArray *tweets, NSError *error))block { [[AFTwitterAPIClient sharedClient] getPath:@"statuses/public_timeline.json" parameters:[NSDictionary dictionaryWithObject:@"false" forKey:@"include_entities"] success:^(AFHTTPRequestOperation *operation, id JSON) { NSMutableArray *mutableTweets = [NSMutableArray arrayWithCapacity:[JSON count]]; for (NSDictionary *attributes in JSON) { @@ -55,16 +55,11 @@ + (void)publicTimelineTweetsWithBlock:(void (^)(NSArray *tweets))block { } if (block) { - block([NSArray arrayWithArray:mutableTweets]); + block([NSArray arrayWithArray:mutableTweets], nil); } - } failure:^(AFHTTPRequestOperation *operation, NSError *error) { -#if __IPHONE_OS_VERSION_MIN_REQUIRED - [[[UIAlertView alloc] initWithTitle:NSLocalizedString(@"Error", nil) message:[error localizedDescription] delegate:nil cancelButtonTitle:nil otherButtonTitles:NSLocalizedString(@"OK", nil), nil] show]; -#else - [[NSAlert alertWithMessageText:NSLocalizedString(@"Error", nil) defaultButton:NSLocalizedString(@"OK", nil) alternateButton:nil otherButton:nil informativeTextWithFormat:[error localizedDescription]] runModal]; -#endif + } failure:^(AFHTTPRequestOperation *operation, NSError *error) { if (block) { - block(nil); + block([NSArray array], error); } }]; } diff --git a/Example/Classes/Models/User.m b/Example/Classes/Models/User.m index 18da822138..a32b4bdf27 100644 --- a/Example/Classes/Models/User.m +++ b/Example/Classes/Models/User.m @@ -27,7 +27,9 @@ #if __MAC_OS_X_VERSION_MIN_REQUIRED @interface User () +#if __MAC_OS_X_VERSION_MIN_REQUIRED + (NSOperationQueue *)sharedProfileImageRequestOperationQueue; +#endif @end #endif diff --git a/README.md b/README.md index efc226fb6b..3e6b482c26 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Choose AFNetworking for your next project, or migrate over your existing project - [Download AFNetworking](https://github.com/AFNetworking/AFNetworking/zipball/master) and try out the included Mac and iPhone example apps - Read the ["Getting Started" guide](https://github.com/AFNetworking/AFNetworking/wiki/Getting-Started-with-AFNetworking), [FAQ](https://github.com/AFNetworking/AFNetworking/wiki/AFNetworking-FAQ), or [other articles in the wiki](https://github.com/AFNetworking/AFNetworking/wiki) -- Check out the [complete documentation](http://afnetworking.org/Documentation/) for a comprehensive look at the APIs available in AFNetworking +- Check out the [complete documentation](http://afnetworking.github.com/AFNetworking/) for a comprehensive look at the APIs available in AFNetworking - Watch the [NSScreencast episode about AFNetworking](http://nsscreencast.com/episodes/6-afnetworking) for a quick introduction to how to use it in your application - Questions? [Stack Overflow](http://stackoverflow.com/questions/tagged/afnetworking) is the best place to find answers @@ -75,11 +75,11 @@ AFNetworking is architected to be as small and modular as possible, in order to Images AFImageRequestOperation - A subclass of AFHTTPRequestOperation for downloading an processing images. + A subclass of AFHTTPRequestOperation for downloading and processing images. UIImageView+AFNetworking - Adds methods to `UIImageView` for loading remote images asynchronously from a URL. + Adds methods to UIImageView for loading remote images asynchronously from a URL. @@ -124,8 +124,8 @@ NSMutableURLRequest *request = [httpClient multipartFormRequestWithMethod:@"POST }]; AFHTTPRequestOperation *operation = [[[AFHTTPRequestOperation alloc] initWithRequest:request] autorelease]; -[operation setUploadProgressBlock:^(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite) { - NSLog(@"Sent %d of %d bytes", totalBytesWritten, totalBytesExpectedToWrite); +[operation setUploadProgressBlock:^(NSInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) { + NSLog(@"Sent %lld of %lld bytes", totalBytesWritten, totalBytesExpectedToWrite); }]; [operation start]; ```