pushing current progress of project: most of ui complete

This commit is contained in:
Okechi Onyeje 2016-05-27 19:30:44 -04:00
parent b7f195a160
commit 3872e2db13
1049 changed files with 107229 additions and 0 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

31
Podfile Normal file
View File

@ -0,0 +1,31 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'
target 'Vendoo' do
# Comment this line if you're not using Swift and don't want to use dynamic frameworks
use_frameworks!
# Pods for Vendoo
pod 'PicoKit'
pod 'Firebase', '>= 2.5.0'
pod 'FirebaseAuth'
pod 'Locksmith'
# pod 'SwiftyJSON', :git => 'https://github.com/SwiftyJSON/SwiftyJSON.git'
pod 'Alamofire-SwiftyJSON'
pod 'OAuthSwift', '~> 0.5.0'
pod 'FBSDKCoreKit'
pod 'FBSDKLoginKit'
pod 'FBSDKShareKit'
target 'VendooTests' do
inherit! :search_paths
# Pods for testing
end
target 'VendooUITests' do
inherit! :search_paths
# Pods for testing
end
end

115
Podfile.lock Normal file
View File

@ -0,0 +1,115 @@
PODS:
- AFNetworking (2.6.3):
- AFNetworking/NSURLConnection (= 2.6.3)
- AFNetworking/NSURLSession (= 2.6.3)
- AFNetworking/Reachability (= 2.6.3)
- AFNetworking/Security (= 2.6.3)
- AFNetworking/Serialization (= 2.6.3)
- AFNetworking/UIKit (= 2.6.3)
- AFNetworking/NSURLConnection (2.6.3):
- AFNetworking/Reachability
- AFNetworking/Security
- AFNetworking/Serialization
- AFNetworking/NSURLSession (2.6.3):
- AFNetworking/Reachability
- AFNetworking/Security
- AFNetworking/Serialization
- AFNetworking/Reachability (2.6.3)
- AFNetworking/Security (2.6.3)
- AFNetworking/Serialization (2.6.3)
- AFNetworking/UIKit (2.6.3):
- AFNetworking/NSURLConnection
- AFNetworking/NSURLSession
- Alamofire (3.4.0)
- Alamofire-SwiftyJSON (0.1.1):
- Alamofire (~> 3.4)
- SwiftyJSON (~> 2.3)
- Bolts (1.7.0):
- Bolts/AppLinks (= 1.7.0)
- Bolts/Tasks (= 1.7.0)
- Bolts/AppLinks (1.7.0):
- Bolts/Tasks
- Bolts/Tasks (1.7.0)
- FBSDKCoreKit (4.12.0):
- Bolts (~> 1.7)
- FBSDKLoginKit (4.12.0):
- FBSDKCoreKit
- FBSDKShareKit (4.12.0):
- FBSDKCoreKit
- Firebase (3.2.0):
- Firebase/Core (= 3.2.0)
- Firebase/Analytics (3.2.0):
- FirebaseAnalytics (= 3.2.0)
- Firebase/Core (3.2.0):
- Firebase/Analytics (= 3.2.0)
- FirebaseAnalytics (3.2.0):
- FirebaseInstanceID (~> 1.0)
- GoogleInterchangeUtilities (~> 1.2)
- GoogleSymbolUtilities (~> 1.1)
- GoogleUtilities (~> 1.3)
- FirebaseAuth (3.0.2):
- FirebaseAnalytics (~> 3.2)
- GoogleNetworkingUtilities (~> 1.2)
- GoogleUtilities (~> 1.3)
- FirebaseInstanceID (1.0.6)
- GDataXML-HTML (1.3.0)
- GoogleInterchangeUtilities (1.2.1):
- GoogleSymbolUtilities (~> 1.0)
- GoogleNetworkingUtilities (1.2.1):
- GoogleSymbolUtilities (~> 1.0)
- GoogleSymbolUtilities (1.1.1)
- GoogleUtilities (1.3.1):
- GoogleSymbolUtilities (~> 1.0)
- Locksmith (2.0.8)
- OAuthSwift (0.5.2)
- PicoKit (0.7.2):
- PicoKit/WebService (= 0.7.2)
- PicoKit/Core (0.7.2):
- GDataXML-HTML (~> 1.3.0)
- PicoKit/OrderedDictionary
- PicoKit/XMLWriter
- PicoKit/OrderedDictionary (0.7.2)
- PicoKit/Soap (0.7.2):
- PicoKit/Core
- PicoKit/WebService (0.7.2):
- AFNetworking (~> 2.6.0)
- PicoKit/Soap
- PicoKit/XMLWriter (0.7.2)
- SwiftyJSON (2.3.2)
DEPENDENCIES:
- Alamofire-SwiftyJSON
- FBSDKCoreKit
- FBSDKLoginKit
- FBSDKShareKit
- Firebase (>= 2.5.0)
- FirebaseAuth
- Locksmith
- OAuthSwift (~> 0.5.0)
- PicoKit
SPEC CHECKSUMS:
AFNetworking: cb8d14a848e831097108418f5d49217339d4eb60
Alamofire: c19a627cefd6a95f840401c49ab1f124e07f54ee
Alamofire-SwiftyJSON: 5812bb37accc36897cc2f2dabb070d8ebcd7ac98
Bolts: a0058fa3b331c5a1e4402d534f2dae36dbff31e4
FBSDKCoreKit: 6e56d5a2a64a755ad4efb5dbeee8f82fb1b99775
FBSDKLoginKit: 03f68a8d48c2c8bb81d7eb8097973d483543e1f6
FBSDKShareKit: df1491f064cffc8ebcc3fc8b71be94cc190c728e
Firebase: 6d4012da1598732a2b43f16d8355eca90f94ed59
FirebaseAnalytics: 6f75b04e6bfb8e0b6e9ef298e04c24eea3b13863
FirebaseAuth: 94b851fc345ce98036dfedfdc2eddeaef8fa3b4c
FirebaseInstanceID: d014d574053a2fe84478f12f7bae96979e7051bb
GDataXML-HTML: 7adc03668cab35c288f1dbb8929a179f0fece898
GoogleInterchangeUtilities: def8415a862effc67d549d5b5b0b9c7a2f97d4de
GoogleNetworkingUtilities: 3e83269048cfb498dc7ec83ab36813360965c74f
GoogleSymbolUtilities: 33117db1b5f290c6fbf259585e4885b4c84b98d7
GoogleUtilities: 56c5ac05b7aa5dc417a1bb85221a9516e04d7032
Locksmith: a8ed41ac4c06506ea8cb199d8ec8a8d3d108eb2a
OAuthSwift: 1ef042d4362e755e24a78f158d817245641a5d24
PicoKit: 9079bce659a8d5408c8af1c45254b971df614de3
SwiftyJSON: 04ccea08915aa0109039157c7974cf0298da292a
PODFILE CHECKSUM: b38736b343fb2b19e963a4b958d967b3d0cd4d11
COCOAPODS: 1.0.0

BIN
Pods/.DS_Store generated vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,70 @@
// AFHTTPRequestOperation.h
// Copyright (c) 20112015 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
#import "AFURLConnectionOperation.h"
NS_ASSUME_NONNULL_BEGIN
/**
`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.
*/
@interface AFHTTPRequestOperation : AFURLConnectionOperation
///------------------------------------------------
/// @name Getting HTTP URL Connection Information
///------------------------------------------------
/**
The last HTTP response received by the operation's connection.
*/
@property (readonly, nonatomic, strong, nullable) NSHTTPURLResponse *response;
/**
Responses sent from the server in data tasks created with `dataTaskWithRequest:success:failure:` and run using the `GET` / `POST` / et al. convenience methods are automatically validated and serialized by the response serializer. By default, this property is set to an AFHTTPResponse serializer, which uses the raw data as its response object. The serializer validates the status code to be in the `2XX` range, denoting success. If the response serializer generates an error in `-responseObjectForResponse:data:error:`, the `failure` callback of the session task or request operation will be executed; otherwise, the `success` callback will be executed.
@warning `responseSerializer` must not be `nil`. Setting a response serializer will clear out any cached value
*/
@property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;
/**
An object constructed by the `responseSerializer` from the response and response data. Returns `nil` unless the operation `isFinished`, has a `response`, and has `responseData` with non-zero content length. If an error occurs during serialization, `nil` will be returned, and the `error` property will be populated with the serialization error.
*/
@property (readonly, nonatomic, strong, nullable) id responseObject;
///-----------------------------------------------------------
/// @name Setting Completion Block Success / Failure Callbacks
///-----------------------------------------------------------
/**
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.
This method should be overridden in subclasses in order to specify the response object passed into the success block.
@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 occurred during the request.
*/
- (void)setCompletionBlockWithSuccess:(nullable void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(nullable void (^)(AFHTTPRequestOperation *operation, NSError *error))failure;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,206 @@
// AFHTTPRequestOperation.m
// Copyright (c) 20112015 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "AFHTTPRequestOperation.h"
static dispatch_queue_t http_request_operation_processing_queue() {
static dispatch_queue_t af_http_request_operation_processing_queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
af_http_request_operation_processing_queue = dispatch_queue_create("com.alamofire.networking.http-request.processing", DISPATCH_QUEUE_CONCURRENT);
});
return af_http_request_operation_processing_queue;
}
static dispatch_group_t http_request_operation_completion_group() {
static dispatch_group_t af_http_request_operation_completion_group;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
af_http_request_operation_completion_group = dispatch_group_create();
});
return af_http_request_operation_completion_group;
}
#pragma mark -
@interface AFURLConnectionOperation ()
@property (readwrite, nonatomic, strong) NSURLRequest *request;
@property (readwrite, nonatomic, strong) NSURLResponse *response;
@end
@interface AFHTTPRequestOperation ()
@property (readwrite, nonatomic, strong) NSHTTPURLResponse *response;
@property (readwrite, nonatomic, strong) id responseObject;
@property (readwrite, nonatomic, strong) NSError *responseSerializationError;
@property (readwrite, nonatomic, strong) NSRecursiveLock *lock;
@end
@implementation AFHTTPRequestOperation
@dynamic response;
@dynamic lock;
- (instancetype)initWithRequest:(NSURLRequest *)urlRequest {
self = [super initWithRequest:urlRequest];
if (!self) {
return nil;
}
self.responseSerializer = [AFHTTPResponseSerializer serializer];
return self;
}
- (void)setResponseSerializer:(AFHTTPResponseSerializer <AFURLResponseSerialization> *)responseSerializer {
NSParameterAssert(responseSerializer);
[self.lock lock];
_responseSerializer = responseSerializer;
self.responseObject = nil;
self.responseSerializationError = nil;
[self.lock unlock];
}
- (id)responseObject {
[self.lock lock];
if (!_responseObject && [self isFinished] && !self.error) {
NSError *error = nil;
self.responseObject = [self.responseSerializer responseObjectForResponse:self.response data:self.responseData error:&error];
if (error) {
self.responseSerializationError = error;
}
}
[self.lock unlock];
return _responseObject;
}
- (NSError *)error {
if (_responseSerializationError) {
return _responseSerializationError;
} else {
return [super error];
}
}
#pragma mark - AFHTTPRequestOperation
- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
// completionBlock is manually nilled out in AFURLConnectionOperation to break the retain cycle.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
#pragma clang diagnostic ignored "-Wgnu"
self.completionBlock = ^{
if (self.completionGroup) {
dispatch_group_enter(self.completionGroup);
}
dispatch_async(http_request_operation_processing_queue(), ^{
if (self.error) {
if (failure) {
dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(self, self.error);
});
}
} else {
id responseObject = self.responseObject;
if (self.error) {
if (failure) {
dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(self, self.error);
});
}
} else {
if (success) {
dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
success(self, responseObject);
});
}
}
}
if (self.completionGroup) {
dispatch_group_leave(self.completionGroup);
}
});
};
#pragma clang diagnostic pop
}
#pragma mark - AFURLRequestOperation
- (void)pause {
[super pause];
u_int64_t offset = 0;
if ([self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey]) {
offset = [(NSNumber *)[self.outputStream propertyForKey:NSStreamFileCurrentOffsetKey] unsignedLongLongValue];
} else {
offset = [(NSData *)[self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey] length];
}
NSMutableURLRequest *mutableURLRequest = [self.request mutableCopy];
if ([self.response respondsToSelector:@selector(allHeaderFields)] && [[self.response allHeaderFields] valueForKey:@"ETag"]) {
[mutableURLRequest setValue:[[self.response allHeaderFields] valueForKey:@"ETag"] forHTTPHeaderField:@"If-Range"];
}
[mutableURLRequest setValue:[NSString stringWithFormat:@"bytes=%llu-", offset] forHTTPHeaderField:@"Range"];
self.request = mutableURLRequest;
}
#pragma mark - NSSecureCoding
+ (BOOL)supportsSecureCoding {
return YES;
}
- (id)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder];
if (!self) {
return nil;
}
self.responseSerializer = [decoder decodeObjectOfClass:[AFHTTPResponseSerializer class] forKey:NSStringFromSelector(@selector(responseSerializer))];
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[super encodeWithCoder:coder];
[coder encodeObject:self.responseSerializer forKey:NSStringFromSelector(@selector(responseSerializer))];
}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {
AFHTTPRequestOperation *operation = [super copyWithZone:zone];
operation.responseSerializer = [self.responseSerializer copyWithZone:zone];
operation.completionQueue = self.completionQueue;
operation.completionGroup = self.completionGroup;
return operation;
}
@end

View File

@ -0,0 +1,326 @@
// AFHTTPRequestOperationManager.h
// Copyright (c) 20112015 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
#import <SystemConfiguration/SystemConfiguration.h>
#import <Availability.h>
#if __IPHONE_OS_VERSION_MIN_REQUIRED
#import <MobileCoreServices/MobileCoreServices.h>
#else
#import <CoreServices/CoreServices.h>
#endif
#import "AFHTTPRequestOperation.h"
#import "AFURLResponseSerialization.h"
#import "AFURLRequestSerialization.h"
#import "AFSecurityPolicy.h"
#import "AFNetworkReachabilityManager.h"
#ifndef NS_DESIGNATED_INITIALIZER
#if __has_attribute(objc_designated_initializer)
#define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))
#else
#define NS_DESIGNATED_INITIALIZER
#endif
#endif
NS_ASSUME_NONNULL_BEGIN
/**
`AFHTTPRequestOperationManager` encapsulates the common patterns of communicating with a web application over HTTP, including request creation, response serialization, network reachability monitoring, and security, as well as request operation management.
## Subclassing Notes
Developers targeting iOS 7 or Mac OS X 10.9 or later that deal extensively with a web service are encouraged to subclass `AFHTTPSessionManager`, providing a class method that returns a shared singleton object on which authentication and other configuration can be shared across the application.
For developers targeting iOS 6 or Mac OS X 10.8 or earlier, `AFHTTPRequestOperationManager` may be used to similar effect.
## Methods to Override
To change the behavior of all request operation construction for an `AFHTTPRequestOperationManager` subclass, override `HTTPRequestOperationWithRequest:success:failure`.
## Serialization
Requests created by an HTTP client will contain default headers and encode parameters according to the `requestSerializer` property, which is an object conforming to `<AFURLRequestSerialization>`.
Responses received from the server are automatically validated and serialized by the `responseSerializers` property, which is an object conforming to `<AFURLResponseSerialization>`
## URL Construction Using Relative Paths
For HTTP convenience methods, the request serializer constructs URLs from the path relative to the `-baseURL`, using `NSURL +URLWithString:relativeToURL:`, when provided. If `baseURL` is `nil`, `path` needs to resolve to a valid `NSURL` object using `NSURL +URLWithString:`.
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/
Also important to note is that a trailing slash will be added to any `baseURL` without one. This would otherwise cause unexpected behavior when constructing URLs using paths without a leading slash.
## Network Reachability Monitoring
Network reachability status and change monitoring is available through the `reachabilityManager` property. Applications may choose to monitor network reachability conditions in order to prevent or suspend any outbound requests. See `AFNetworkReachabilityManager` for more details.
## NSSecureCoding & NSCopying Caveats
`AFHTTPRequestOperationManager` conforms to the `NSSecureCoding` 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.
- NSSecureCoding cannot serialize / deserialize block properties, so an archive of an HTTP client will not include any reachability callback block that may be set.
*/
@interface AFHTTPRequestOperationManager : NSObject <NSSecureCoding, NSCopying>
/**
The URL used to monitor reachability, and construct requests from relative paths in methods like `requestWithMethod:URLString:parameters:`, and the `GET` / `POST` / et al. convenience methods.
*/
@property (readonly, nonatomic, strong, nullable) NSURL *baseURL;
/**
Requests created with `requestWithMethod:URLString:parameters:` & `multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:` are constructed with a set of default headers using a parameter serialization specified by this property. By default, this is set to an instance of `AFHTTPRequestSerializer`, which serializes query string parameters for `GET`, `HEAD`, and `DELETE` requests, or otherwise URL-form-encodes HTTP message bodies.
@warning `requestSerializer` must not be `nil`.
*/
@property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;
/**
Responses sent from the server in data tasks created with `dataTaskWithRequest:success:failure:` and run using the `GET` / `POST` / et al. convenience methods are automatically validated and serialized by the response serializer. By default, this property is set to a JSON serializer, which serializes data from responses with a `application/json` MIME type, and falls back to the raw data object. The serializer validates the status code to be in the `2XX` range, denoting success. If the response serializer generates an error in `-responseObjectForResponse:data:error:`, the `failure` callback of the session task or request operation will be executed; otherwise, the `success` callback will be executed.
@warning `responseSerializer` must not be `nil`.
*/
@property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;
/**
The operation queue on which request operations are scheduled and run.
*/
@property (nonatomic, strong) NSOperationQueue *operationQueue;
///-------------------------------
/// @name Managing URL Credentials
///-------------------------------
/**
Whether request operations should consult the credential storage for authenticating the connection. `YES` by default.
@see AFURLConnectionOperation -shouldUseCredentialStorage
*/
@property (nonatomic, assign) BOOL shouldUseCredentialStorage;
/**
The credential used by request operations for authentication challenges.
@see AFURLConnectionOperation -credential
*/
@property (nonatomic, strong, nullable) NSURLCredential *credential;
///-------------------------------
/// @name Managing Security Policy
///-------------------------------
/**
The security policy used by created request operations to evaluate server trust for secure connections. `AFHTTPRequestOperationManager` uses the `defaultPolicy` unless otherwise specified.
*/
@property (nonatomic, strong) AFSecurityPolicy *securityPolicy;
///------------------------------------
/// @name Managing Network Reachability
///------------------------------------
/**
The network reachability manager. `AFHTTPRequestOperationManager` uses the `sharedManager` by default.
*/
@property (readwrite, nonatomic, strong) AFNetworkReachabilityManager *reachabilityManager;
///-------------------------------
/// @name Managing Callback Queues
///-------------------------------
/**
The dispatch queue for the `completionBlock` of request operations. If `NULL` (default), the main queue is used.
*/
#if OS_OBJECT_USE_OBJC
@property (nonatomic, strong, nullable) dispatch_queue_t completionQueue;
#else
@property (nonatomic, assign, nullable) dispatch_queue_t completionQueue;
#endif
/**
The dispatch group for the `completionBlock` of request operations. If `NULL` (default), a private dispatch group is used.
*/
#if OS_OBJECT_USE_OBJC
@property (nonatomic, strong, nullable) dispatch_group_t completionGroup;
#else
@property (nonatomic, assign, nullable) dispatch_group_t completionGroup;
#endif
///---------------------------------------------
/// @name Creating and Initializing HTTP Clients
///---------------------------------------------
/**
Creates and returns an `AFHTTPRequestOperationManager` object.
*/
+ (instancetype)manager;
/**
Initializes an `AFHTTPRequestOperationManager` object with the specified base URL.
This is the designated initializer.
@param url The base URL for the HTTP client.
@return The newly-initialized HTTP client
*/
- (instancetype)initWithBaseURL:(nullable NSURL *)url NS_DESIGNATED_INITIALIZER;
///---------------------------------------
/// @name Managing HTTP Request Operations
///---------------------------------------
/**
Creates an `AFHTTPRequestOperation`, and sets the response serializers to that of the HTTP client.
@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 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:(nullable void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(nullable void (^)(AFHTTPRequestOperation *operation, NSError *error))failure;
///---------------------------
/// @name Making HTTP Requests
///---------------------------
/**
Creates and runs an `AFHTTPRequestOperation` with a `GET` request.
@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@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 request operation, and the response object created by the client response serializer.
@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 a two arguments: the request operation and the error describing the network or parsing error that occurred.
@see -HTTPRequestOperationWithRequest:success:failure:
*/
- (nullable AFHTTPRequestOperation *)GET:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(nullable void (^)(AFHTTPRequestOperation * __nullable operation, NSError *error))failure;
/**
Creates and runs an `AFHTTPRequestOperation` with a `HEAD` request.
@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param success A block object to be executed when the request operation finishes successfully. This block has no return value and takes a single arguments: the request operation.
@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 a two arguments: the request operation and the error describing the network or parsing error that occurred.
@see -HTTPRequestOperationWithRequest:success:failure:
*/
- (nullable AFHTTPRequestOperation *)HEAD:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(AFHTTPRequestOperation *operation))success
failure:(nullable void (^)(AFHTTPRequestOperation * __nullable operation, NSError *error))failure;
/**
Creates and runs an `AFHTTPRequestOperation` with a `POST` request.
@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@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 request operation, and the response object created by the client response serializer.
@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 a two arguments: the request operation and the error describing the network or parsing error that occurred.
@see -HTTPRequestOperationWithRequest:success:failure:
*/
- (nullable AFHTTPRequestOperation *)POST:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(nullable void (^)(AFHTTPRequestOperation * __nullable operation, NSError *error))failure;
/**
Creates and runs an `AFHTTPRequestOperation` with a multipart `POST` request.
@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param block A block that takes a single argument and appends data to the HTTP body. The block argument is an object adopting the `AFMultipartFormData` protocol.
@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 request operation, and the response object created by the client response serializer.
@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 a two arguments: the request operation and the error describing the network or parsing error that occurred.
@see -HTTPRequestOperationWithRequest:success:failure:
*/
- (nullable AFHTTPRequestOperation *)POST:(NSString *)URLString
parameters:(nullable id)parameters
constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
success:(nullable void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(nullable void (^)(AFHTTPRequestOperation * __nullable operation, NSError *error))failure;
/**
Creates and runs an `AFHTTPRequestOperation` with a `PUT` request.
@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@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 request operation, and the response object created by the client response serializer.
@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 a two arguments: the request operation and the error describing the network or parsing error that occurred.
@see -HTTPRequestOperationWithRequest:success:failure:
*/
- (nullable AFHTTPRequestOperation *)PUT:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(nullable void (^)(AFHTTPRequestOperation * __nullable operation, NSError *error))failure;
/**
Creates and runs an `AFHTTPRequestOperation` with a `PATCH` request.
@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@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 request operation, and the response object created by the client response serializer.
@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 a two arguments: the request operation and the error describing the network or parsing error that occurred.
@see -HTTPRequestOperationWithRequest:success:failure:
*/
- (nullable AFHTTPRequestOperation *)PATCH:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(nullable void (^)(AFHTTPRequestOperation * __nullable operation, NSError *error))failure;
/**
Creates and runs an `AFHTTPRequestOperation` with a `DELETE` request.
@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@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 request operation, and the response object created by the client response serializer.
@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 a two arguments: the request operation and the error describing the network or parsing error that occurred.
@see -HTTPRequestOperationWithRequest:success:failure:
*/
- (nullable AFHTTPRequestOperation *)DELETE:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(nullable void (^)(AFHTTPRequestOperation * __nullable operation, NSError *error))failure;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,290 @@
// AFHTTPRequestOperationManager.m
// Copyright (c) 20112015 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
#import "AFHTTPRequestOperationManager.h"
#import "AFHTTPRequestOperation.h"
#import <Availability.h>
#import <Security/Security.h>
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
#import <UIKit/UIKit.h>
#endif
@interface AFHTTPRequestOperationManager ()
@property (readwrite, nonatomic, strong) NSURL *baseURL;
@end
@implementation AFHTTPRequestOperationManager
+ (instancetype)manager {
return [[self alloc] initWithBaseURL:nil];
}
- (instancetype)init {
return [self initWithBaseURL:nil];
}
- (instancetype)initWithBaseURL:(NSURL *)url {
self = [super init];
if (!self) {
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.requestSerializer = [AFHTTPRequestSerializer serializer];
self.responseSerializer = [AFJSONResponseSerializer serializer];
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
self.operationQueue = [[NSOperationQueue alloc] init];
self.shouldUseCredentialStorage = YES;
return self;
}
#pragma mark -
#ifdef _SYSTEMCONFIGURATION_H
#endif
- (void)setRequestSerializer:(AFHTTPRequestSerializer <AFURLRequestSerialization> *)requestSerializer {
NSParameterAssert(requestSerializer);
_requestSerializer = requestSerializer;
}
- (void)setResponseSerializer:(AFHTTPResponseSerializer <AFURLResponseSerialization> *)responseSerializer {
NSParameterAssert(responseSerializer);
_responseSerializer = responseSerializer;
}
#pragma mark -
- (AFHTTPRequestOperation *)HTTPRequestOperationWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
if (serializationError) {
if (failure) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
#pragma clang diagnostic pop
}
return nil;
}
return [self HTTPRequestOperationWithRequest:request success:success failure:failure];
}
- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)request
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = self.responseSerializer;
operation.shouldUseCredentialStorage = self.shouldUseCredentialStorage;
operation.credential = self.credential;
operation.securityPolicy = self.securityPolicy;
[operation setCompletionBlockWithSuccess:success failure:failure];
operation.completionQueue = self.completionQueue;
operation.completionGroup = self.completionGroup;
return operation;
}
#pragma mark -
- (AFHTTPRequestOperation *)GET:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:@"GET" URLString:URLString parameters:parameters success:success failure:failure];
[self.operationQueue addOperation:operation];
return operation;
}
- (AFHTTPRequestOperation *)HEAD:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(AFHTTPRequestOperation *operation))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:@"HEAD" URLString:URLString parameters:parameters success:^(AFHTTPRequestOperation *requestOperation, __unused id responseObject) {
if (success) {
success(requestOperation);
}
} failure:failure];
[self.operationQueue addOperation:operation];
return operation;
}
- (AFHTTPRequestOperation *)POST:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:@"POST" URLString:URLString parameters:parameters success:success failure:failure];
[self.operationQueue addOperation:operation];
return operation;
}
- (AFHTTPRequestOperation *)POST:(NSString *)URLString
parameters:(id)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer multipartFormRequestWithMethod:@"POST" URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters constructingBodyWithBlock:block error:&serializationError];
if (serializationError) {
if (failure) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
#pragma clang diagnostic pop
}
return nil;
}
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:success failure:failure];
[self.operationQueue addOperation:operation];
return operation;
}
- (AFHTTPRequestOperation *)PUT:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:@"PUT" URLString:URLString parameters:parameters success:success failure:failure];
[self.operationQueue addOperation:operation];
return operation;
}
- (AFHTTPRequestOperation *)PATCH:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:@"PATCH" URLString:URLString parameters:parameters success:success failure:failure];
[self.operationQueue addOperation:operation];
return operation;
}
- (AFHTTPRequestOperation *)DELETE:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:@"DELETE" URLString:URLString parameters:parameters success:success failure:failure];
[self.operationQueue addOperation:operation];
return operation;
}
#pragma mark - NSObject
- (NSString *)description {
return [NSString stringWithFormat:@"<%@: %p, baseURL: %@, operationQueue: %@>", NSStringFromClass([self class]), self, [self.baseURL absoluteString], self.operationQueue];
}
#pragma mark - NSSecureCoding
+ (BOOL)supportsSecureCoding {
return YES;
}
- (id)initWithCoder:(NSCoder *)decoder {
NSURL *baseURL = [decoder decodeObjectForKey:NSStringFromSelector(@selector(baseURL))];
self = [self initWithBaseURL:baseURL];
if (!self) {
return nil;
}
self.requestSerializer = [decoder decodeObjectOfClass:[AFHTTPRequestSerializer class] forKey:NSStringFromSelector(@selector(requestSerializer))];
self.responseSerializer = [decoder decodeObjectOfClass:[AFHTTPResponseSerializer class] forKey:NSStringFromSelector(@selector(responseSerializer))];
AFSecurityPolicy *decodedPolicy = [decoder decodeObjectOfClass:[AFSecurityPolicy class] forKey:NSStringFromSelector(@selector(securityPolicy))];
if (decodedPolicy) {
self.securityPolicy = decodedPolicy;
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.baseURL forKey:NSStringFromSelector(@selector(baseURL))];
[coder encodeObject:self.requestSerializer forKey:NSStringFromSelector(@selector(requestSerializer))];
[coder encodeObject:self.responseSerializer forKey:NSStringFromSelector(@selector(responseSerializer))];
[coder encodeObject:self.securityPolicy forKey:NSStringFromSelector(@selector(securityPolicy))];
}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {
AFHTTPRequestOperationManager *HTTPClient = [[[self class] allocWithZone:zone] initWithBaseURL:self.baseURL];
HTTPClient.requestSerializer = [self.requestSerializer copyWithZone:zone];
HTTPClient.responseSerializer = [self.responseSerializer copyWithZone:zone];
HTTPClient.securityPolicy = [self.securityPolicy copyWithZone:zone];
return HTTPClient;
}
@end

View File

@ -0,0 +1,253 @@
// AFHTTPSessionManager.h
// Copyright (c) 20112015 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
#if !TARGET_OS_WATCH
#import <SystemConfiguration/SystemConfiguration.h>
#endif
#import <Availability.h>
#if __IPHONE_OS_VERSION_MIN_REQUIRED
#import <MobileCoreServices/MobileCoreServices.h>
#else
#import <CoreServices/CoreServices.h>
#endif
#import "AFURLSessionManager.h"
#ifndef NS_DESIGNATED_INITIALIZER
#if __has_attribute(objc_designated_initializer)
#define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))
#else
#define NS_DESIGNATED_INITIALIZER
#endif
#endif
/**
`AFHTTPSessionManager` is a subclass of `AFURLSessionManager` with convenience methods for making HTTP requests. When a `baseURL` is provided, requests made with the `GET` / `POST` / et al. convenience methods can be made with relative paths.
## Subclassing Notes
Developers targeting iOS 7 or Mac OS X 10.9 or later that deal extensively with a web service are encouraged to subclass `AFHTTPSessionManager`, providing a class method that returns a shared singleton object on which authentication and other configuration can be shared across the application.
For developers targeting iOS 6 or Mac OS X 10.8 or earlier, `AFHTTPRequestOperationManager` may be used to similar effect.
## Methods to Override
To change the behavior of all data task operation construction, which is also used in the `GET` / `POST` / et al. convenience methods, override `dataTaskWithRequest:completionHandler:`.
## Serialization
Requests created by an HTTP client will contain default headers and encode parameters according to the `requestSerializer` property, which is an object conforming to `<AFURLRequestSerialization>`.
Responses received from the server are automatically validated and serialized by the `responseSerializers` property, which is an object conforming to `<AFURLResponseSerialization>`
## URL Construction Using Relative Paths
For HTTP convenience methods, the request serializer constructs URLs from the path relative to the `-baseURL`, using `NSURL +URLWithString:relativeToURL:`, when provided. If `baseURL` is `nil`, `path` needs to resolve to a valid `NSURL` object using `NSURL +URLWithString:`.
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/
Also important to note is that a trailing slash will be added to any `baseURL` without one. This would otherwise cause unexpected behavior when constructing URLs using paths without a leading slash.
@warning Managers for background sessions must be owned for the duration of their use. This can be accomplished by creating an application-wide or shared singleton instance.
*/
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1090) || TARGET_OS_WATCH
NS_ASSUME_NONNULL_BEGIN
@interface AFHTTPSessionManager : AFURLSessionManager <NSSecureCoding, NSCopying>
/**
The URL used to construct requests from relative paths in methods like `requestWithMethod:URLString:parameters:`, and the `GET` / `POST` / et al. convenience methods.
*/
@property (readonly, nonatomic, strong, nullable) NSURL *baseURL;
/**
Requests created with `requestWithMethod:URLString:parameters:` & `multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:` are constructed with a set of default headers using a parameter serialization specified by this property. By default, this is set to an instance of `AFHTTPRequestSerializer`, which serializes query string parameters for `GET`, `HEAD`, and `DELETE` requests, or otherwise URL-form-encodes HTTP message bodies.
@warning `requestSerializer` must not be `nil`.
*/
@property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;
/**
Responses sent from the server in data tasks created with `dataTaskWithRequest:success:failure:` and run using the `GET` / `POST` / et al. convenience methods are automatically validated and serialized by the response serializer. By default, this property is set to an instance of `AFJSONResponseSerializer`.
@warning `responseSerializer` must not be `nil`.
*/
@property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;
///---------------------
/// @name Initialization
///---------------------
/**
Creates and returns an `AFHTTPSessionManager` object.
*/
+ (instancetype)manager;
/**
Initializes an `AFHTTPSessionManager` object with the specified base URL.
@param url The base URL for the HTTP client.
@return The newly-initialized HTTP client
*/
- (instancetype)initWithBaseURL:(nullable NSURL *)url;
/**
Initializes an `AFHTTPSessionManager` object with the specified base URL.
This is the designated initializer.
@param url The base URL for the HTTP client.
@param configuration The configuration used to create the managed session.
@return The newly-initialized HTTP client
*/
- (instancetype)initWithBaseURL:(nullable NSURL *)url
sessionConfiguration:(nullable NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER;
///---------------------------
/// @name Making HTTP Requests
///---------------------------
/**
Creates and runs an `NSURLSessionDataTask` with a `GET` request.
@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer.
@param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred.
@see -dataTaskWithRequest:completionHandler:
*/
- (nullable NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure;
/**
Creates and runs an `NSURLSessionDataTask` with a `HEAD` request.
@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param success A block object to be executed when the task finishes successfully. This block has no return value and takes a single arguments: the data task.
@param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred.
@see -dataTaskWithRequest:completionHandler:
*/
- (nullable NSURLSessionDataTask *)HEAD:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task))success
failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure;
/**
Creates and runs an `NSURLSessionDataTask` with a `POST` request.
@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer.
@param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred.
@see -dataTaskWithRequest:completionHandler:
*/
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure;
/**
Creates and runs an `NSURLSessionDataTask` with a multipart `POST` request.
@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param block A block that takes a single argument and appends data to the HTTP body. The block argument is an object adopting the `AFMultipartFormData` protocol.
@param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer.
@param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred.
@see -dataTaskWithRequest:completionHandler:
*/
- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(nullable id)parameters
constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
success:(nullable void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure;
/**
Creates and runs an `NSURLSessionDataTask` with a `PUT` request.
@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer.
@param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred.
@see -dataTaskWithRequest:completionHandler:
*/
- (nullable NSURLSessionDataTask *)PUT:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure;
/**
Creates and runs an `NSURLSessionDataTask` with a `PATCH` request.
@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer.
@param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred.
@see -dataTaskWithRequest:completionHandler:
*/
- (nullable NSURLSessionDataTask *)PATCH:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure;
/**
Creates and runs an `NSURLSessionDataTask` with a `DELETE` request.
@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded according to the client request serializer.
@param success A block object to be executed when the task finishes successfully. This block has no return value and takes two arguments: the data task, and the response object created by the client response serializer.
@param failure A block object to be executed when the task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a two arguments: the data task and the error describing the network or parsing error that occurred.
@see -dataTaskWithRequest:completionHandler:
*/
- (nullable NSURLSessionDataTask *)DELETE:(NSString *)URLString
parameters:(nullable id)parameters
success:(nullable void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(nullable void (^)(NSURLSessionDataTask * __nullable task, NSError *error))failure;
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,328 @@
// AFHTTPSessionManager.m
// Copyright (c) 20112015 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "AFHTTPSessionManager.h"
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1090) || TARGET_WATCH_OS
#import "AFURLRequestSerialization.h"
#import "AFURLResponseSerialization.h"
#import <Availability.h>
#import <Security/Security.h>
#ifdef _SYSTEMCONFIGURATION_H
#import <netinet/in.h>
#import <netinet6/in6.h>
#import <arpa/inet.h>
#import <ifaddrs.h>
#import <netdb.h>
#endif
#if TARGET_OS_IOS
#import <UIKit/UIKit.h>
#elif TARGET_OS_WATCH
#import <WatchKit/WatchKit.h>
#endif
@interface AFHTTPSessionManager ()
@property (readwrite, nonatomic, strong) NSURL *baseURL;
@end
@implementation AFHTTPSessionManager
@dynamic responseSerializer;
+ (instancetype)manager {
return [[[self class] alloc] initWithBaseURL:nil];
}
- (instancetype)init {
return [self initWithBaseURL:nil];
}
- (instancetype)initWithBaseURL:(NSURL *)url {
return [self initWithBaseURL:url sessionConfiguration:nil];
}
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
return [self initWithBaseURL:nil sessionConfiguration:configuration];
}
- (instancetype)initWithBaseURL:(NSURL *)url
sessionConfiguration:(NSURLSessionConfiguration *)configuration
{
self = [super initWithSessionConfiguration:configuration];
if (!self) {
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.requestSerializer = [AFHTTPRequestSerializer serializer];
self.responseSerializer = [AFJSONResponseSerializer serializer];
return self;
}
#pragma mark -
#ifdef _SYSTEMCONFIGURATION_H
#endif
- (void)setRequestSerializer:(AFHTTPRequestSerializer <AFURLRequestSerialization> *)requestSerializer {
NSParameterAssert(requestSerializer);
_requestSerializer = requestSerializer;
}
- (void)setResponseSerializer:(AFHTTPResponseSerializer <AFURLResponseSerialization> *)responseSerializer {
NSParameterAssert(responseSerializer);
[super setResponseSerializer:responseSerializer];
}
#pragma mark -
- (NSURLSessionDataTask *)GET:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET" URLString:URLString parameters:parameters success:success failure:failure];
[dataTask resume];
return dataTask;
}
- (NSURLSessionDataTask *)HEAD:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(NSURLSessionDataTask *task))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"HEAD" URLString:URLString parameters:parameters success:^(NSURLSessionDataTask *task, __unused id responseObject) {
if (success) {
success(task);
}
} failure:failure];
[dataTask resume];
return dataTask;
}
- (NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"POST" URLString:URLString parameters:parameters success:success failure:failure];
[dataTask resume];
return dataTask;
}
- (NSURLSessionDataTask *)POST:(NSString *)URLString
parameters:(id)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer multipartFormRequestWithMethod:@"POST" URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters constructingBodyWithBlock:block error:&serializationError];
if (serializationError) {
if (failure) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
#pragma clang diagnostic pop
}
return nil;
}
__block NSURLSessionDataTask *task = [self uploadTaskWithStreamedRequest:request progress:nil completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
if (error) {
if (failure) {
failure(task, error);
}
} else {
if (success) {
success(task, responseObject);
}
}
}];
[task resume];
return task;
}
- (NSURLSessionDataTask *)PUT:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"PUT" URLString:URLString parameters:parameters success:success failure:failure];
[dataTask resume];
return dataTask;
}
- (NSURLSessionDataTask *)PATCH:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"PATCH" URLString:URLString parameters:parameters success:success failure:failure];
[dataTask resume];
return dataTask;
}
- (NSURLSessionDataTask *)DELETE:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{
NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"DELETE" URLString:URLString parameters:parameters success:success failure:failure];
[dataTask resume];
return dataTask;
}
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
success:(void (^)(NSURLSessionDataTask *, id))success
failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{
NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
if (serializationError) {
if (failure) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
#pragma clang diagnostic pop
}
return nil;
}
__block NSURLSessionDataTask *dataTask = nil;
dataTask = [self dataTaskWithRequest:request completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
if (error) {
if (failure) {
failure(dataTask, error);
}
} else {
if (success) {
success(dataTask, responseObject);
}
}
}];
return dataTask;
}
#pragma mark - NSObject
- (NSString *)description {
return [NSString stringWithFormat:@"<%@: %p, baseURL: %@, session: %@, operationQueue: %@>", NSStringFromClass([self class]), self, [self.baseURL absoluteString], self.session, self.operationQueue];
}
#pragma mark - NSSecureCoding
+ (BOOL)supportsSecureCoding {
return YES;
}
- (id)initWithCoder:(NSCoder *)decoder {
NSURL *baseURL = [decoder decodeObjectOfClass:[NSURL class] forKey:NSStringFromSelector(@selector(baseURL))];
NSURLSessionConfiguration *configuration = [decoder decodeObjectOfClass:[NSURLSessionConfiguration class] forKey:@"sessionConfiguration"];
if (!configuration) {
NSString *configurationIdentifier = [decoder decodeObjectOfClass:[NSString class] forKey:@"identifier"];
if (configurationIdentifier) {
#if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 80000) || (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 1100)
configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:configurationIdentifier];
#else
configuration = [NSURLSessionConfiguration backgroundSessionConfiguration:configurationIdentifier];
#endif
}
}
self = [self initWithBaseURL:baseURL sessionConfiguration:configuration];
if (!self) {
return nil;
}
self.requestSerializer = [decoder decodeObjectOfClass:[AFHTTPRequestSerializer class] forKey:NSStringFromSelector(@selector(requestSerializer))];
self.responseSerializer = [decoder decodeObjectOfClass:[AFHTTPResponseSerializer class] forKey:NSStringFromSelector(@selector(responseSerializer))];
AFSecurityPolicy *decodedPolicy = [decoder decodeObjectOfClass:[AFSecurityPolicy class] forKey:NSStringFromSelector(@selector(securityPolicy))];
if (decodedPolicy) {
self.securityPolicy = decodedPolicy;
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[super encodeWithCoder:coder];
[coder encodeObject:self.baseURL forKey:NSStringFromSelector(@selector(baseURL))];
if ([self.session.configuration conformsToProtocol:@protocol(NSCoding)]) {
[coder encodeObject:self.session.configuration forKey:@"sessionConfiguration"];
} else {
[coder encodeObject:self.session.configuration.identifier forKey:@"identifier"];
}
[coder encodeObject:self.requestSerializer forKey:NSStringFromSelector(@selector(requestSerializer))];
[coder encodeObject:self.responseSerializer forKey:NSStringFromSelector(@selector(responseSerializer))];
[coder encodeObject:self.securityPolicy forKey:NSStringFromSelector(@selector(securityPolicy))];
}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {
AFHTTPSessionManager *HTTPClient = [[[self class] allocWithZone:zone] initWithBaseURL:self.baseURL sessionConfiguration:self.session.configuration];
HTTPClient.requestSerializer = [self.requestSerializer copyWithZone:zone];
HTTPClient.responseSerializer = [self.responseSerializer copyWithZone:zone];
HTTPClient.securityPolicy = [self.securityPolicy copyWithZone:zone];
return HTTPClient;
}
@end
#endif

View File

@ -0,0 +1,207 @@
// AFNetworkReachabilityManager.h
// Copyright (c) 20112015 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
#if !TARGET_OS_WATCH
#import <SystemConfiguration/SystemConfiguration.h>
#ifndef NS_DESIGNATED_INITIALIZER
#if __has_attribute(objc_designated_initializer)
#define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))
#else
#define NS_DESIGNATED_INITIALIZER
#endif
#endif
typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
AFNetworkReachabilityStatusUnknown = -1,
AFNetworkReachabilityStatusNotReachable = 0,
AFNetworkReachabilityStatusReachableViaWWAN = 1,
AFNetworkReachabilityStatusReachableViaWiFi = 2,
};
NS_ASSUME_NONNULL_BEGIN
/**
`AFNetworkReachabilityManager` monitors the reachability of domains, and addresses for both WWAN and WiFi network interfaces.
Reachability can be used to determine background information about why a network operation failed, or to trigger a network operation retrying when a connection is established. It should not be used to prevent a user from initiating a network request, as it's possible that an initial request may be required to establish reachability.
See Apple's Reachability Sample Code (https://developer.apple.com/library/ios/samplecode/reachability/)
@warning Instances of `AFNetworkReachabilityManager` must be started with `-startMonitoring` before reachability status can be determined.
*/
@interface AFNetworkReachabilityManager : NSObject
/**
The current network reachability status.
*/
@property (readonly, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus;
/**
Whether or not the network is currently reachable.
*/
@property (readonly, nonatomic, assign, getter = isReachable) BOOL reachable;
/**
Whether or not the network is currently reachable via WWAN.
*/
@property (readonly, nonatomic, assign, getter = isReachableViaWWAN) BOOL reachableViaWWAN;
/**
Whether or not the network is currently reachable via WiFi.
*/
@property (readonly, nonatomic, assign, getter = isReachableViaWiFi) BOOL reachableViaWiFi;
///---------------------
/// @name Initialization
///---------------------
/**
Returns the shared network reachability manager.
*/
+ (instancetype)sharedManager;
/**
Creates and returns a network reachability manager for the specified domain.
@param domain The domain used to evaluate network reachability.
@return An initialized network reachability manager, actively monitoring the specified domain.
*/
+ (instancetype)managerForDomain:(NSString *)domain;
/**
Creates and returns a network reachability manager for the socket address.
@param address The socket address (`sockaddr_in`) used to evaluate network reachability.
@return An initialized network reachability manager, actively monitoring the specified socket address.
*/
+ (instancetype)managerForAddress:(const void *)address;
/**
Initializes an instance of a network reachability manager from the specified reachability object.
@param reachability The reachability object to monitor.
@return An initialized network reachability manager, actively monitoring the specified reachability.
*/
- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability NS_DESIGNATED_INITIALIZER;
///--------------------------------------------------
/// @name Starting & Stopping Reachability Monitoring
///--------------------------------------------------
/**
Starts monitoring for changes in network reachability status.
*/
- (void)startMonitoring;
/**
Stops monitoring for changes in network reachability status.
*/
- (void)stopMonitoring;
///-------------------------------------------------
/// @name Getting Localized Reachability Description
///-------------------------------------------------
/**
Returns a localized string representation of the current network reachability status.
*/
- (NSString *)localizedNetworkReachabilityStatusString;
///---------------------------------------------------
/// @name Setting Network Reachability Change Callback
///---------------------------------------------------
/**
Sets a callback to be executed when the network availability of the `baseURL` host changes.
@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`.
*/
- (void)setReachabilityStatusChangeBlock:(nullable void (^)(AFNetworkReachabilityStatus status))block;
@end
///----------------
/// @name Constants
///----------------
/**
## Network Reachability
The following constants are provided by `AFNetworkReachabilityManager` 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.
*/
///--------------------
/// @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 <SystemConfiguration/SystemConfiguration.h>` to the header prefix of the project (`Prefix.pch`).
*/
FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityDidChangeNotification;
FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityNotificationStatusItem;
///--------------------
/// @name Functions
///--------------------
/**
Returns a localized string representation of an `AFNetworkReachabilityStatus` value.
*/
FOUNDATION_EXPORT NSString * AFStringFromNetworkReachabilityStatus(AFNetworkReachabilityStatus status);
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,248 @@
// AFNetworkReachabilityManager.m
// Copyright (c) 20112015 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "AFNetworkReachabilityManager.h"
#if !TARGET_OS_WATCH
#import <netinet/in.h>
#import <netinet6/in6.h>
#import <arpa/inet.h>
#import <ifaddrs.h>
#import <netdb.h>
NSString * const AFNetworkingReachabilityDidChangeNotification = @"com.alamofire.networking.reachability.change";
NSString * const AFNetworkingReachabilityNotificationStatusItem = @"AFNetworkingReachabilityNotificationStatusItem";
typedef void (^AFNetworkReachabilityStatusBlock)(AFNetworkReachabilityStatus status);
NSString * AFStringFromNetworkReachabilityStatus(AFNetworkReachabilityStatus status) {
switch (status) {
case AFNetworkReachabilityStatusNotReachable:
return NSLocalizedStringFromTable(@"Not Reachable", @"AFNetworking", nil);
case AFNetworkReachabilityStatusReachableViaWWAN:
return NSLocalizedStringFromTable(@"Reachable via WWAN", @"AFNetworking", nil);
case AFNetworkReachabilityStatusReachableViaWiFi:
return NSLocalizedStringFromTable(@"Reachable via WiFi", @"AFNetworking", nil);
case AFNetworkReachabilityStatusUnknown:
default:
return NSLocalizedStringFromTable(@"Unknown", @"AFNetworking", nil);
}
}
static AFNetworkReachabilityStatus AFNetworkReachabilityStatusForFlags(SCNetworkReachabilityFlags flags) {
BOOL isReachable = ((flags & kSCNetworkReachabilityFlagsReachable) != 0);
BOOL needsConnection = ((flags & kSCNetworkReachabilityFlagsConnectionRequired) != 0);
BOOL canConnectionAutomatically = (((flags & kSCNetworkReachabilityFlagsConnectionOnDemand ) != 0) || ((flags & kSCNetworkReachabilityFlagsConnectionOnTraffic) != 0));
BOOL canConnectWithoutUserInteraction = (canConnectionAutomatically && (flags & kSCNetworkReachabilityFlagsInterventionRequired) == 0);
BOOL isNetworkReachable = (isReachable && (!needsConnection || canConnectWithoutUserInteraction));
AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusUnknown;
if (isNetworkReachable == NO) {
status = AFNetworkReachabilityStatusNotReachable;
}
#if TARGET_OS_IPHONE
else if ((flags & kSCNetworkReachabilityFlagsIsWWAN) != 0) {
status = AFNetworkReachabilityStatusReachableViaWWAN;
}
#endif
else {
status = AFNetworkReachabilityStatusReachableViaWiFi;
}
return status;
}
/**
* Queue a status change notification for the main thread.
*
* This is done to ensure that the notifications are received in the same order
* as they are sent. If notifications are sent directly, it is possible that
* a queued notification (for an earlier status condition) is processed after
* the later update, resulting in the listener being left in the wrong state.
*/
static void AFPostReachabilityStatusChange(SCNetworkReachabilityFlags flags, AFNetworkReachabilityStatusBlock block) {
AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags);
dispatch_async(dispatch_get_main_queue(), ^{
if (block) {
block(status);
}
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
NSDictionary *userInfo = @{ AFNetworkingReachabilityNotificationStatusItem: @(status) };
[notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification object:nil userInfo:userInfo];
});
}
static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) {
AFPostReachabilityStatusChange(flags, (__bridge AFNetworkReachabilityStatusBlock)info);
}
static const void * AFNetworkReachabilityRetainCallback(const void *info) {
return Block_copy(info);
}
static void AFNetworkReachabilityReleaseCallback(const void *info) {
if (info) {
Block_release(info);
}
}
@interface AFNetworkReachabilityManager ()
@property (readwrite, nonatomic, strong) id networkReachability;
@property (readwrite, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus;
@property (readwrite, nonatomic, copy) AFNetworkReachabilityStatusBlock networkReachabilityStatusBlock;
@end
@implementation AFNetworkReachabilityManager
+ (instancetype)sharedManager {
static AFNetworkReachabilityManager *_sharedManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_len = sizeof(address);
address.sin_family = AF_INET;
_sharedManager = [self managerForAddress:&address];
});
return _sharedManager;
}
#ifndef __clang_analyzer__
+ (instancetype)managerForDomain:(NSString *)domain {
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithName(kCFAllocatorDefault, [domain UTF8String]);
AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];
return manager;
}
#endif
#ifndef __clang_analyzer__
+ (instancetype)managerForAddress:(const void *)address {
SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)address);
AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability];
return manager;
}
#endif
- (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability {
self = [super init];
if (!self) {
return nil;
}
self.networkReachability = CFBridgingRelease(reachability);
self.networkReachabilityStatus = AFNetworkReachabilityStatusUnknown;
return self;
}
- (instancetype)init NS_UNAVAILABLE
{
return nil;
}
- (void)dealloc {
[self stopMonitoring];
}
#pragma mark -
- (BOOL)isReachable {
return [self isReachableViaWWAN] || [self isReachableViaWiFi];
}
- (BOOL)isReachableViaWWAN {
return self.networkReachabilityStatus == AFNetworkReachabilityStatusReachableViaWWAN;
}
- (BOOL)isReachableViaWiFi {
return self.networkReachabilityStatus == AFNetworkReachabilityStatusReachableViaWiFi;
}
#pragma mark -
- (void)startMonitoring {
[self stopMonitoring];
if (!self.networkReachability) {
return;
}
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}
};
id networkReachability = self.networkReachability;
SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
SCNetworkReachabilitySetCallback((__bridge SCNetworkReachabilityRef)networkReachability, AFNetworkReachabilityCallback, &context);
SCNetworkReachabilityScheduleWithRunLoop((__bridge SCNetworkReachabilityRef)networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
SCNetworkReachabilityFlags flags;
if (SCNetworkReachabilityGetFlags((__bridge SCNetworkReachabilityRef)networkReachability, &flags)) {
AFPostReachabilityStatusChange(flags, callback);
}
});
}
- (void)stopMonitoring {
if (!self.networkReachability) {
return;
}
SCNetworkReachabilityUnscheduleFromRunLoop((__bridge SCNetworkReachabilityRef)self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
}
#pragma mark -
- (NSString *)localizedNetworkReachabilityStatusString {
return AFStringFromNetworkReachabilityStatus(self.networkReachabilityStatus);
}
#pragma mark -
- (void)setReachabilityStatusChangeBlock:(void (^)(AFNetworkReachabilityStatus status))block {
self.networkReachabilityStatusBlock = block;
}
#pragma mark - NSKeyValueObserving
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
if ([key isEqualToString:@"reachable"] || [key isEqualToString:@"reachableViaWWAN"] || [key isEqualToString:@"reachableViaWiFi"]) {
return [NSSet setWithObject:@"networkReachabilityStatus"];
}
return [super keyPathsForValuesAffectingValueForKey:key];
}
@end
#endif

View File

@ -0,0 +1,46 @@
// AFNetworking.h
//
// Copyright (c) 2013 AFNetworking (http://afnetworking.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
#import <Availability.h>
#ifndef _AFNETWORKING_
#define _AFNETWORKING_
#import "AFURLRequestSerialization.h"
#import "AFURLResponseSerialization.h"
#import "AFSecurityPolicy.h"
#if !TARGET_OS_WATCH
#import "AFNetworkReachabilityManager.h"
#import "AFURLConnectionOperation.h"
#import "AFHTTPRequestOperation.h"
#import "AFHTTPRequestOperationManager.h"
#endif
#if ( ( defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1090) || \
( defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 ) || \
TARGET_OS_WATCH )
#import "AFURLSessionManager.h"
#import "AFHTTPSessionManager.h"
#endif
#endif /* _AFNETWORKING_ */

View File

@ -0,0 +1,142 @@
// AFSecurityPolicy.h
// Copyright (c) 20112015 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
#import <Security/Security.h>
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
AFSSLPinningModeNone,
AFSSLPinningModePublicKey,
AFSSLPinningModeCertificate,
};
/**
`AFSecurityPolicy` evaluates server trust against pinned X.509 certificates and public keys over secure connections.
Adding pinned SSL certificates to your app helps prevent man-in-the-middle attacks and other vulnerabilities. Applications dealing with sensitive customer data or financial information are strongly encouraged to route all communication over an HTTPS connection with SSL pinning configured and enabled.
*/
NS_ASSUME_NONNULL_BEGIN
@interface AFSecurityPolicy : NSObject <NSSecureCoding, NSCopying>
/**
The criteria by which server trust should be evaluated against the pinned SSL certificates. Defaults to `AFSSLPinningModeNone`.
*/
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
/**
The certificates used to evaluate server trust according to the SSL pinning mode. By default, this property is set to any (`.cer`) certificates included in the app bundle. Note that if you create an array with duplicate certificates, the duplicate certificates will be removed. Note that if pinning is enabled, `evaluateServerTrust:forDomain:` will return true if any pinned certificate matches.
*/
@property (nonatomic, strong, nullable) NSArray *pinnedCertificates;
/**
Whether or not to trust servers with an invalid or expired SSL certificates. Defaults to `NO`.
*/
@property (nonatomic, assign) BOOL allowInvalidCertificates;
/**
Whether or not to validate the domain name in the certificate's CN field. Defaults to `YES`.
*/
@property (nonatomic, assign) BOOL validatesDomainName;
///-----------------------------------------
/// @name Getting Specific Security Policies
///-----------------------------------------
/**
Returns the shared default security policy, which does not allow invalid certificates, validates domain name, and does not validate against pinned certificates or public keys.
@return The default security policy.
*/
+ (instancetype)defaultPolicy;
///---------------------
/// @name Initialization
///---------------------
/**
Creates and returns a security policy with the specified pinning mode.
@param pinningMode The SSL pinning mode.
@return A new security policy.
*/
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode;
///------------------------------
/// @name Evaluating Server Trust
///------------------------------
/**
Whether or not the specified server trust should be accepted, based on the security policy.
This method should be used when responding to an authentication challenge from a server.
@param serverTrust The X.509 certificate trust of the server.
@return Whether or not to trust the server.
@warning This method has been deprecated in favor of `-evaluateServerTrust:forDomain:`.
*/
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust DEPRECATED_ATTRIBUTE;
/**
Whether or not the specified server trust should be accepted, based on the security policy.
This method should be used when responding to an authentication challenge from a server.
@param serverTrust The X.509 certificate trust of the server.
@param domain The domain of serverTrust. If `nil`, the domain will not be validated.
@return Whether or not to trust the server.
*/
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(nullable NSString *)domain;
@end
NS_ASSUME_NONNULL_END
///----------------
/// @name Constants
///----------------
/**
## SSL Pinning Modes
The following constants are provided by `AFSSLPinningMode` as possible SSL pinning modes.
enum {
AFSSLPinningModeNone,
AFSSLPinningModePublicKey,
AFSSLPinningModeCertificate,
}
`AFSSLPinningModeNone`
Do not used pinned certificates to validate servers.
`AFSSLPinningModePublicKey`
Validate host certificates against public keys of pinned certificates.
`AFSSLPinningModeCertificate`
Validate host certificates against pinned certificates.
*/

View File

@ -0,0 +1,347 @@
// AFSecurityPolicy.m
// Copyright (c) 20112015 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "AFSecurityPolicy.h"
#import <AssertMacros.h>
#if !TARGET_OS_IOS && !TARGET_OS_WATCH
static NSData * AFSecKeyGetData(SecKeyRef key) {
CFDataRef data = NULL;
__Require_noErr_Quiet(SecItemExport(key, kSecFormatUnknown, kSecItemPemArmour, NULL, &data), _out);
return (__bridge_transfer NSData *)data;
_out:
if (data) {
CFRelease(data);
}
return nil;
}
#endif
static BOOL AFSecKeyIsEqualToKey(SecKeyRef key1, SecKeyRef key2) {
#if TARGET_OS_IOS || TARGET_OS_WATCH
return [(__bridge id)key1 isEqual:(__bridge id)key2];
#else
return [AFSecKeyGetData(key1) isEqual:AFSecKeyGetData(key2)];
#endif
}
static id AFPublicKeyForCertificate(NSData *certificate) {
id allowedPublicKey = nil;
SecCertificateRef allowedCertificate;
SecCertificateRef allowedCertificates[1];
CFArrayRef tempCertificates = nil;
SecPolicyRef policy = nil;
SecTrustRef allowedTrust = nil;
SecTrustResultType result;
allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);
__Require_Quiet(allowedCertificate != NULL, _out);
allowedCertificates[0] = allowedCertificate;
tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL);
policy = SecPolicyCreateBasicX509();
__Require_noErr_Quiet(SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust), _out);
__Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out);
allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust);
_out:
if (allowedTrust) {
CFRelease(allowedTrust);
}
if (policy) {
CFRelease(policy);
}
if (tempCertificates) {
CFRelease(tempCertificates);
}
if (allowedCertificate) {
CFRelease(allowedCertificate);
}
return allowedPublicKey;
}
static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
BOOL isValid = NO;
SecTrustResultType result;
__Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);
isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
_out:
return isValid;
}
static NSArray * AFCertificateTrustChainForServerTrust(SecTrustRef serverTrust) {
CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount];
for (CFIndex i = 0; i < certificateCount; i++) {
SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);
[trustChain addObject:(__bridge_transfer NSData *)SecCertificateCopyData(certificate)];
}
return [NSArray arrayWithArray:trustChain];
}
static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
SecPolicyRef policy = SecPolicyCreateBasicX509();
CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount];
for (CFIndex i = 0; i < certificateCount; i++) {
SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);
SecCertificateRef someCertificates[] = {certificate};
CFArrayRef certificates = CFArrayCreate(NULL, (const void **)someCertificates, 1, NULL);
SecTrustRef trust;
__Require_noErr_Quiet(SecTrustCreateWithCertificates(certificates, policy, &trust), _out);
SecTrustResultType result;
__Require_noErr_Quiet(SecTrustEvaluate(trust, &result), _out);
[trustChain addObject:(__bridge_transfer id)SecTrustCopyPublicKey(trust)];
_out:
if (trust) {
CFRelease(trust);
}
if (certificates) {
CFRelease(certificates);
}
continue;
}
CFRelease(policy);
return [NSArray arrayWithArray:trustChain];
}
#pragma mark -
@interface AFSecurityPolicy()
@property (readwrite, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
@property (readwrite, nonatomic, strong) NSArray *pinnedPublicKeys;
@end
@implementation AFSecurityPolicy
+ (NSArray *)defaultPinnedCertificates {
static NSArray *_defaultPinnedCertificates = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."];
NSMutableArray *certificates = [NSMutableArray arrayWithCapacity:[paths count]];
for (NSString *path in paths) {
NSData *certificateData = [NSData dataWithContentsOfFile:path];
[certificates addObject:certificateData];
}
_defaultPinnedCertificates = [[NSArray alloc] initWithArray:certificates];
});
return _defaultPinnedCertificates;
}
+ (instancetype)defaultPolicy {
AFSecurityPolicy *securityPolicy = [[self alloc] init];
securityPolicy.SSLPinningMode = AFSSLPinningModeNone;
return securityPolicy;
}
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode {
AFSecurityPolicy *securityPolicy = [[self alloc] init];
securityPolicy.SSLPinningMode = pinningMode;
[securityPolicy setPinnedCertificates:[self defaultPinnedCertificates]];
return securityPolicy;
}
- (id)init {
self = [super init];
if (!self) {
return nil;
}
self.validatesDomainName = YES;
return self;
}
- (void)setPinnedCertificates:(NSArray *)pinnedCertificates {
_pinnedCertificates = [[NSOrderedSet orderedSetWithArray:pinnedCertificates] array];
if (self.pinnedCertificates) {
NSMutableArray *mutablePinnedPublicKeys = [NSMutableArray arrayWithCapacity:[self.pinnedCertificates count]];
for (NSData *certificate in self.pinnedCertificates) {
id publicKey = AFPublicKeyForCertificate(certificate);
if (!publicKey) {
continue;
}
[mutablePinnedPublicKeys addObject:publicKey];
}
self.pinnedPublicKeys = [NSArray arrayWithArray:mutablePinnedPublicKeys];
} else {
self.pinnedPublicKeys = nil;
}
}
#pragma mark -
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust {
return [self evaluateServerTrust:serverTrust forDomain:nil];
}
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain
{
if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
// https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html
// According to the docs, you should only trust your provided certs for evaluation.
// Pinned certificates are added to the trust. Without pinned certificates,
// there is nothing to evaluate against.
//
// From Apple Docs:
// "Do not implicitly trust self-signed certificates as anchors (kSecTrustOptionImplicitAnchors).
// Instead, add your own (self-signed) CA certificate to the list of trusted anchors."
NSLog(@"In order to validate a domain name for self signed certificates, you MUST use pinning.");
return NO;
}
NSMutableArray *policies = [NSMutableArray array];
if (self.validatesDomainName) {
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
if (self.SSLPinningMode == AFSSLPinningModeNone) {
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
return NO;
}
NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
switch (self.SSLPinningMode) {
case AFSSLPinningModeNone:
default:
return NO;
case AFSSLPinningModeCertificate: {
NSMutableArray *pinnedCertificates = [NSMutableArray array];
for (NSData *certificateData in self.pinnedCertificates) {
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);
if (!AFServerTrustIsValid(serverTrust)) {
return NO;
}
NSUInteger trustedCertificateCount = 0;
for (NSData *trustChainCertificate in serverCertificates) {
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
trustedCertificateCount++;
}
}
return trustedCertificateCount > 0;
}
case AFSSLPinningModePublicKey: {
NSUInteger trustedPublicKeyCount = 0;
NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
for (id trustChainPublicKey in publicKeys) {
for (id pinnedPublicKey in self.pinnedPublicKeys) {
if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
trustedPublicKeyCount += 1;
}
}
}
return trustedPublicKeyCount > 0;
}
}
return NO;
}
#pragma mark - NSKeyValueObserving
+ (NSSet *)keyPathsForValuesAffectingPinnedPublicKeys {
return [NSSet setWithObject:@"pinnedCertificates"];
}
#pragma mark - NSSecureCoding
+ (BOOL)supportsSecureCoding {
return YES;
}
- (instancetype)initWithCoder:(NSCoder *)decoder {
self = [self init];
if (!self) {
return nil;
}
self.SSLPinningMode = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(SSLPinningMode))] unsignedIntegerValue];
self.allowInvalidCertificates = [decoder decodeBoolForKey:NSStringFromSelector(@selector(allowInvalidCertificates))];
self.validatesDomainName = [decoder decodeBoolForKey:NSStringFromSelector(@selector(validatesDomainName))];
self.pinnedCertificates = [decoder decodeObjectOfClass:[NSArray class] forKey:NSStringFromSelector(@selector(pinnedCertificates))];
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:[NSNumber numberWithUnsignedInteger:self.SSLPinningMode] forKey:NSStringFromSelector(@selector(SSLPinningMode))];
[coder encodeBool:self.allowInvalidCertificates forKey:NSStringFromSelector(@selector(allowInvalidCertificates))];
[coder encodeBool:self.validatesDomainName forKey:NSStringFromSelector(@selector(validatesDomainName))];
[coder encodeObject:self.pinnedCertificates forKey:NSStringFromSelector(@selector(pinnedCertificates))];
}
#pragma mark - NSCopying
- (instancetype)copyWithZone:(NSZone *)zone {
AFSecurityPolicy *securityPolicy = [[[self class] allocWithZone:zone] init];
securityPolicy.SSLPinningMode = self.SSLPinningMode;
securityPolicy.allowInvalidCertificates = self.allowInvalidCertificates;
securityPolicy.validatesDomainName = self.validatesDomainName;
securityPolicy.pinnedCertificates = [self.pinnedCertificates copyWithZone:zone];
return securityPolicy;
}
@end

View File

@ -0,0 +1,344 @@
// AFURLConnectionOperation.h
// Copyright (c) 20112015 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
#import <Availability.h>
#import "AFURLRequestSerialization.h"
#import "AFURLResponseSerialization.h"
#import "AFSecurityPolicy.h"
#ifndef NS_DESIGNATED_INITIALIZER
#if __has_attribute(objc_designated_initializer)
#define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))
#else
#define NS_DESIGNATED_INITIALIZER
#endif
#endif
/**
`AFURLConnectionOperation` is a subclass of `NSOperation` that implements `NSURLConnection` delegate methods.
## Subclassing Notes
This is the base class of all network request operations. You may wish to create your own subclass in order to implement additional `NSURLConnection` delegate methods (see "`NSURLConnection` Delegate Methods" below), or to provide additional properties and/or class constructors.
If you are creating a subclass that communicates over the HTTP or HTTPS protocols, you may want to consider subclassing `AFHTTPRequestOperation` instead, as it supports specifying acceptable content types or status codes.
## NSURLConnection Delegate Methods
`AFURLConnectionOperation` implements the following `NSURLConnection` delegate methods:
- `connection:didReceiveResponse:`
- `connection:didReceiveData:`
- `connectionDidFinishLoading:`
- `connection:didFailWithError:`
- `connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:`
- `connection:willCacheResponse:`
- `connectionShouldUseCredentialStorage:`
- `connection:needNewBodyStream:`
- `connection:willSendRequestForAuthenticationChallenge:`
If any of these methods are overridden in a subclass, they _must_ call the `super` implementation first.
## Callbacks and Completion Blocks
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` (i.e. returning early before parsing response data). See the implementation of any of the `AFHTTPRequestOperation` subclasses for an example of this.
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/).
## SSL Pinning
Relying on the CA trust model to validate SSL certificates exposes your app to security vulnerabilities, such as man-in-the-middle attacks. For applications that connect to known servers, SSL certificate pinning provides an increased level of security, by checking server certificate validity against those specified in the app bundle.
SSL with certificate pinning is strongly recommended for any application that transmits sensitive information to an external webservice.
Connections will be validated on all matching certificates with a `.cer` extension in the bundle root.
## 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
- 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`, as it often strongly captures a reference to `self`, which would otherwise have the unintuitive side-effect of pointing to the _original_ operation when copied.
*/
NS_ASSUME_NONNULL_BEGIN
@interface AFURLConnectionOperation : NSOperation <NSURLConnectionDelegate, NSURLConnectionDataDelegate, NSSecureCoding, NSCopying>
///-------------------------------
/// @name Accessing Run Loop Modes
///-------------------------------
/**
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, strong) NSSet *runLoopModes;
///-----------------------------------------
/// @name Getting URL Connection Information
///-----------------------------------------
/**
The request used by the operation's connection.
*/
@property (readonly, nonatomic, strong) NSURLRequest *request;
/**
The last response received by the operation's connection.
*/
@property (readonly, nonatomic, strong, nullable) NSURLResponse *response;
/**
The error, if any, that occurred in the lifecycle of the request.
*/
@property (readonly, nonatomic, strong, nullable) NSError *error;
///----------------------------
/// @name Getting Response Data
///----------------------------
/**
The data received during the request.
*/
@property (readonly, nonatomic, strong, nullable) NSData *responseData;
/**
The string representation of the response data.
*/
@property (readonly, nonatomic, copy, nullable) NSString *responseString;
/**
The string encoding of the response.
If the response does not specify a valid string encoding, `responseStringEncoding` will return `NSUTF8StringEncoding`.
*/
@property (readonly, nonatomic, assign) NSStringEncoding responseStringEncoding;
///-------------------------------
/// @name Managing URL Credentials
///-------------------------------
/**
Whether the URL connection should consult the credential storage for authenticating the connection. `YES` by default.
This is the value that is returned in the `NSURLConnectionDelegate` method `-connectionShouldUseCredentialStorage:`.
*/
@property (nonatomic, assign) BOOL shouldUseCredentialStorage;
/**
The credential used for authentication challenges in `-connection:didReceiveAuthenticationChallenge:`.
This will be overridden by any shared credentials that exist for the username or password of the request URL, if present.
*/
@property (nonatomic, strong, nullable) NSURLCredential *credential;
///-------------------------------
/// @name Managing Security Policy
///-------------------------------
/**
The security policy used to evaluate server trust for secure connections.
*/
@property (nonatomic, strong) AFSecurityPolicy *securityPolicy;
///------------------------
/// @name Accessing Streams
///------------------------
/**
The input stream used to read data to be sent during the request.
This property acts as a proxy to the `HTTPBodyStream` property of `request`.
*/
@property (nonatomic, strong) NSInputStream *inputStream;
/**
The output stream that is used to write data received until the request is finished.
By default, data is accumulated into a buffer that is stored into `responseData` upon completion of the request, with the intermediary `outputStream` property set to `nil`. 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, strong, nullable) NSOutputStream *outputStream;
///---------------------------------
/// @name Managing Callback Queues
///---------------------------------
/**
The dispatch queue for `completionBlock`. If `NULL` (default), the main queue is used.
*/
#if OS_OBJECT_USE_OBJC
@property (nonatomic, strong, nullable) dispatch_queue_t completionQueue;
#else
@property (nonatomic, assign, nullable) dispatch_queue_t completionQueue;
#endif
/**
The dispatch group for `completionBlock`. If `NULL` (default), a private dispatch group is used.
*/
#if OS_OBJECT_USE_OBJC
@property (nonatomic, strong, nullable) dispatch_group_t completionGroup;
#else
@property (nonatomic, assign, nullable) dispatch_group_t completionGroup;
#endif
///---------------------------------------------
/// @name Managing Request Operation Information
///---------------------------------------------
/**
The user info dictionary for the receiver.
*/
@property (nonatomic, strong) NSDictionary *userInfo;
// FIXME: It doesn't seem that this userInfo is used anywhere in the implementation.
///------------------------------------------------------
/// @name Initializing an AFURLConnectionOperation Object
///------------------------------------------------------
/**
Initializes and returns a newly allocated operation object with a url connection configured with the specified url request.
This is the designated initializer.
@param urlRequest The request object to be used by the operation connection.
*/
- (instancetype)initWithRequest:(NSURLRequest *)urlRequest NS_DESIGNATED_INITIALIZER;
///----------------------------------
/// @name Pausing / Resuming Requests
///----------------------------------
/**
Pauses the execution of the request operation.
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;
/**
Whether the request operation is currently paused.
@return `YES` if the operation is currently paused, otherwise `NO`.
*/
- (BOOL)isPaused;
/**
Resumes the execution of the paused request operation.
Pause/Resume behavior varies depending on the underlying implementation for the operation class. In its base implementation, resuming a paused requests restarts the original request. However, since HTTP defines a specification for how to request a specific content range, `AFHTTPRequestOperation` will resume downloading the request from where it left off, instead of restarting the original request.
*/
- (void)resume;
///----------------------------------------------
/// @name Configuring Backgrounding Task Behavior
///----------------------------------------------
/**
Specifies that the operation should continue execution after the app has entered the background, and the expiration handler for that background task.
@param handler A handler to be called shortly before the applications remaining background time reaches 0. The handler is wrapped in a block that cancels the operation, and cleans up and marks the end of execution, unlike the `handler` parameter in `UIApplication -beginBackgroundTaskWithExpirationHandler:`, which expects this to be done in the handler itself. The handler is called synchronously on the main thread, thus blocking the applications suspension momentarily while the application is notified.
*/
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
- (void)setShouldExecuteAsBackgroundTaskWithExpirationHandler:(nullable void (^)(void))handler NS_EXTENSION_UNAVAILABLE_IOS("Not available in app extensions.");
#endif
///---------------------------------
/// @name Setting Progress Callbacks
///---------------------------------
/**
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, and will execute on the main thread.
*/
- (void)setUploadProgressBlock:(nullable 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, and will execute on the main thread.
*/
- (void)setDownloadProgressBlock:(nullable void (^)(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead))block;
///-------------------------------------------------
/// @name Setting NSURLConnection Delegate Callbacks
///-------------------------------------------------
/**
Sets a block to be executed when the connection will authenticate a challenge in order to download its request, as handled by the `NSURLConnectionDelegate` method `connection:willSendRequestForAuthenticationChallenge:`.
@param block A block object to be executed when the connection will authenticate a challenge in order to download its request. The block has no return type and takes two arguments: the URL connection object, and the challenge that must be authenticated. This block must invoke one of the challenge-responder methods (NSURLAuthenticationChallengeSender protocol).
If `allowsInvalidSSLCertificate` is set to YES, `connection:willSendRequestForAuthenticationChallenge:` will attempt to have the challenge sender use credentials with invalid SSL certificates.
*/
- (void)setWillSendRequestForAuthenticationChallengeBlock:(nullable void (^)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge))block;
/**
Sets a block to be executed when the server redirects the request from one URL to another URL, or when the request URL changed by the `NSURLProtocol` subclass handling the request in order to standardize its format, as handled by the `NSURLConnectionDataDelegate` method `connection:willSendRequest:redirectResponse:`.
@param block A block object to be executed when the request URL was changed. The block returns an `NSURLRequest` object, the URL request to redirect, and takes three arguments: the URL connection object, the the proposed redirected request, and the URL response that caused the redirect.
*/
- (void)setRedirectResponseBlock:(nullable NSURLRequest * (^)(NSURLConnection *connection, NSURLRequest *request, NSURLResponse *redirectResponse))block;
/**
Sets a block to be executed to modify the response a connection will cache, if any, as handled by the `NSURLConnectionDelegate` method `connection:willCacheResponse:`.
@param block A block object to be executed to determine what response a connection will cache, if any. The block returns an `NSCachedURLResponse` object, the cached response to store in memory or `nil` to prevent the response from being cached, and takes two arguments: the URL connection object, and the cached response provided for the request.
*/
- (void)setCacheResponseBlock:(nullable NSCachedURLResponse * (^)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse))block;
///
/**
*/
+ (NSArray *)batchOfRequestOperations:(nullable NSArray *)operations
progressBlock:(nullable void (^)(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations))progressBlock
completionBlock:(nullable void (^)(NSArray *operations))completionBlock;
@end
///--------------------
/// @name Notifications
///--------------------
/**
Posted when an operation begins executing.
*/
FOUNDATION_EXPORT NSString * const AFNetworkingOperationDidStartNotification;
/**
Posted when an operation finishes.
*/
FOUNDATION_EXPORT NSString * const AFNetworkingOperationDidFinishNotification;
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,792 @@
// AFURLConnectionOperation.m
// Copyright (c) 20112015 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "AFURLConnectionOperation.h"
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
#import <UIKit/UIKit.h>
#endif
#if !__has_feature(objc_arc)
#error AFNetworking must be built with ARC.
// You can turn on ARC for only AFNetworking files by adding -fobjc-arc to the build phase for each of its files.
#endif
typedef NS_ENUM(NSInteger, AFOperationState) {
AFOperationPausedState = -1,
AFOperationReadyState = 1,
AFOperationExecutingState = 2,
AFOperationFinishedState = 3,
};
static dispatch_group_t url_request_operation_completion_group() {
static dispatch_group_t af_url_request_operation_completion_group;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
af_url_request_operation_completion_group = dispatch_group_create();
});
return af_url_request_operation_completion_group;
}
static dispatch_queue_t url_request_operation_completion_queue() {
static dispatch_queue_t af_url_request_operation_completion_queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
af_url_request_operation_completion_queue = dispatch_queue_create("com.alamofire.networking.operation.queue", DISPATCH_QUEUE_CONCURRENT );
});
return af_url_request_operation_completion_queue;
}
static NSString * const kAFNetworkingLockName = @"com.alamofire.networking.operation.lock";
NSString * const AFNetworkingOperationDidStartNotification = @"com.alamofire.networking.operation.start";
NSString * const AFNetworkingOperationDidFinishNotification = @"com.alamofire.networking.operation.finish";
typedef void (^AFURLConnectionOperationProgressBlock)(NSUInteger bytes, long long totalBytes, long long totalBytesExpected);
typedef void (^AFURLConnectionOperationAuthenticationChallengeBlock)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge);
typedef NSCachedURLResponse * (^AFURLConnectionOperationCacheResponseBlock)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse);
typedef NSURLRequest * (^AFURLConnectionOperationRedirectResponseBlock)(NSURLConnection *connection, NSURLRequest *request, NSURLResponse *redirectResponse);
typedef void (^AFURLConnectionOperationBackgroundTaskCleanupBlock)();
static inline NSString * AFKeyPathFromOperationState(AFOperationState state) {
switch (state) {
case AFOperationReadyState:
return @"isReady";
case AFOperationExecutingState:
return @"isExecuting";
case AFOperationFinishedState:
return @"isFinished";
case AFOperationPausedState:
return @"isPaused";
default: {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunreachable-code"
return @"state";
#pragma clang diagnostic pop
}
}
}
static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperationState toState, BOOL isCancelled) {
switch (fromState) {
case AFOperationReadyState:
switch (toState) {
case AFOperationPausedState:
case AFOperationExecutingState:
return YES;
case AFOperationFinishedState:
return isCancelled;
default:
return NO;
}
case AFOperationExecutingState:
switch (toState) {
case AFOperationPausedState:
case AFOperationFinishedState:
return YES;
default:
return NO;
}
case AFOperationFinishedState:
return NO;
case AFOperationPausedState:
return toState == AFOperationReadyState;
default: {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunreachable-code"
switch (toState) {
case AFOperationPausedState:
case AFOperationReadyState:
case AFOperationExecutingState:
case AFOperationFinishedState:
return YES;
default:
return NO;
}
}
#pragma clang diagnostic pop
}
}
@interface AFURLConnectionOperation ()
@property (readwrite, nonatomic, assign) AFOperationState state;
@property (readwrite, nonatomic, strong) NSRecursiveLock *lock;
@property (readwrite, nonatomic, strong) NSURLConnection *connection;
@property (readwrite, nonatomic, strong) NSURLRequest *request;
@property (readwrite, nonatomic, strong) NSURLResponse *response;
@property (readwrite, nonatomic, strong) NSError *error;
@property (readwrite, nonatomic, strong) NSData *responseData;
@property (readwrite, nonatomic, copy) NSString *responseString;
@property (readwrite, nonatomic, assign) NSStringEncoding responseStringEncoding;
@property (readwrite, nonatomic, assign) long long totalBytesRead;
@property (readwrite, nonatomic, copy) AFURLConnectionOperationBackgroundTaskCleanupBlock backgroundTaskCleanup;
@property (readwrite, nonatomic, copy) AFURLConnectionOperationProgressBlock uploadProgress;
@property (readwrite, nonatomic, copy) AFURLConnectionOperationProgressBlock downloadProgress;
@property (readwrite, nonatomic, copy) AFURLConnectionOperationAuthenticationChallengeBlock authenticationChallenge;
@property (readwrite, nonatomic, copy) AFURLConnectionOperationCacheResponseBlock cacheResponse;
@property (readwrite, nonatomic, copy) AFURLConnectionOperationRedirectResponseBlock redirectResponse;
- (void)operationDidStart;
- (void)finish;
- (void)cancelConnection;
@end
@implementation AFURLConnectionOperation
@synthesize outputStream = _outputStream;
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
- (instancetype)initWithRequest:(NSURLRequest *)urlRequest {
NSParameterAssert(urlRequest);
self = [super init];
if (!self) {
return nil;
}
_state = AFOperationReadyState;
self.lock = [[NSRecursiveLock alloc] init];
self.lock.name = kAFNetworkingLockName;
self.runLoopModes = [NSSet setWithObject:NSRunLoopCommonModes];
self.request = urlRequest;
self.shouldUseCredentialStorage = YES;
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
return self;
}
- (instancetype)init NS_UNAVAILABLE
{
return nil;
}
- (void)dealloc {
if (_outputStream) {
[_outputStream close];
_outputStream = nil;
}
if (_backgroundTaskCleanup) {
_backgroundTaskCleanup();
}
}
#pragma mark -
- (void)setResponseData:(NSData *)responseData {
[self.lock lock];
if (!responseData) {
_responseData = nil;
} else {
_responseData = [NSData dataWithBytes:responseData.bytes length:responseData.length];
}
[self.lock unlock];
}
- (NSString *)responseString {
[self.lock lock];
if (!_responseString && self.response && self.responseData) {
self.responseString = [[NSString alloc] initWithData:self.responseData encoding:self.responseStringEncoding];
}
[self.lock unlock];
return _responseString;
}
- (NSStringEncoding)responseStringEncoding {
[self.lock lock];
if (!_responseStringEncoding && self.response) {
NSStringEncoding stringEncoding = NSUTF8StringEncoding;
if (self.response.textEncodingName) {
CFStringEncoding IANAEncoding = CFStringConvertIANACharSetNameToEncoding((__bridge CFStringRef)self.response.textEncodingName);
if (IANAEncoding != kCFStringEncodingInvalidId) {
stringEncoding = CFStringConvertEncodingToNSStringEncoding(IANAEncoding);
}
}
self.responseStringEncoding = stringEncoding;
}
[self.lock unlock];
return _responseStringEncoding;
}
- (NSInputStream *)inputStream {
return self.request.HTTPBodyStream;
}
- (void)setInputStream:(NSInputStream *)inputStream {
NSMutableURLRequest *mutableRequest = [self.request mutableCopy];
mutableRequest.HTTPBodyStream = inputStream;
self.request = mutableRequest;
}
- (NSOutputStream *)outputStream {
if (!_outputStream) {
self.outputStream = [NSOutputStream outputStreamToMemory];
}
return _outputStream;
}
- (void)setOutputStream:(NSOutputStream *)outputStream {
[self.lock lock];
if (outputStream != _outputStream) {
if (_outputStream) {
[_outputStream close];
}
_outputStream = outputStream;
}
[self.lock unlock];
}
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
- (void)setShouldExecuteAsBackgroundTaskWithExpirationHandler:(void (^)(void))handler {
[self.lock lock];
if (!self.backgroundTaskCleanup) {
UIApplication *application = [UIApplication sharedApplication];
UIBackgroundTaskIdentifier __block backgroundTaskIdentifier = UIBackgroundTaskInvalid;
__weak __typeof(self)weakSelf = self;
self.backgroundTaskCleanup = ^(){
if (backgroundTaskIdentifier != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:backgroundTaskIdentifier];
backgroundTaskIdentifier = UIBackgroundTaskInvalid;
}
};
backgroundTaskIdentifier = [application beginBackgroundTaskWithExpirationHandler:^{
__strong __typeof(weakSelf)strongSelf = weakSelf;
if (handler) {
handler();
}
if (strongSelf) {
[strongSelf cancel];
strongSelf.backgroundTaskCleanup();
}
}];
}
[self.lock unlock];
}
#endif
#pragma mark -
- (void)setState:(AFOperationState)state {
if (!AFStateTransitionIsValid(self.state, state, [self isCancelled])) {
return;
}
[self.lock lock];
NSString *oldStateKey = AFKeyPathFromOperationState(self.state);
NSString *newStateKey = AFKeyPathFromOperationState(state);
[self willChangeValueForKey:newStateKey];
[self willChangeValueForKey:oldStateKey];
_state = state;
[self didChangeValueForKey:oldStateKey];
[self didChangeValueForKey:newStateKey];
[self.lock unlock];
}
- (void)pause {
if ([self isPaused] || [self isFinished] || [self isCancelled]) {
return;
}
[self.lock lock];
if ([self isExecuting]) {
[self performSelector:@selector(operationDidPause) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
dispatch_async(dispatch_get_main_queue(), ^{
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter postNotificationName:AFNetworkingOperationDidFinishNotification object:self];
});
}
self.state = AFOperationPausedState;
[self.lock unlock];
}
- (void)operationDidPause {
[self.lock lock];
[self.connection cancel];
[self.lock unlock];
}
- (BOOL)isPaused {
return self.state == AFOperationPausedState;
}
- (void)resume {
if (![self isPaused]) {
return;
}
[self.lock lock];
self.state = AFOperationReadyState;
[self start];
[self.lock unlock];
}
#pragma mark -
- (void)setUploadProgressBlock:(void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))block {
self.uploadProgress = block;
}
- (void)setDownloadProgressBlock:(void (^)(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead))block {
self.downloadProgress = block;
}
- (void)setWillSendRequestForAuthenticationChallengeBlock:(void (^)(NSURLConnection *connection, NSURLAuthenticationChallenge *challenge))block {
self.authenticationChallenge = block;
}
- (void)setCacheResponseBlock:(NSCachedURLResponse * (^)(NSURLConnection *connection, NSCachedURLResponse *cachedResponse))block {
self.cacheResponse = block;
}
- (void)setRedirectResponseBlock:(NSURLRequest * (^)(NSURLConnection *connection, NSURLRequest *request, NSURLResponse *redirectResponse))block {
self.redirectResponse = block;
}
#pragma mark - NSOperation
- (void)setCompletionBlock:(void (^)(void))block {
[self.lock lock];
if (!block) {
[super setCompletionBlock:nil];
} else {
__weak __typeof(self)weakSelf = self;
[super setCompletionBlock:^ {
__strong __typeof(weakSelf)strongSelf = weakSelf;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
dispatch_group_t group = strongSelf.completionGroup ?: url_request_operation_completion_group();
dispatch_queue_t queue = strongSelf.completionQueue ?: dispatch_get_main_queue();
#pragma clang diagnostic pop
dispatch_group_async(group, queue, ^{
block();
});
dispatch_group_notify(group, url_request_operation_completion_queue(), ^{
[strongSelf setCompletionBlock:nil];
});
}];
}
[self.lock unlock];
}
- (BOOL)isReady {
return self.state == AFOperationReadyState && [super isReady];
}
- (BOOL)isExecuting {
return self.state == AFOperationExecutingState;
}
- (BOOL)isFinished {
return self.state == AFOperationFinishedState;
}
- (BOOL)isConcurrent {
return YES;
}
- (void)start {
[self.lock lock];
if ([self isCancelled]) {
[self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
} else if ([self isReady]) {
self.state = AFOperationExecutingState;
[self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
}
[self.lock unlock];
}
- (void)operationDidStart {
[self.lock lock];
if (![self isCancelled]) {
self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
for (NSString *runLoopMode in self.runLoopModes) {
[self.connection scheduleInRunLoop:runLoop forMode:runLoopMode];
[self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode];
}
[self.outputStream open];
[self.connection start];
}
[self.lock unlock];
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidStartNotification object:self];
});
}
- (void)finish {
[self.lock lock];
self.state = AFOperationFinishedState;
[self.lock unlock];
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidFinishNotification object:self];
});
}
- (void)cancel {
[self.lock lock];
if (![self isFinished] && ![self isCancelled]) {
[super cancel];
if ([self isExecuting]) {
[self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
}
}
[self.lock unlock];
}
- (void)cancelConnection {
NSDictionary *userInfo = nil;
if ([self.request URL]) {
userInfo = @{NSURLErrorFailingURLErrorKey : [self.request URL]};
}
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorCancelled userInfo:userInfo];
if (![self isFinished]) {
if (self.connection) {
[self.connection cancel];
[self performSelector:@selector(connection:didFailWithError:) withObject:self.connection withObject:error];
} else {
// Accommodate race condition where `self.connection` has not yet been set before cancellation
self.error = error;
[self finish];
}
}
}
#pragma mark -
+ (NSArray *)batchOfRequestOperations:(NSArray *)operations
progressBlock:(void (^)(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations))progressBlock
completionBlock:(void (^)(NSArray *operations))completionBlock
{
if (!operations || [operations count] == 0) {
return @[[NSBlockOperation blockOperationWithBlock:^{
dispatch_async(dispatch_get_main_queue(), ^{
if (completionBlock) {
completionBlock(@[]);
}
});
}]];
}
__block dispatch_group_t group = dispatch_group_create();
NSBlockOperation *batchedOperation = [NSBlockOperation blockOperationWithBlock:^{
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
if (completionBlock) {
completionBlock(operations);
}
});
}];
for (AFURLConnectionOperation *operation in operations) {
operation.completionGroup = group;
void (^originalCompletionBlock)(void) = [operation.completionBlock copy];
__weak __typeof(operation)weakOperation = operation;
operation.completionBlock = ^{
__strong __typeof(weakOperation)strongOperation = weakOperation;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
dispatch_queue_t queue = strongOperation.completionQueue ?: dispatch_get_main_queue();
#pragma clang diagnostic pop
dispatch_group_async(group, queue, ^{
if (originalCompletionBlock) {
originalCompletionBlock();
}
NSUInteger numberOfFinishedOperations = [[operations indexesOfObjectsPassingTest:^BOOL(id op, NSUInteger __unused idx, BOOL __unused *stop) {
return [op isFinished];
}] count];
if (progressBlock) {
progressBlock(numberOfFinishedOperations, [operations count]);
}
dispatch_group_leave(group);
});
};
dispatch_group_enter(group);
[batchedOperation addDependency:operation];
}
return [operations arrayByAddingObject:batchedOperation];
}
#pragma mark - NSObject
- (NSString *)description {
[self.lock lock];
NSString *description = [NSString stringWithFormat:@"<%@: %p, state: %@, cancelled: %@ request: %@, response: %@>", NSStringFromClass([self class]), self, AFKeyPathFromOperationState(self.state), ([self isCancelled] ? @"YES" : @"NO"), self.request, self.response];
[self.lock unlock];
return description;
}
#pragma mark - NSURLConnectionDelegate
- (void)connection:(NSURLConnection *)connection
willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
if (self.authenticationChallenge) {
self.authenticationChallenge(connection, challenge);
return;
}
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
} else {
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
} else {
if ([challenge previousFailureCount] == 0) {
if (self.credential) {
[[challenge sender] useCredential:self.credential forAuthenticationChallenge:challenge];
} else {
[[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
}
} else {
[[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
}
}
}
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection __unused *)connection {
return self.shouldUseCredentialStorage;
}
- (NSURLRequest *)connection:(NSURLConnection *)connection
willSendRequest:(NSURLRequest *)request
redirectResponse:(NSURLResponse *)redirectResponse
{
if (self.redirectResponse) {
return self.redirectResponse(connection, request, redirectResponse);
} else {
return request;
}
}
- (void)connection:(NSURLConnection __unused *)connection
didSendBodyData:(NSInteger)bytesWritten
totalBytesWritten:(NSInteger)totalBytesWritten
totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
{
dispatch_async(dispatch_get_main_queue(), ^{
if (self.uploadProgress) {
self.uploadProgress((NSUInteger)bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
}
});
}
- (void)connection:(NSURLConnection __unused *)connection
didReceiveResponse:(NSURLResponse *)response
{
self.response = response;
}
- (void)connection:(NSURLConnection __unused *)connection
didReceiveData:(NSData *)data
{
NSUInteger length = [data length];
while (YES) {
NSInteger totalNumberOfBytesWritten = 0;
if ([self.outputStream hasSpaceAvailable]) {
const uint8_t *dataBuffer = (uint8_t *)[data bytes];
NSInteger numberOfBytesWritten = 0;
while (totalNumberOfBytesWritten < (NSInteger)length) {
numberOfBytesWritten = [self.outputStream write:&dataBuffer[(NSUInteger)totalNumberOfBytesWritten] maxLength:(length - (NSUInteger)totalNumberOfBytesWritten)];
if (numberOfBytesWritten == -1) {
break;
}
totalNumberOfBytesWritten += numberOfBytesWritten;
}
break;
} else {
[self.connection cancel];
if (self.outputStream.streamError) {
[self performSelector:@selector(connection:didFailWithError:) withObject:self.connection withObject:self.outputStream.streamError];
}
return;
}
}
dispatch_async(dispatch_get_main_queue(), ^{
self.totalBytesRead += (long long)length;
if (self.downloadProgress) {
self.downloadProgress(length, self.totalBytesRead, self.response.expectedContentLength);
}
});
}
- (void)connectionDidFinishLoading:(NSURLConnection __unused *)connection {
self.responseData = [self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey];
[self.outputStream close];
if (self.responseData) {
self.outputStream = nil;
}
self.connection = nil;
[self finish];
}
- (void)connection:(NSURLConnection __unused *)connection
didFailWithError:(NSError *)error
{
self.error = error;
[self.outputStream close];
if (self.responseData) {
self.outputStream = nil;
}
self.connection = nil;
[self finish];
}
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
if (self.cacheResponse) {
return self.cacheResponse(connection, cachedResponse);
} else {
if ([self isCancelled]) {
return nil;
}
return cachedResponse;
}
}
#pragma mark - NSSecureCoding
+ (BOOL)supportsSecureCoding {
return YES;
}
- (id)initWithCoder:(NSCoder *)decoder {
NSURLRequest *request = [decoder decodeObjectOfClass:[NSURLRequest class] forKey:NSStringFromSelector(@selector(request))];
self = [self initWithRequest:request];
if (!self) {
return nil;
}
self.state = (AFOperationState)[[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(state))] integerValue];
self.response = [decoder decodeObjectOfClass:[NSHTTPURLResponse class] forKey:NSStringFromSelector(@selector(response))];
self.error = [decoder decodeObjectOfClass:[NSError class] forKey:NSStringFromSelector(@selector(error))];
self.responseData = [decoder decodeObjectOfClass:[NSData class] forKey:NSStringFromSelector(@selector(responseData))];
self.totalBytesRead = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(totalBytesRead))] longLongValue];
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[self pause];
[coder encodeObject:self.request forKey:NSStringFromSelector(@selector(request))];
switch (self.state) {
case AFOperationExecutingState:
case AFOperationPausedState:
[coder encodeInteger:AFOperationReadyState forKey:NSStringFromSelector(@selector(state))];
break;
default:
[coder encodeInteger:self.state forKey:NSStringFromSelector(@selector(state))];
break;
}
[coder encodeObject:self.response forKey:NSStringFromSelector(@selector(response))];
[coder encodeObject:self.error forKey:NSStringFromSelector(@selector(error))];
[coder encodeObject:self.responseData forKey:NSStringFromSelector(@selector(responseData))];
[coder encodeInt64:self.totalBytesRead forKey:NSStringFromSelector(@selector(totalBytesRead))];
}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {
AFURLConnectionOperation *operation = [(AFURLConnectionOperation *)[[self class] allocWithZone:zone] initWithRequest:self.request];
operation.uploadProgress = self.uploadProgress;
operation.downloadProgress = self.downloadProgress;
operation.authenticationChallenge = self.authenticationChallenge;
operation.cacheResponse = self.cacheResponse;
operation.redirectResponse = self.redirectResponse;
operation.completionQueue = self.completionQueue;
operation.completionGroup = self.completionGroup;
return operation;
}
@end

View File

@ -0,0 +1,476 @@
// AFURLRequestSerialization.h
// Copyright (c) 20112015 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
#if TARGET_OS_IOS
#import <UIKit/UIKit.h>
#elif TARGET_OS_WATCH
#import <WatchKit/WatchKit.h>
#endif
NS_ASSUME_NONNULL_BEGIN
/**
The `AFURLRequestSerialization` protocol is adopted by an object that encodes parameters for a specified HTTP requests. Request serializers may encode parameters as query strings, HTTP bodies, setting the appropriate HTTP header fields as necessary.
For example, a JSON request serializer may set the HTTP body of the request to a JSON representation, and set the `Content-Type` HTTP header field value to `application/json`.
*/
@protocol AFURLRequestSerialization <NSObject, NSSecureCoding, NSCopying>
/**
Returns a request with the specified parameters encoded into a copy of the original request.
@param request The original request.
@param parameters The parameters to be encoded.
@param error The error that occurred while attempting to encode the request parameters.
@return A serialized request.
*/
- (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(nullable id)parameters
error:(NSError * __nullable __autoreleasing *)error
#ifdef NS_SWIFT_NOTHROW
NS_SWIFT_NOTHROW
#endif
;
@end
#pragma mark -
/**
*/
typedef NS_ENUM(NSUInteger, AFHTTPRequestQueryStringSerializationStyle) {
AFHTTPRequestQueryStringDefaultStyle = 0,
};
@protocol AFMultipartFormData;
/**
`AFHTTPRequestSerializer` conforms to the `AFURLRequestSerialization` & `AFURLResponseSerialization` protocols, offering a concrete base implementation of query string / URL form-encoded parameter serialization and default request headers, as well as response status code and content type validation.
Any request or response serializer dealing with HTTP is encouraged to subclass `AFHTTPRequestSerializer` in order to ensure consistent default behavior.
*/
@interface AFHTTPRequestSerializer : NSObject <AFURLRequestSerialization>
/**
The string encoding used to serialize parameters. `NSUTF8StringEncoding` by default.
*/
@property (nonatomic, assign) NSStringEncoding stringEncoding;
/**
Whether created requests can use the devices cellular radio (if present). `YES` by default.
@see NSMutableURLRequest -setAllowsCellularAccess:
*/
@property (nonatomic, assign) BOOL allowsCellularAccess;
/**
The cache policy of created requests. `NSURLRequestUseProtocolCachePolicy` by default.
@see NSMutableURLRequest -setCachePolicy:
*/
@property (nonatomic, assign) NSURLRequestCachePolicy cachePolicy;
/**
Whether created requests should use the default cookie handling. `YES` by default.
@see NSMutableURLRequest -setHTTPShouldHandleCookies:
*/
@property (nonatomic, assign) BOOL HTTPShouldHandleCookies;
/**
Whether created requests can continue transmitting data before receiving a response from an earlier transmission. `NO` by default
@see NSMutableURLRequest -setHTTPShouldUsePipelining:
*/
@property (nonatomic, assign) BOOL HTTPShouldUsePipelining;
/**
The network service type for created requests. `NSURLNetworkServiceTypeDefault` by default.
@see NSMutableURLRequest -setNetworkServiceType:
*/
@property (nonatomic, assign) NSURLRequestNetworkServiceType networkServiceType;
/**
The timeout interval, in seconds, for created requests. The default timeout interval is 60 seconds.
@see NSMutableURLRequest -setTimeoutInterval:
*/
@property (nonatomic, assign) NSTimeInterval timeoutInterval;
///---------------------------------------
/// @name Configuring HTTP Request Headers
///---------------------------------------
/**
Default HTTP header field values to be applied to serialized requests. By default, these include the following:
- `Accept-Language` with the contents of `NSLocale +preferredLanguages`
- `User-Agent` with the contents of various bundle identifiers and OS designations
@discussion To add or remove default request headers, use `setValue:forHTTPHeaderField:`.
*/
@property (readonly, nonatomic, strong) NSDictionary *HTTPRequestHeaders;
/**
Creates and returns a serializer with default configuration.
*/
+ (instancetype)serializer;
/**
Sets the value for the HTTP headers set in request objects made by the HTTP client. If `nil`, removes the existing value for that header.
@param field The HTTP header to set a default value for
@param value The value set as default for the specified header, or `nil`
*/
- (void)setValue:(nullable NSString *)value
forHTTPHeaderField:(NSString *)field;
/**
Returns the value for the HTTP headers set in the request serializer.
@param field The HTTP header to retrieve the default value for
@return The value set as default for the specified header, or `nil`
*/
- (nullable NSString *)valueForHTTPHeaderField:(NSString *)field;
/**
Sets the "Authorization" HTTP header set in request objects made by the HTTP client to a basic authentication value with Base64-encoded username and password. This overwrites any existing value for this header.
@param username The HTTP basic auth username
@param password The HTTP basic auth password
*/
- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
password:(NSString *)password;
/**
@deprecated This method has been deprecated. Use -setValue:forHTTPHeaderField: instead.
*/
- (void)setAuthorizationHeaderFieldWithToken:(NSString *)token DEPRECATED_ATTRIBUTE;
/**
Clears any existing value for the "Authorization" HTTP header.
*/
- (void)clearAuthorizationHeader;
///-------------------------------------------------------
/// @name Configuring Query String Parameter Serialization
///-------------------------------------------------------
/**
HTTP methods for which serialized requests will encode parameters as a query string. `GET`, `HEAD`, and `DELETE` by default.
*/
@property (nonatomic, strong) NSSet *HTTPMethodsEncodingParametersInURI;
/**
Set the method of query string serialization according to one of the pre-defined styles.
@param style The serialization style.
@see AFHTTPRequestQueryStringSerializationStyle
*/
- (void)setQueryStringSerializationWithStyle:(AFHTTPRequestQueryStringSerializationStyle)style;
/**
Set the a custom method of query string serialization according to the specified block.
@param block A block that defines a process of encoding parameters into a query string. This block returns the query string and takes three arguments: the request, the parameters to encode, and the error that occurred when attempting to encode parameters for the given request.
*/
- (void)setQueryStringSerializationWithBlock:(nullable NSString * (^)(NSURLRequest *request, id parameters, NSError * __autoreleasing *error))block;
///-------------------------------
/// @name Creating Request Objects
///-------------------------------
/**
@deprecated This method has been deprecated. Use -requestWithMethod:URLString:parameters:error: instead.
*/
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters DEPRECATED_ATTRIBUTE;
/**
Creates an `NSMutableURLRequest` object with the specified HTTP method and URL string.
If the HTTP method is `GET`, `HEAD`, or `DELETE`, the parameters will be used to construct a url-encoded query string that is appended to the request's URL. Otherwise, the parameters will be encoded according to the value of the `parameterEncoding` property, and set as the request body.
@param method The HTTP method for the request, such as `GET`, `POST`, `PUT`, or `DELETE`. This parameter must not be `nil`.
@param URLString The URL string used to create the request URL.
@param parameters The parameters to be either set as a query string for `GET` requests, or the request HTTP body.
@param error The error that occurred while constructing the request.
@return An `NSMutableURLRequest` object.
*/
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(nullable id)parameters
error:(NSError * __nullable __autoreleasing *)error;
/**
@deprecated This method has been deprecated. Use -multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:error: instead.
*/
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(NSDictionary *)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block DEPRECATED_ATTRIBUTE;
/**
Creates an `NSMutableURLRequest` object with the specified HTTP method and URLString, and constructs a `multipart/form-data` HTTP body, using the specified parameters and multipart form data block. See http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.2
Multipart form requests are automatically streamed, reading files directly from disk along with in-memory data in a single HTTP body. The resulting `NSMutableURLRequest` object has an `HTTPBodyStream` property, so refrain from setting `HTTPBodyStream` or `HTTPBody` on this request object, as it will clear out the multipart form body stream.
@param method The HTTP method for the request. This parameter must not be `GET` or `HEAD`, or `nil`.
@param URLString The URL string used to create the request URL.
@param parameters The parameters to be encoded and set in the request HTTP body.
@param block A block that takes a single argument and appends data to the HTTP body. The block argument is an object adopting the `AFMultipartFormData` protocol.
@param error The error that occurred while constructing the request.
@return An `NSMutableURLRequest` object
*/
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(nullable NSDictionary *)parameters
constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block
error:(NSError * __nullable __autoreleasing *)error;
/**
Creates an `NSMutableURLRequest` by removing the `HTTPBodyStream` from a request, and asynchronously writing its contents into the specified file, invoking the completion handler when finished.
@param request The multipart form request. The `HTTPBodyStream` property of `request` must not be `nil`.
@param fileURL The file URL to write multipart form contents to.
@param handler A handler block to execute.
@discussion There is a bug in `NSURLSessionTask` that causes requests to not send a `Content-Length` header when streaming contents from an HTTP body, which is notably problematic when interacting with the Amazon S3 webservice. As a workaround, this method takes a request constructed with `multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:error:`, or any other request with an `HTTPBodyStream`, writes the contents to the specified file and returns a copy of the original request with the `HTTPBodyStream` property set to `nil`. From here, the file can either be passed to `AFURLSessionManager -uploadTaskWithRequest:fromFile:progress:completionHandler:`, or have its contents read into an `NSData` that's assigned to the `HTTPBody` property of the request.
@see https://github.com/AFNetworking/AFNetworking/issues/1398
*/
- (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request
writingStreamContentsToFile:(NSURL *)fileURL
completionHandler:(nullable void (^)(NSError * __nullable error))handler;
@end
#pragma mark -
/**
The `AFMultipartFormData` protocol defines the methods supported by the parameter in the block argument of `AFHTTPRequestSerializer -multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:`.
*/
@protocol AFMultipartFormData
/**
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.
The filename and MIME type for this data in the form will be automatically generated, using the last path component of the `fileURL` and system associated MIME type for the `fileURL` extension, respectively.
@param fileURL The URL corresponding to the file whose content will be appended to the form. This parameter must not be `nil`.
@param name The name to be associated with the specified data. This parameter must not be `nil`.
@param error If an error occurs, upon return contains an `NSError` object that describes the problem.
@return `YES` if the file data was successfully appended, otherwise `NO`.
*/
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
name:(NSString *)name
error:(NSError * __nullable __autoreleasing *)error;
/**
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 fileURL The URL corresponding to the file whose content will be appended to the form. This parameter must not be `nil`.
@param name The name to be associated with the specified data. This parameter must not be `nil`.
@param fileName The file name to be used in the `Content-Disposition` header. This parameter must not be `nil`.
@param mimeType The declared MIME type of the file data. This parameter must not be `nil`.
@param error If an error occurs, upon return contains an `NSError` object that describes the problem.
@return `YES` if the file data was successfully appended otherwise `NO`.
*/
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
name:(NSString *)name
fileName:(NSString *)fileName
mimeType:(NSString *)mimeType
error:(NSError * __nullable __autoreleasing *)error;
/**
Appends the HTTP header `Content-Disposition: file; filename=#{filename}; name=#{name}"` and `Content-Type: #{mimeType}`, followed by the data from the input stream and the multipart form boundary.
@param inputStream The input stream to be appended to the form data
@param name The name to be associated with the specified input stream. This parameter must not be `nil`.
@param fileName The filename to be associated with the specified input stream. This parameter must not be `nil`.
@param length The length of the specified input stream in bytes.
@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`.
*/
- (void)appendPartWithInputStream:(nullable NSInputStream *)inputStream
name:(NSString *)name
fileName:(NSString *)fileName
length:(int64_t)length
mimeType:(NSString *)mimeType;
/**
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`.
*/
- (void)appendPartWithFileData:(NSData *)data
name:(NSString *)name
fileName:(NSString *)fileName
mimeType:(NSString *)mimeType;
/**
Appends the HTTP headers `Content-Disposition: form-data; name=#{name}"`, followed by the encoded data and the multipart form boundary.
@param data The data to be encoded and appended to the form data.
@param name The name to be associated with the specified data. This parameter must not be `nil`.
*/
- (void)appendPartWithFormData:(NSData *)data
name:(NSString *)name;
/**
Appends HTTP headers, followed by the encoded data and the multipart form boundary.
@param headers The HTTP headers to be appended to the form data.
@param body The data to be encoded and appended to the form data. This parameter must not be `nil`.
*/
- (void)appendPartWithHeaders:(nullable NSDictionary *)headers
body:(NSData *)body;
/**
Throttles request bandwidth by limiting the packet size and adding a delay for each chunk read from the upload stream.
When uploading over a 3G or EDGE connection, requests may fail with "request body stream exhausted". Setting a maximum packet size and delay according to the recommended values (`kAFUploadStream3GSuggestedPacketSize` and `kAFUploadStream3GSuggestedDelay`) lowers the risk of the input stream exceeding its allocated bandwidth. Unfortunately, there is no definite way to distinguish between a 3G, EDGE, or LTE connection over `NSURLConnection`. As such, it is not recommended that you throttle bandwidth based solely on network reachability. Instead, you should consider checking for the "request body stream exhausted" in a failure block, and then retrying the request with throttled bandwidth.
@param numberOfBytes Maximum packet size, in number of bytes. The default packet size for an input stream is 16kb.
@param delay Duration of delay each time a packet is read. By default, no delay is set.
*/
- (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes
delay:(NSTimeInterval)delay;
@end
#pragma mark -
/**
`AFJSONRequestSerializer` is a subclass of `AFHTTPRequestSerializer` that encodes parameters as JSON using `NSJSONSerialization`, setting the `Content-Type` of the encoded request to `application/json`.
*/
@interface AFJSONRequestSerializer : AFHTTPRequestSerializer
/**
Options for writing the request JSON data from Foundation objects. For possible values, see the `NSJSONSerialization` documentation section "NSJSONWritingOptions". `0` by default.
*/
@property (nonatomic, assign) NSJSONWritingOptions writingOptions;
/**
Creates and returns a JSON serializer with specified reading and writing options.
@param writingOptions The specified JSON writing options.
*/
+ (instancetype)serializerWithWritingOptions:(NSJSONWritingOptions)writingOptions;
@end
#pragma mark -
/**
`AFPropertyListRequestSerializer` is a subclass of `AFHTTPRequestSerializer` that encodes parameters as JSON using `NSPropertyListSerializer`, setting the `Content-Type` of the encoded request to `application/x-plist`.
*/
@interface AFPropertyListRequestSerializer : AFHTTPRequestSerializer
/**
The property list format. Possible values are described in "NSPropertyListFormat".
*/
@property (nonatomic, assign) NSPropertyListFormat format;
/**
@warning The `writeOptions` property is currently unused.
*/
@property (nonatomic, assign) NSPropertyListWriteOptions writeOptions;
/**
Creates and returns a property list serializer with a specified format, read options, and write options.
@param format The property list format.
@param writeOptions The property list write options.
@warning The `writeOptions` property is currently unused.
*/
+ (instancetype)serializerWithFormat:(NSPropertyListFormat)format
writeOptions:(NSPropertyListWriteOptions)writeOptions;
@end
#pragma mark -
///----------------
/// @name Constants
///----------------
/**
## Error Domains
The following error domain is predefined.
- `NSString * const AFURLRequestSerializationErrorDomain`
### Constants
`AFURLRequestSerializationErrorDomain`
AFURLRequestSerializer errors. Error codes for `AFURLRequestSerializationErrorDomain` correspond to codes in `NSURLErrorDomain`.
*/
FOUNDATION_EXPORT NSString * const AFURLRequestSerializationErrorDomain;
/**
## User info dictionary keys
These keys may exist in the user info dictionary, in addition to those defined for NSError.
- `NSString * const AFNetworkingOperationFailingURLRequestErrorKey`
### 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 `AFURLRequestSerializationErrorDomain`.
*/
FOUNDATION_EXPORT NSString * const AFNetworkingOperationFailingURLRequestErrorKey;
/**
## Throttling Bandwidth for HTTP Request Input Streams
@see -throttleBandwidthWithPacketSize:delay:
### Constants
`kAFUploadStream3GSuggestedPacketSize`
Maximum packet size, in number of bytes. Equal to 16kb.
`kAFUploadStream3GSuggestedDelay`
Duration of delay each time a packet is read. Equal to 0.2 seconds.
*/
FOUNDATION_EXPORT NSUInteger const kAFUploadStream3GSuggestedPacketSize;
FOUNDATION_EXPORT NSTimeInterval const kAFUploadStream3GSuggestedDelay;
NS_ASSUME_NONNULL_END

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,315 @@
// AFURLResponseSerialization.h
// Copyright (c) 20112015 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
#import <CoreGraphics/CoreGraphics.h>
NS_ASSUME_NONNULL_BEGIN
/**
The `AFURLResponseSerialization` protocol is adopted by an object that decodes data into a more useful object representation, according to details in the server response. Response serializers may additionally perform validation on the incoming response and data.
For example, a JSON response serializer may check for an acceptable status code (`2XX` range) and content type (`application/json`), decoding a valid JSON response into an object.
*/
@protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying>
/**
The response object decoded from the data associated with a specified response.
@param response The response to be processed.
@param data The response data to be decoded.
@param error The error that occurred while attempting to decode the response data.
@return The object decoded from the specified response data.
*/
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
data:(nullable NSData *)data
error:(NSError * __nullable __autoreleasing *)error
#ifdef NS_SWIFT_NOTHROW
NS_SWIFT_NOTHROW
#endif
;
@end
#pragma mark -
/**
`AFHTTPResponseSerializer` conforms to the `AFURLRequestSerialization` & `AFURLResponseSerialization` protocols, offering a concrete base implementation of query string / URL form-encoded parameter serialization and default request headers, as well as response status code and content type validation.
Any request or response serializer dealing with HTTP is encouraged to subclass `AFHTTPResponseSerializer` in order to ensure consistent default behavior.
*/
@interface AFHTTPResponseSerializer : NSObject <AFURLResponseSerialization>
- (instancetype)init;
/**
The string encoding used to serialize data received from the server, when no string encoding is specified by the response. `NSUTF8StringEncoding` by default.
*/
@property (nonatomic, assign) NSStringEncoding stringEncoding;
/**
Creates and returns a serializer with default configuration.
*/
+ (instancetype)serializer;
///-----------------------------------------
/// @name Configuring Response Serialization
///-----------------------------------------
/**
The acceptable HTTP status codes for responses. When non-`nil`, responses with status codes not contained by the set will result in an error during validation.
See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
*/
@property (nonatomic, copy, nullable) NSIndexSet *acceptableStatusCodes;
/**
The acceptable MIME types for responses. When non-`nil`, responses with a `Content-Type` with MIME types that do not intersect with the set will result in an error during validation.
*/
@property (nonatomic, copy, nullable) NSSet *acceptableContentTypes;
/**
Validates the specified response and data.
In its base implementation, this method checks for an acceptable status code and content type. Subclasses may wish to add other domain-specific checks.
@param response The response to be validated.
@param data The data associated with the response.
@param error The error that occurred while attempting to validate the response.
@return `YES` if the response is valid, otherwise `NO`.
*/
- (BOOL)validateResponse:(nullable NSHTTPURLResponse *)response
data:(nullable NSData *)data
error:(NSError * __nullable __autoreleasing *)error;
@end
#pragma mark -
/**
`AFJSONResponseSerializer` is a subclass of `AFHTTPResponseSerializer` that validates and decodes JSON responses.
By default, `AFJSONResponseSerializer` accepts the following MIME types, which includes the official standard, `application/json`, as well as other commonly-used types:
- `application/json`
- `text/json`
- `text/javascript`
*/
@interface AFJSONResponseSerializer : AFHTTPResponseSerializer
- (instancetype)init;
/**
Options for reading the response JSON data and creating the Foundation objects. For possible values, see the `NSJSONSerialization` documentation section "NSJSONReadingOptions". `0` by default.
*/
@property (nonatomic, assign) NSJSONReadingOptions readingOptions;
/**
Whether to remove keys with `NSNull` values from response JSON. Defaults to `NO`.
*/
@property (nonatomic, assign) BOOL removesKeysWithNullValues;
/**
Creates and returns a JSON serializer with specified reading and writing options.
@param readingOptions The specified JSON reading options.
*/
+ (instancetype)serializerWithReadingOptions:(NSJSONReadingOptions)readingOptions;
@end
#pragma mark -
/**
`AFXMLParserResponseSerializer` is a subclass of `AFHTTPResponseSerializer` that validates and decodes XML responses as an `NSXMLParser` objects.
By default, `AFXMLParserResponseSerializer` accepts the following MIME types, which includes the official standard, `application/xml`, as well as other commonly-used types:
- `application/xml`
- `text/xml`
*/
@interface AFXMLParserResponseSerializer : AFHTTPResponseSerializer
@end
#pragma mark -
#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED
/**
`AFXMLDocumentResponseSerializer` is a subclass of `AFHTTPResponseSerializer` that validates and decodes XML responses as an `NSXMLDocument` objects.
By default, `AFXMLDocumentResponseSerializer` accepts the following MIME types, which includes the official standard, `application/xml`, as well as other commonly-used types:
- `application/xml`
- `text/xml`
*/
@interface AFXMLDocumentResponseSerializer : AFHTTPResponseSerializer
- (instancetype)init;
/**
Input and output options specifically intended for `NSXMLDocument` objects. For possible values, see the `NSJSONSerialization` documentation section "NSJSONReadingOptions". `0` by default.
*/
@property (nonatomic, assign) NSUInteger options;
/**
Creates and returns an XML document serializer with the specified options.
@param mask The XML document options.
*/
+ (instancetype)serializerWithXMLDocumentOptions:(NSUInteger)mask;
@end
#endif
#pragma mark -
/**
`AFPropertyListResponseSerializer` is a subclass of `AFHTTPResponseSerializer` that validates and decodes XML responses as an `NSXMLDocument` objects.
By default, `AFPropertyListResponseSerializer` accepts the following MIME types:
- `application/x-plist`
*/
@interface AFPropertyListResponseSerializer : AFHTTPResponseSerializer
- (instancetype)init;
/**
The property list format. Possible values are described in "NSPropertyListFormat".
*/
@property (nonatomic, assign) NSPropertyListFormat format;
/**
The property list reading options. Possible values are described in "NSPropertyListMutabilityOptions."
*/
@property (nonatomic, assign) NSPropertyListReadOptions readOptions;
/**
Creates and returns a property list serializer with a specified format, read options, and write options.
@param format The property list format.
@param readOptions The property list reading options.
*/
+ (instancetype)serializerWithFormat:(NSPropertyListFormat)format
readOptions:(NSPropertyListReadOptions)readOptions;
@end
#pragma mark -
/**
`AFImageResponseSerializer` is a subclass of `AFHTTPResponseSerializer` that validates and decodes image responses.
By default, `AFImageResponseSerializer` accepts the following MIME types, which correspond to the image formats supported by UIImage or NSImage:
- `image/tiff`
- `image/jpeg`
- `image/gif`
- `image/png`
- `image/ico`
- `image/x-icon`
- `image/bmp`
- `image/x-bmp`
- `image/x-xbitmap`
- `image/x-win-bitmap`
*/
@interface AFImageResponseSerializer : AFHTTPResponseSerializer
#if defined(__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 scale of the main screen by default, which automatically scales images for retina displays, for instance.
*/
@property (nonatomic, assign) CGFloat imageScale;
/**
Whether to automatically inflate response image data for compressed formats (such as PNG or JPEG). Enabling this can significantly improve drawing performance on iOS when used with `setCompletionBlockWithSuccess:failure:`, as it allows a bitmap representation to be constructed in the background rather than on the main thread. `YES` by default.
*/
@property (nonatomic, assign) BOOL automaticallyInflatesResponseImage;
#endif
@end
#pragma mark -
/**
`AFCompoundSerializer` is a subclass of `AFHTTPResponseSerializer` that delegates the response serialization to the first `AFHTTPResponseSerializer` object that returns an object for `responseObjectForResponse:data:error:`, falling back on the default behavior of `AFHTTPResponseSerializer`. This is useful for supporting multiple potential types and structures of server responses with a single serializer.
*/
@interface AFCompoundResponseSerializer : AFHTTPResponseSerializer
/**
The component response serializers.
*/
@property (readonly, nonatomic, copy) NSArray *responseSerializers;
/**
Creates and returns a compound serializer comprised of the specified response serializers.
@warning Each response serializer specified must be a subclass of `AFHTTPResponseSerializer`, and response to `-validateResponse:data:error:`.
*/
+ (instancetype)compoundSerializerWithResponseSerializers:(NSArray *)responseSerializers;
@end
///----------------
/// @name Constants
///----------------
/**
## Error Domains
The following error domain is predefined.
- `NSString * const AFURLResponseSerializationErrorDomain`
### Constants
`AFURLResponseSerializationErrorDomain`
AFURLResponseSerializer errors. Error codes for `AFURLResponseSerializationErrorDomain` correspond to codes in `NSURLErrorDomain`.
*/
FOUNDATION_EXPORT NSString * const AFURLResponseSerializationErrorDomain;
/**
## User info dictionary keys
These keys may exist in the user info dictionary, in addition to those defined for NSError.
- `NSString * const AFNetworkingOperationFailingURLResponseErrorKey`
- `NSString * const AFNetworkingOperationFailingURLResponseDataErrorKey`
### Constants
`AFNetworkingOperationFailingURLResponseErrorKey`
The corresponding value is an `NSURLResponse` containing the response of the operation associated with an error. This key is only present in the `AFURLResponseSerializationErrorDomain`.
`AFNetworkingOperationFailingURLResponseDataErrorKey`
The corresponding value is an `NSData` containing the original data of the operation associated with an error. This key is only present in the `AFURLResponseSerializationErrorDomain`.
*/
FOUNDATION_EXPORT NSString * const AFNetworkingOperationFailingURLResponseErrorKey;
FOUNDATION_EXPORT NSString * const AFNetworkingOperationFailingURLResponseDataErrorKey;
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,825 @@
// AFURLResponseSerialization.m
// Copyright (c) 20112015 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "AFURLResponseSerialization.h"
#if TARGET_OS_IOS
#import <UIKit/UIKit.h>
#elif TARGET_OS_WATCH
#import <WatchKit/WatchKit.h>
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
#import <Cocoa/Cocoa.h>
#endif
NSString * const AFURLResponseSerializationErrorDomain = @"com.alamofire.error.serialization.response";
NSString * const AFNetworkingOperationFailingURLResponseErrorKey = @"com.alamofire.serialization.response.error.response";
NSString * const AFNetworkingOperationFailingURLResponseDataErrorKey = @"com.alamofire.serialization.response.error.data";
static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
if (!error) {
return underlyingError;
}
if (!underlyingError || error.userInfo[NSUnderlyingErrorKey]) {
return error;
}
NSMutableDictionary *mutableUserInfo = [error.userInfo mutableCopy];
mutableUserInfo[NSUnderlyingErrorKey] = underlyingError;
return [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:mutableUserInfo];
}
static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger code, NSString *domain) {
if ([error.domain isEqualToString:domain] && error.code == code) {
return YES;
} else if (error.userInfo[NSUnderlyingErrorKey]) {
return AFErrorOrUnderlyingErrorHasCodeInDomain(error.userInfo[NSUnderlyingErrorKey], code, domain);
}
return NO;
}
static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) {
if ([JSONObject isKindOfClass:[NSArray class]]) {
NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]];
for (id value in (NSArray *)JSONObject) {
[mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)];
}
return (readingOptions & NSJSONReadingMutableContainers) ? mutableArray : [NSArray arrayWithArray:mutableArray];
} else if ([JSONObject isKindOfClass:[NSDictionary class]]) {
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:JSONObject];
for (id <NSCopying> key in [(NSDictionary *)JSONObject allKeys]) {
id value = (NSDictionary *)JSONObject[key];
if (!value || [value isEqual:[NSNull null]]) {
[mutableDictionary removeObjectForKey:key];
} else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) {
mutableDictionary[key] = AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions);
}
}
return (readingOptions & NSJSONReadingMutableContainers) ? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary];
}
return JSONObject;
}
@implementation AFHTTPResponseSerializer
+ (instancetype)serializer {
return [[self alloc] init];
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.stringEncoding = NSUTF8StringEncoding;
self.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)];
self.acceptableContentTypes = nil;
return self;
}
#pragma mark -
- (BOOL)validateResponse:(NSHTTPURLResponse *)response
data:(NSData *)data
error:(NSError * __autoreleasing *)error
{
BOOL responseIsValid = YES;
NSError *validationError = nil;
if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]]) {
if ([data length] > 0 && [response URL]) {
NSMutableDictionary *mutableUserInfo = [@{
NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: unacceptable content-type: %@", @"AFNetworking", nil), [response MIMEType]],
NSURLErrorFailingURLErrorKey:[response URL],
AFNetworkingOperationFailingURLResponseErrorKey: response,
} mutableCopy];
if (data) {
mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
}
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:mutableUserInfo], validationError);
}
responseIsValid = NO;
}
if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {
NSMutableDictionary *mutableUserInfo = [@{
NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: %@ (%ld)", @"AFNetworking", nil), [NSHTTPURLResponse localizedStringForStatusCode:response.statusCode], (long)response.statusCode],
NSURLErrorFailingURLErrorKey:[response URL],
AFNetworkingOperationFailingURLResponseErrorKey: response,
} mutableCopy];
if (data) {
mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
}
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);
responseIsValid = NO;
}
}
if (error && !responseIsValid) {
*error = validationError;
}
return responseIsValid;
}
#pragma mark - AFURLResponseSerialization
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
[self validateResponse:(NSHTTPURLResponse *)response data:data error:error];
return data;
}
#pragma mark - NSSecureCoding
+ (BOOL)supportsSecureCoding {
return YES;
}
- (id)initWithCoder:(NSCoder *)decoder {
self = [self init];
if (!self) {
return nil;
}
self.acceptableStatusCodes = [decoder decodeObjectOfClass:[NSIndexSet class] forKey:NSStringFromSelector(@selector(acceptableStatusCodes))];
self.acceptableContentTypes = [decoder decodeObjectOfClass:[NSIndexSet class] forKey:NSStringFromSelector(@selector(acceptableContentTypes))];
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:self.acceptableStatusCodes forKey:NSStringFromSelector(@selector(acceptableStatusCodes))];
[coder encodeObject:self.acceptableContentTypes forKey:NSStringFromSelector(@selector(acceptableContentTypes))];
}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {
AFHTTPResponseSerializer *serializer = [[[self class] allocWithZone:zone] init];
serializer.acceptableStatusCodes = [self.acceptableStatusCodes copyWithZone:zone];
serializer.acceptableContentTypes = [self.acceptableContentTypes copyWithZone:zone];
return serializer;
}
@end
#pragma mark -
@implementation AFJSONResponseSerializer
+ (instancetype)serializer {
return [self serializerWithReadingOptions:(NSJSONReadingOptions)0];
}
+ (instancetype)serializerWithReadingOptions:(NSJSONReadingOptions)readingOptions {
AFJSONResponseSerializer *serializer = [[self alloc] init];
serializer.readingOptions = readingOptions;
return serializer;
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil];
return self;
}
#pragma mark - AFURLResponseSerialization
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
return nil;
}
}
// Workaround for behavior of Rails to return a single space for `head :ok` (a workaround for a bug in Safari), which is not interpreted as valid input by NSJSONSerialization.
// See https://github.com/rails/rails/issues/1742
NSStringEncoding stringEncoding = self.stringEncoding;
if (response.textEncodingName) {
CFStringEncoding encoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
if (encoding != kCFStringEncodingInvalidId) {
stringEncoding = CFStringConvertEncodingToNSStringEncoding(encoding);
}
}
id responseObject = nil;
NSError *serializationError = nil;
@autoreleasepool {
NSString *responseString = [[NSString alloc] initWithData:data encoding:stringEncoding];
if (responseString && ![responseString isEqualToString:@" "]) {
// Workaround for a bug in NSJSONSerialization when Unicode character escape codes are used instead of the actual character
// See http://stackoverflow.com/a/12843465/157142
data = [responseString dataUsingEncoding:NSUTF8StringEncoding];
if (data) {
if ([data length] > 0) {
responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];
} else {
return nil;
}
} else {
NSDictionary *userInfo = @{
NSLocalizedDescriptionKey: NSLocalizedStringFromTable(@"Data failed decoding as a UTF-8 string", @"AFNetworking", nil),
NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Could not decode string: %@", @"AFNetworking", nil), responseString]
};
serializationError = [NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:userInfo];
}
}
}
if (self.removesKeysWithNullValues && responseObject) {
responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
}
if (error) {
*error = AFErrorWithUnderlyingError(serializationError, *error);
}
return responseObject;
}
#pragma mark - NSSecureCoding
- (id)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder];
if (!self) {
return nil;
}
self.readingOptions = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(readingOptions))] unsignedIntegerValue];
self.removesKeysWithNullValues = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(removesKeysWithNullValues))] boolValue];
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[super encodeWithCoder:coder];
[coder encodeObject:@(self.readingOptions) forKey:NSStringFromSelector(@selector(readingOptions))];
[coder encodeObject:@(self.removesKeysWithNullValues) forKey:NSStringFromSelector(@selector(removesKeysWithNullValues))];
}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {
AFJSONResponseSerializer *serializer = [[[self class] allocWithZone:zone] init];
serializer.readingOptions = self.readingOptions;
serializer.removesKeysWithNullValues = self.removesKeysWithNullValues;
return serializer;
}
@end
#pragma mark -
@implementation AFXMLParserResponseSerializer
+ (instancetype)serializer {
AFXMLParserResponseSerializer *serializer = [[self alloc] init];
return serializer;
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"application/xml", @"text/xml", nil];
return self;
}
#pragma mark - AFURLResponseSerialization
- (id)responseObjectForResponse:(NSHTTPURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
return nil;
}
}
return [[NSXMLParser alloc] initWithData:data];
}
@end
#pragma mark -
#ifdef __MAC_OS_X_VERSION_MIN_REQUIRED
@implementation AFXMLDocumentResponseSerializer
+ (instancetype)serializer {
return [self serializerWithXMLDocumentOptions:0];
}
+ (instancetype)serializerWithXMLDocumentOptions:(NSUInteger)mask {
AFXMLDocumentResponseSerializer *serializer = [[self alloc] init];
serializer.options = mask;
return serializer;
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"application/xml", @"text/xml", nil];
return self;
}
#pragma mark - AFURLResponseSerialization
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
return nil;
}
}
NSError *serializationError = nil;
NSXMLDocument *document = [[NSXMLDocument alloc] initWithData:data options:self.options error:&serializationError];
if (error) {
*error = AFErrorWithUnderlyingError(serializationError, *error);
}
return document;
}
#pragma mark - NSSecureCoding
- (id)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder];
if (!self) {
return nil;
}
self.options = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(options))] unsignedIntegerValue];
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[super encodeWithCoder:coder];
[coder encodeObject:@(self.options) forKey:NSStringFromSelector(@selector(options))];
}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {
AFXMLDocumentResponseSerializer *serializer = [[[self class] allocWithZone:zone] init];
serializer.options = self.options;
return serializer;
}
@end
#endif
#pragma mark -
@implementation AFPropertyListResponseSerializer
+ (instancetype)serializer {
return [self serializerWithFormat:NSPropertyListXMLFormat_v1_0 readOptions:0];
}
+ (instancetype)serializerWithFormat:(NSPropertyListFormat)format
readOptions:(NSPropertyListReadOptions)readOptions
{
AFPropertyListResponseSerializer *serializer = [[self alloc] init];
serializer.format = format;
serializer.readOptions = readOptions;
return serializer;
}
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"application/x-plist", nil];
return self;
}
#pragma mark - AFURLResponseSerialization
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
return nil;
}
}
id responseObject;
NSError *serializationError = nil;
if (data) {
responseObject = [NSPropertyListSerialization propertyListWithData:data options:self.readOptions format:NULL error:&serializationError];
}
if (error) {
*error = AFErrorWithUnderlyingError(serializationError, *error);
}
return responseObject;
}
#pragma mark - NSSecureCoding
- (id)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder];
if (!self) {
return nil;
}
self.format = (NSPropertyListFormat)[[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(format))] unsignedIntegerValue];
self.readOptions = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(readOptions))] unsignedIntegerValue];
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[super encodeWithCoder:coder];
[coder encodeObject:@(self.format) forKey:NSStringFromSelector(@selector(format))];
[coder encodeObject:@(self.readOptions) forKey:NSStringFromSelector(@selector(readOptions))];
}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {
AFPropertyListResponseSerializer *serializer = [[[self class] allocWithZone:zone] init];
serializer.format = self.format;
serializer.readOptions = self.readOptions;
return serializer;
}
@end
#pragma mark -
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
#import <CoreGraphics/CoreGraphics.h>
@interface UIImage (AFNetworkingSafeImageLoading)
+ (UIImage *)af_safeImageWithData:(NSData *)data;
@end
static NSLock* imageLock = nil;
@implementation UIImage (AFNetworkingSafeImageLoading)
+ (UIImage *)af_safeImageWithData:(NSData *)data {
UIImage* image = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
imageLock = [[NSLock alloc] init];
});
[imageLock lock];
image = [UIImage imageWithData:data];
[imageLock unlock];
return image;
}
@end
static UIImage * AFImageWithDataAtScale(NSData *data, CGFloat scale) {
UIImage *image = [UIImage af_safeImageWithData:data];
if (image.images) {
return image;
}
return [[UIImage alloc] initWithCGImage:[image CGImage] scale:scale orientation:image.imageOrientation];
}
static UIImage * AFInflatedImageFromResponseWithDataAtScale(NSHTTPURLResponse *response, NSData *data, CGFloat scale) {
if (!data || [data length] == 0) {
return nil;
}
CGImageRef imageRef = NULL;
CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
if ([response.MIMEType isEqualToString:@"image/png"]) {
imageRef = CGImageCreateWithPNGDataProvider(dataProvider, NULL, true, kCGRenderingIntentDefault);
} else if ([response.MIMEType isEqualToString:@"image/jpeg"]) {
imageRef = CGImageCreateWithJPEGDataProvider(dataProvider, NULL, true, kCGRenderingIntentDefault);
if (imageRef) {
CGColorSpaceRef imageColorSpace = CGImageGetColorSpace(imageRef);
CGColorSpaceModel imageColorSpaceModel = CGColorSpaceGetModel(imageColorSpace);
// CGImageCreateWithJPEGDataProvider does not properly handle CMKY, so fall back to AFImageWithDataAtScale
if (imageColorSpaceModel == kCGColorSpaceModelCMYK) {
CGImageRelease(imageRef);
imageRef = NULL;
}
}
}
CGDataProviderRelease(dataProvider);
UIImage *image = AFImageWithDataAtScale(data, scale);
if (!imageRef) {
if (image.images || !image) {
return image;
}
imageRef = CGImageCreateCopy([image CGImage]);
if (!imageRef) {
return nil;
}
}
size_t width = CGImageGetWidth(imageRef);
size_t height = CGImageGetHeight(imageRef);
size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
if (width * height > 1024 * 1024 || bitsPerComponent > 8) {
CGImageRelease(imageRef);
return image;
}
// CGImageGetBytesPerRow() calculates incorrectly in iOS 5.0, so defer to CGBitmapContextCreate
size_t bytesPerRow = 0;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGColorSpaceModel colorSpaceModel = CGColorSpaceGetModel(colorSpace);
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
if (colorSpaceModel == kCGColorSpaceModelRGB) {
uint32_t alpha = (bitmapInfo & kCGBitmapAlphaInfoMask);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wassign-enum"
if (alpha == kCGImageAlphaNone) {
bitmapInfo &= ~kCGBitmapAlphaInfoMask;
bitmapInfo |= kCGImageAlphaNoneSkipFirst;
} else if (!(alpha == kCGImageAlphaNoneSkipFirst || alpha == kCGImageAlphaNoneSkipLast)) {
bitmapInfo &= ~kCGBitmapAlphaInfoMask;
bitmapInfo |= kCGImageAlphaPremultipliedFirst;
}
#pragma clang diagnostic pop
}
CGContextRef context = CGBitmapContextCreate(NULL, width, height, bitsPerComponent, bytesPerRow, colorSpace, bitmapInfo);
CGColorSpaceRelease(colorSpace);
if (!context) {
CGImageRelease(imageRef);
return image;
}
CGContextDrawImage(context, CGRectMake(0.0f, 0.0f, width, height), imageRef);
CGImageRef inflatedImageRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);
UIImage *inflatedImage = [[UIImage alloc] initWithCGImage:inflatedImageRef scale:scale orientation:image.imageOrientation];
CGImageRelease(inflatedImageRef);
CGImageRelease(imageRef);
return inflatedImage;
}
#endif
@implementation AFImageResponseSerializer
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.acceptableContentTypes = [[NSSet alloc] initWithObjects:@"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];
#if TARGET_OS_IOS
self.imageScale = [[UIScreen mainScreen] scale];
self.automaticallyInflatesResponseImage = YES;
#elif TARGET_OS_WATCH
self.imageScale = [[WKInterfaceDevice currentDevice] screenScale];
self.automaticallyInflatesResponseImage = YES;
#endif
return self;
}
#pragma mark - AFURLResponseSerializer
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
return nil;
}
}
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
if (self.automaticallyInflatesResponseImage) {
return AFInflatedImageFromResponseWithDataAtScale((NSHTTPURLResponse *)response, data, self.imageScale);
} else {
return AFImageWithDataAtScale(data, self.imageScale);
}
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
// Ensure that the image is set to it's correct pixel width and height
NSBitmapImageRep *bitimage = [[NSBitmapImageRep alloc] initWithData:data];
NSImage *image = [[NSImage alloc] initWithSize:NSMakeSize([bitimage pixelsWide], [bitimage pixelsHigh])];
[image addRepresentation:bitimage];
return image;
#endif
return nil;
}
#pragma mark - NSSecureCoding
- (id)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder];
if (!self) {
return nil;
}
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
NSNumber *imageScale = [decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(imageScale))];
#if CGFLOAT_IS_DOUBLE
self.imageScale = [imageScale doubleValue];
#else
self.imageScale = [imageScale floatValue];
#endif
self.automaticallyInflatesResponseImage = [decoder decodeBoolForKey:NSStringFromSelector(@selector(automaticallyInflatesResponseImage))];
#endif
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[super encodeWithCoder:coder];
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
[coder encodeObject:@(self.imageScale) forKey:NSStringFromSelector(@selector(imageScale))];
[coder encodeBool:self.automaticallyInflatesResponseImage forKey:NSStringFromSelector(@selector(automaticallyInflatesResponseImage))];
#endif
}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {
AFImageResponseSerializer *serializer = [[[self class] allocWithZone:zone] init];
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
serializer.imageScale = self.imageScale;
serializer.automaticallyInflatesResponseImage = self.automaticallyInflatesResponseImage;
#endif
return serializer;
}
@end
#pragma mark -
@interface AFCompoundResponseSerializer ()
@property (readwrite, nonatomic, copy) NSArray *responseSerializers;
@end
@implementation AFCompoundResponseSerializer
+ (instancetype)compoundSerializerWithResponseSerializers:(NSArray *)responseSerializers {
AFCompoundResponseSerializer *serializer = [[self alloc] init];
serializer.responseSerializers = responseSerializers;
return serializer;
}
#pragma mark - AFURLResponseSerialization
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
for (id <AFURLResponseSerialization> serializer in self.responseSerializers) {
if (![serializer isKindOfClass:[AFHTTPResponseSerializer class]]) {
continue;
}
NSError *serializerError = nil;
id responseObject = [serializer responseObjectForResponse:response data:data error:&serializerError];
if (responseObject) {
if (error) {
*error = AFErrorWithUnderlyingError(serializerError, *error);
}
return responseObject;
}
}
return [super responseObjectForResponse:response data:data error:error];
}
#pragma mark - NSSecureCoding
- (id)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder];
if (!self) {
return nil;
}
self.responseSerializers = [decoder decodeObjectOfClass:[NSArray class] forKey:NSStringFromSelector(@selector(responseSerializers))];
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[super encodeWithCoder:coder];
[coder encodeObject:self.responseSerializers forKey:NSStringFromSelector(@selector(responseSerializers))];
}
#pragma mark - NSCopying
- (id)copyWithZone:(NSZone *)zone {
AFCompoundResponseSerializer *serializer = [[[self class] allocWithZone:zone] init];
serializer.responseSerializers = self.responseSerializers;
return serializer;
}
@end

View File

@ -0,0 +1,554 @@
// AFURLSessionManager.h
// Copyright (c) 20112015 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
#import "AFURLResponseSerialization.h"
#import "AFURLRequestSerialization.h"
#import "AFSecurityPolicy.h"
#if !TARGET_OS_WATCH
#import "AFNetworkReachabilityManager.h"
#endif
#ifndef NS_DESIGNATED_INITIALIZER
#if __has_attribute(objc_designated_initializer)
#define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))
#else
#define NS_DESIGNATED_INITIALIZER
#endif
#endif
/**
`AFURLSessionManager` creates and manages an `NSURLSession` object based on a specified `NSURLSessionConfiguration` object, which conforms to `<NSURLSessionTaskDelegate>`, `<NSURLSessionDataDelegate>`, `<NSURLSessionDownloadDelegate>`, and `<NSURLSessionDelegate>`.
## Subclassing Notes
This is the base class for `AFHTTPSessionManager`, which adds functionality specific to making HTTP requests. If you are looking to extend `AFURLSessionManager` specifically for HTTP, consider subclassing `AFHTTPSessionManager` instead.
## NSURLSession & NSURLSessionTask Delegate Methods
`AFURLSessionManager` implements the following delegate methods:
### `NSURLSessionDelegate`
- `URLSession:didBecomeInvalidWithError:`
- `URLSession:didReceiveChallenge:completionHandler:`
- `URLSessionDidFinishEventsForBackgroundURLSession:`
### `NSURLSessionTaskDelegate`
- `URLSession:willPerformHTTPRedirection:newRequest:completionHandler:`
- `URLSession:task:didReceiveChallenge:completionHandler:`
- `URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:`
- `URLSession:task:didCompleteWithError:`
### `NSURLSessionDataDelegate`
- `URLSession:dataTask:didReceiveResponse:completionHandler:`
- `URLSession:dataTask:didBecomeDownloadTask:`
- `URLSession:dataTask:didReceiveData:`
- `URLSession:dataTask:willCacheResponse:completionHandler:`
### `NSURLSessionDownloadDelegate`
- `URLSession:downloadTask:didFinishDownloadingToURL:`
- `URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesWritten:totalBytesExpectedToWrite:`
- `URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:`
If any of these methods are overridden in a subclass, they _must_ call the `super` implementation first.
## Network Reachability Monitoring
Network reachability status and change monitoring is available through the `reachabilityManager` property. Applications may choose to monitor network reachability conditions in order to prevent or suspend any outbound requests. See `AFNetworkReachabilityManager` for more details.
## NSCoding Caveats
- Encoded managers do not include any block properties. Be sure to set delegate callback blocks when using `-initWithCoder:` or `NSKeyedUnarchiver`.
## NSCopying Caveats
- `-copy` and `-copyWithZone:` return a new manager with a new `NSURLSession` created from the configuration of the original.
- Operation copies do not include any delegate callback blocks, as they often strongly captures a reference to `self`, which would otherwise have the unintuitive side-effect of pointing to the _original_ session manager when copied.
@warning Managers for background sessions must be owned for the duration of their use. This can be accomplished by creating an application-wide or shared singleton instance.
*/
NS_ASSUME_NONNULL_BEGIN
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1090) || TARGET_OS_WATCH
@interface AFURLSessionManager : NSObject <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate, NSSecureCoding, NSCopying>
/**
The managed session.
*/
@property (readonly, nonatomic, strong) NSURLSession *session;
/**
The operation queue on which delegate callbacks are run.
*/
@property (readonly, nonatomic, strong) NSOperationQueue *operationQueue;
/**
Responses sent from the server in data tasks created with `dataTaskWithRequest:success:failure:` and run using the `GET` / `POST` / et al. convenience methods are automatically validated and serialized by the response serializer. By default, this property is set to an instance of `AFJSONResponseSerializer`.
@warning `responseSerializer` must not be `nil`.
*/
@property (nonatomic, strong) id <AFURLResponseSerialization> responseSerializer;
///-------------------------------
/// @name Managing Security Policy
///-------------------------------
/**
The security policy used by created request operations to evaluate server trust for secure connections. `AFURLSessionManager` uses the `defaultPolicy` unless otherwise specified.
*/
@property (nonatomic, strong) AFSecurityPolicy *securityPolicy;
#if !TARGET_OS_WATCH
///--------------------------------------
/// @name Monitoring Network Reachability
///--------------------------------------
/**
The network reachability manager. `AFURLSessionManager` uses the `sharedManager` by default.
*/
@property (readwrite, nonatomic, strong) AFNetworkReachabilityManager *reachabilityManager;
#endif
///----------------------------
/// @name Getting Session Tasks
///----------------------------
/**
The data, upload, and download tasks currently run by the managed session.
*/
@property (readonly, nonatomic, strong) NSArray *tasks;
/**
The data tasks currently run by the managed session.
*/
@property (readonly, nonatomic, strong) NSArray *dataTasks;
/**
The upload tasks currently run by the managed session.
*/
@property (readonly, nonatomic, strong) NSArray *uploadTasks;
/**
The download tasks currently run by the managed session.
*/
@property (readonly, nonatomic, strong) NSArray *downloadTasks;
///-------------------------------
/// @name Managing Callback Queues
///-------------------------------
/**
The dispatch queue for `completionBlock`. If `NULL` (default), the main queue is used.
*/
#if OS_OBJECT_USE_OBJC
@property (nonatomic, strong, nullable) dispatch_queue_t completionQueue;
#else
@property (nonatomic, assign, nullable) dispatch_queue_t completionQueue;
#endif
/**
The dispatch group for `completionBlock`. If `NULL` (default), a private dispatch group is used.
*/
#if OS_OBJECT_USE_OBJC
@property (nonatomic, strong, nullable) dispatch_group_t completionGroup;
#else
@property (nonatomic, assign, nullable) dispatch_group_t completionGroup;
#endif
///---------------------------------
/// @name Working Around System Bugs
///---------------------------------
/**
Whether to attempt to retry creation of upload tasks for background sessions when initial call returns `nil`. `NO` by default.
@bug As of iOS 7.0, there is a bug where upload tasks created for background tasks are sometimes `nil`. As a workaround, if this property is `YES`, AFNetworking will follow Apple's recommendation to try creating the task again.
@see https://github.com/AFNetworking/AFNetworking/issues/1675
*/
@property (nonatomic, assign) BOOL attemptsToRecreateUploadTasksForBackgroundSessions;
///---------------------
/// @name Initialization
///---------------------
/**
Creates and returns a manager for a session created with the specified configuration. This is the designated initializer.
@param configuration The configuration used to create the managed session.
@return A manager for a newly-created session.
*/
- (instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER;
/**
Invalidates the managed session, optionally canceling pending tasks.
@param cancelPendingTasks Whether or not to cancel pending tasks.
*/
- (void)invalidateSessionCancelingTasks:(BOOL)cancelPendingTasks;
///-------------------------
/// @name Running Data Tasks
///-------------------------
/**
Creates an `NSURLSessionDataTask` with the specified request.
@param request The HTTP request for the request.
@param completionHandler A block object to be executed when the task finishes. This block has no return value and takes three arguments: the server response, the response object created by that serializer, and the error that occurred, if any.
*/
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
completionHandler:(nullable void (^)(NSURLResponse *response, id __nullable responseObject, NSError * __nullable error))completionHandler;
///---------------------------
/// @name Running Upload Tasks
///---------------------------
/**
Creates an `NSURLSessionUploadTask` with the specified request for a local file.
@param request The HTTP request for the request.
@param fileURL A URL to the local file to be uploaded.
@param progress A progress object monitoring the current upload progress.
@param completionHandler A block object to be executed when the task finishes. This block has no return value and takes three arguments: the server response, the response object created by that serializer, and the error that occurred, if any.
@see `attemptsToRecreateUploadTasksForBackgroundSessions`
*/
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
fromFile:(NSURL *)fileURL
progress:(NSProgress * __nullable __autoreleasing * __nullable)progress
completionHandler:(nullable void (^)(NSURLResponse *response, id __nullable responseObject, NSError * __nullable error))completionHandler;
/**
Creates an `NSURLSessionUploadTask` with the specified request for an HTTP body.
@param request The HTTP request for the request.
@param bodyData A data object containing the HTTP body to be uploaded.
@param progress A progress object monitoring the current upload progress.
@param completionHandler A block object to be executed when the task finishes. This block has no return value and takes three arguments: the server response, the response object created by that serializer, and the error that occurred, if any.
*/
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
fromData:(nullable NSData *)bodyData
progress:(NSProgress * __nullable __autoreleasing * __nullable)progress
completionHandler:(nullable void (^)(NSURLResponse *response, id __nullable responseObject, NSError * __nullable error))completionHandler;
/**
Creates an `NSURLSessionUploadTask` with the specified streaming request.
@param request The HTTP request for the request.
@param progress A progress object monitoring the current upload progress.
@param completionHandler A block object to be executed when the task finishes. This block has no return value and takes three arguments: the server response, the response object created by that serializer, and the error that occurred, if any.
*/
- (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request
progress:(NSProgress * __nullable __autoreleasing * __nullable)progress
completionHandler:(nullable void (^)(NSURLResponse *response, id __nullable responseObject, NSError * __nullable error))completionHandler;
///-----------------------------
/// @name Running Download Tasks
///-----------------------------
/**
Creates an `NSURLSessionDownloadTask` with the specified request.
@param request The HTTP request for the request.
@param progress A progress object monitoring the current download progress.
@param destination A block object to be executed in order to determine the destination of the downloaded file. This block takes two arguments, the target path & the server response, and returns the desired file URL of the resulting download. The temporary file used during the download will be automatically deleted after being moved to the returned URL.
@param completionHandler A block to be executed when a task finishes. This block has no return value and takes three arguments: the server response, the path of the downloaded file, and the error describing the network or parsing error that occurred, if any.
@warning If using a background `NSURLSessionConfiguration` on iOS, these blocks will be lost when the app is terminated. Background sessions may prefer to use `-setDownloadTaskDidFinishDownloadingBlock:` to specify the URL for saving the downloaded file, rather than the destination block of this method.
*/
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
progress:(NSProgress * __nullable __autoreleasing * __nullable)progress
destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * __nullable filePath, NSError * __nullable error))completionHandler;
/**
Creates an `NSURLSessionDownloadTask` with the specified resume data.
@param resumeData The data used to resume downloading.
@param progress A progress object monitoring the current download progress.
@param destination A block object to be executed in order to determine the destination of the downloaded file. This block takes two arguments, the target path & the server response, and returns the desired file URL of the resulting download. The temporary file used during the download will be automatically deleted after being moved to the returned URL.
@param completionHandler A block to be executed when a task finishes. This block has no return value and takes three arguments: the server response, the path of the downloaded file, and the error describing the network or parsing error that occurred, if any.
*/
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData
progress:(NSProgress * __nullable __autoreleasing * __nullable)progress
destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * __nullable filePath, NSError * __nullable error))completionHandler;
///---------------------------------
/// @name Getting Progress for Tasks
///---------------------------------
/**
Returns the upload progress of the specified task.
@param uploadTask The session upload task. Must not be `nil`.
@return An `NSProgress` object reporting the upload progress of a task, or `nil` if the progress is unavailable.
*/
- (nullable NSProgress *)uploadProgressForTask:(NSURLSessionUploadTask *)uploadTask;
/**
Returns the download progress of the specified task.
@param downloadTask The session download task. Must not be `nil`.
@return An `NSProgress` object reporting the download progress of a task, or `nil` if the progress is unavailable.
*/
- (nullable NSProgress *)downloadProgressForTask:(NSURLSessionDownloadTask *)downloadTask;
///-----------------------------------------
/// @name Setting Session Delegate Callbacks
///-----------------------------------------
/**
Sets a block to be executed when the managed session becomes invalid, as handled by the `NSURLSessionDelegate` method `URLSession:didBecomeInvalidWithError:`.
@param block A block object to be executed when the managed session becomes invalid. The block has no return value, and takes two arguments: the session, and the error related to the cause of invalidation.
*/
- (void)setSessionDidBecomeInvalidBlock:(nullable void (^)(NSURLSession *session, NSError *error))block;
/**
Sets a block to be executed when a connection level authentication challenge has occurred, as handled by the `NSURLSessionDelegate` method `URLSession:didReceiveChallenge:completionHandler:`.
@param block A block object to be executed when a connection level authentication challenge has occurred. The block returns the disposition of the authentication challenge, and takes three arguments: the session, the authentication challenge, and a pointer to the credential that should be used to resolve the challenge.
*/
- (void)setSessionDidReceiveAuthenticationChallengeBlock:(nullable NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * __nullable __autoreleasing * __nullable credential))block;
///--------------------------------------
/// @name Setting Task Delegate Callbacks
///--------------------------------------
/**
Sets a block to be executed when a task requires a new request body stream to send to the remote server, as handled by the `NSURLSessionTaskDelegate` method `URLSession:task:needNewBodyStream:`.
@param block A block object to be executed when a task requires a new request body stream.
*/
- (void)setTaskNeedNewBodyStreamBlock:(nullable NSInputStream * (^)(NSURLSession *session, NSURLSessionTask *task))block;
/**
Sets a block to be executed when an HTTP request is attempting to perform a redirection to a different URL, as handled by the `NSURLSessionTaskDelegate` method `URLSession:willPerformHTTPRedirection:newRequest:completionHandler:`.
@param block A block object to be executed when an HTTP request is attempting to perform a redirection to a different URL. The block returns the request to be made for the redirection, and takes four arguments: the session, the task, the redirection response, and the request corresponding to the redirection response.
*/
- (void)setTaskWillPerformHTTPRedirectionBlock:(nullable NSURLRequest * (^)(NSURLSession *session, NSURLSessionTask *task, NSURLResponse *response, NSURLRequest *request))block;
/**
Sets a block to be executed when a session task has received a request specific authentication challenge, as handled by the `NSURLSessionTaskDelegate` method `URLSession:task:didReceiveChallenge:completionHandler:`.
@param block A block object to be executed when a session task has received a request specific authentication challenge. The block returns the disposition of the authentication challenge, and takes four arguments: the session, the task, the authentication challenge, and a pointer to the credential that should be used to resolve the challenge.
*/
- (void)setTaskDidReceiveAuthenticationChallengeBlock:(nullable NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLSessionTask *task, NSURLAuthenticationChallenge *challenge, NSURLCredential * __nullable __autoreleasing * __nullable credential))block;
/**
Sets a block to be executed periodically to track upload progress, as handled by the `NSURLSessionTaskDelegate` method `URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:`.
@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 five arguments: the session, the task, 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)setTaskDidSendBodyDataBlock:(nullable void (^)(NSURLSession *session, NSURLSessionTask *task, int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend))block;
/**
Sets a block to be executed as the last message related to a specific task, as handled by the `NSURLSessionTaskDelegate` method `URLSession:task:didCompleteWithError:`.
@param block A block object to be executed when a session task is completed. The block has no return value, and takes three arguments: the session, the task, and any error that occurred in the process of executing the task.
*/
- (void)setTaskDidCompleteBlock:(nullable void (^)(NSURLSession *session, NSURLSessionTask *task, NSError * __nullable error))block;
///-------------------------------------------
/// @name Setting Data Task Delegate Callbacks
///-------------------------------------------
/**
Sets a block to be executed when a data task has received a response, as handled by the `NSURLSessionDataDelegate` method `URLSession:dataTask:didReceiveResponse:completionHandler:`.
@param block A block object to be executed when a data task has received a response. The block returns the disposition of the session response, and takes three arguments: the session, the data task, and the received response.
*/
- (void)setDataTaskDidReceiveResponseBlock:(nullable NSURLSessionResponseDisposition (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response))block;
/**
Sets a block to be executed when a data task has become a download task, as handled by the `NSURLSessionDataDelegate` method `URLSession:dataTask:didBecomeDownloadTask:`.
@param block A block object to be executed when a data task has become a download task. The block has no return value, and takes three arguments: the session, the data task, and the download task it has become.
*/
- (void)setDataTaskDidBecomeDownloadTaskBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask))block;
/**
Sets a block to be executed when a data task receives data, as handled by the `NSURLSessionDataDelegate` method `URLSession:dataTask:didReceiveData:`.
@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 session, the data task, and the data received. This block may be called multiple times, and will execute on the session manager operation queue.
*/
- (void)setDataTaskDidReceiveDataBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data))block;
/**
Sets a block to be executed to determine the caching behavior of a data task, as handled by the `NSURLSessionDataDelegate` method `URLSession:dataTask:willCacheResponse:completionHandler:`.
@param block A block object to be executed to determine the caching behavior of a data task. The block returns the response to cache, and takes three arguments: the session, the data task, and the proposed cached URL response.
*/
- (void)setDataTaskWillCacheResponseBlock:(nullable NSCachedURLResponse * (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSCachedURLResponse *proposedResponse))block;
/**
Sets a block to be executed once all messages enqueued for a session have been delivered, as handled by the `NSURLSessionDataDelegate` method `URLSessionDidFinishEventsForBackgroundURLSession:`.
@param block A block object to be executed once all messages enqueued for a session have been delivered. The block has no return value and takes a single argument: the session.
*/
- (void)setDidFinishEventsForBackgroundURLSessionBlock:(nullable void (^)(NSURLSession *session))block;
///-----------------------------------------------
/// @name Setting Download Task Delegate Callbacks
///-----------------------------------------------
/**
Sets a block to be executed when a download task has completed a download, as handled by the `NSURLSessionDownloadDelegate` method `URLSession:downloadTask:didFinishDownloadingToURL:`.
@param block A block object to be executed when a download task has completed. The block returns the URL the download should be moved to, and takes three arguments: the session, the download task, and the temporary location of the downloaded file. If the file manager encounters an error while attempting to move the temporary file to the destination, an `AFURLSessionDownloadTaskDidFailToMoveFileNotification` will be posted, with the download task as its object, and the user info of the error.
*/
- (void)setDownloadTaskDidFinishDownloadingBlock:(nullable NSURL * __nullable (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location))block;
/**
Sets a block to be executed periodically to track download progress, as handled by the `NSURLSessionDownloadDelegate` method `URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesWritten:totalBytesExpectedToWrite:`.
@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 five arguments: the session, the download task, 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 session manager operation queue.
*/
- (void)setDownloadTaskDidWriteDataBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite))block;
/**
Sets a block to be executed when a download task has been resumed, as handled by the `NSURLSessionDownloadDelegate` method `URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:`.
@param block A block object to be executed when a download task has been resumed. The block has no return value and takes four arguments: the session, the download task, the file offset of the resumed download, and the total number of bytes expected to be downloaded.
*/
- (void)setDownloadTaskDidResumeBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t fileOffset, int64_t expectedTotalBytes))block;
@end
#endif
///--------------------
/// @name Notifications
///--------------------
/**
Posted when a task begins executing.
@deprecated Use `AFNetworkingTaskDidResumeNotification` instead.
*/
FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidStartNotification DEPRECATED_ATTRIBUTE;
/**
Posted when a task resumes.
*/
FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidResumeNotification;
/**
Posted when a task finishes executing. Includes a userInfo dictionary with additional information about the task.
@deprecated Use `AFNetworkingTaskDidCompleteNotification` instead.
*/
FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidFinishNotification DEPRECATED_ATTRIBUTE;
/**
Posted when a task finishes executing. Includes a userInfo dictionary with additional information about the task.
*/
FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidCompleteNotification;
/**
Posted when a task suspends its execution.
*/
FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidSuspendNotification;
/**
Posted when a session is invalidated.
*/
FOUNDATION_EXPORT NSString * const AFURLSessionDidInvalidateNotification;
/**
Posted when a session download task encountered an error when moving the temporary download file to a specified destination.
*/
FOUNDATION_EXPORT NSString * const AFURLSessionDownloadTaskDidFailToMoveFileNotification;
/**
The raw response data of the task. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if response data exists for the task.
@deprecated Use `AFNetworkingTaskDidCompleteResponseDataKey` instead.
*/
FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidFinishResponseDataKey DEPRECATED_ATTRIBUTE;
/**
The raw response data of the task. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if response data exists for the task.
*/
FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidCompleteResponseDataKey;
/**
The serialized response object of the task. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if the response was serialized.
@deprecated Use `AFNetworkingTaskDidCompleteSerializedResponseKey` instead.
*/
FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidFinishSerializedResponseKey DEPRECATED_ATTRIBUTE;
/**
The serialized response object of the task. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if the response was serialized.
*/
FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidCompleteSerializedResponseKey;
/**
The response serializer used to serialize the response. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if the task has an associated response serializer.
@deprecated Use `AFNetworkingTaskDidCompleteResponseSerializerKey` instead.
*/
FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidFinishResponseSerializerKey DEPRECATED_ATTRIBUTE;
/**
The response serializer used to serialize the response. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if the task has an associated response serializer.
*/
FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidCompleteResponseSerializerKey;
/**
The file path associated with the download task. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if an the response data has been stored directly to disk.
@deprecated Use `AFNetworkingTaskDidCompleteAssetPathKey` instead.
*/
FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidFinishAssetPathKey DEPRECATED_ATTRIBUTE;
/**
The file path associated with the download task. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if an the response data has been stored directly to disk.
*/
FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidCompleteAssetPathKey;
/**
Any error associated with the task, or the serialization of the response. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if an error exists.
@deprecated Use `AFNetworkingTaskDidCompleteErrorKey` instead.
*/
FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidFinishErrorKey DEPRECATED_ATTRIBUTE;
/**
Any error associated with the task, or the serialization of the response. Included in the userInfo dictionary of the `AFNetworkingTaskDidFinishNotification` if an error exists.
*/
FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidCompleteErrorKey;
NS_ASSUME_NONNULL_END

File diff suppressed because it is too large Load Diff

19
Pods/AFNetworking/LICENSE generated Normal file
View File

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

394
Pods/AFNetworking/README.md generated Normal file
View File

@ -0,0 +1,394 @@
<p align="center" >
<img src="https://raw.github.com/AFNetworking/AFNetworking/assets/afnetworking-logo.png" alt="AFNetworking" title="AFNetworking">
</p>
[![Build Status](https://travis-ci.org/AFNetworking/AFNetworking.svg)](https://travis-ci.org/AFNetworking/AFNetworking)
AFNetworking is a delightful networking library for iOS and Mac OS X. It's built on top of the [Foundation URL Loading System](http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html), extending the powerful high-level networking abstractions built into Cocoa. It has a modular architecture with well-designed, feature-rich APIs that are a joy to use.
Perhaps the most important feature of all, however, is the amazing community of developers who use and contribute to AFNetworking every day. AFNetworking powers some of the most popular and critically-acclaimed apps on the iPhone, iPad, and Mac.
Choose AFNetworking for your next project, or migrate over your existing projects—you'll be happy you did!
## How To Get Started
- [Download AFNetworking](https://github.com/AFNetworking/AFNetworking/archive/master.zip) 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 on the Wiki](https://github.com/AFNetworking/AFNetworking/wiki)
- Check out the [documentation](http://cocoadocs.org/docsets/AFNetworking/) for a comprehensive look at all of the APIs available in AFNetworking
- Read the [AFNetworking 2.0 Migration Guide](https://github.com/AFNetworking/AFNetworking/wiki/AFNetworking-2.0-Migration-Guide) for an overview of the architectural changes from 1.0.
## Communication
- If you **need help**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/afnetworking). (Tag 'afnetworking')
- If you'd like to **ask a general question**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/afnetworking).
- If you **found a bug**, _and can provide steps to reliably reproduce it_, open an issue.
- If you **have a feature request**, open an issue.
- If you **want to contribute**, submit a pull request.
### Installation with CocoaPods
[CocoaPods](https://cocoapods.org/) is a dependency manager for Objective-C, which automates and simplifies the process of using 3rd-party libraries like AFNetworking in your projects. See the ["Getting Started" guide for more information](https://github.com/AFNetworking/AFNetworking/wiki/Getting-Started-with-AFNetworking).
#### Podfile
```ruby
platform :ios, '7.0'
pod "AFNetworking", "~> 2.0"
```
## Requirements
| AFNetworking Version | Minimum iOS Target | Minimum OS X Target | Notes |
|:--------------------:|:---------------------------:|:----------------------------:|:-------------------------------------------------------------------------:|
| 2.x | iOS 6 | OS X 10.8 | Xcode 5 is required. `NSURLSession` subspec requires iOS 7 or OS X 10.9. |
| [1.x](https://github.com/AFNetworking/AFNetworking/tree/1.x) | iOS 5 | Mac OS X 10.7 | |
| [0.10.x](https://github.com/AFNetworking/AFNetworking/tree/0.10.x) | iOS 4 | Mac OS X 10.6 | |
(OS X projects must support [64-bit with modern Cocoa runtime](https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtVersionsPlatforms.html)).
> Programming in Swift? Try [Alamofire](https://github.com/Alamofire/Alamofire) for a more conventional set of APIs.
## Architecture
### NSURLConnection
- `AFURLConnectionOperation`
- `AFHTTPRequestOperation`
- `AFHTTPRequestOperationManager`
### NSURLSession _(iOS 7 / Mac OS X 10.9)_
- `AFURLSessionManager`
- `AFHTTPSessionManager`
### Serialization
* `<AFURLRequestSerialization>`
- `AFHTTPRequestSerializer`
- `AFJSONRequestSerializer`
- `AFPropertyListRequestSerializer`
* `<AFURLResponseSerialization>`
- `AFHTTPResponseSerializer`
- `AFJSONResponseSerializer`
- `AFXMLParserResponseSerializer`
- `AFXMLDocumentResponseSerializer` _(Mac OS X)_
- `AFPropertyListResponseSerializer`
- `AFImageResponseSerializer`
- `AFCompoundResponseSerializer`
### Additional Functionality
- `AFSecurityPolicy`
- `AFNetworkReachabilityManager`
## Usage
### HTTP Request Operation Manager
`AFHTTPRequestOperationManager` encapsulates the common patterns of communicating with a web application over HTTP, including request creation, response serialization, network reachability monitoring, and security, as well as request operation management.
#### `GET` Request
```objective-c
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:@"http://example.com/resources.json" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"JSON: %@", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"Error: %@", error);
}];
```
#### `POST` URL-Form-Encoded Request
```objective-c
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSDictionary *parameters = @{@"foo": @"bar"};
[manager POST:@"http://example.com/resources.json" parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"JSON: %@", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"Error: %@", error);
}];
```
#### `POST` Multi-Part Request
```objective-c
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
NSDictionary *parameters = @{@"foo": @"bar"};
NSURL *filePath = [NSURL fileURLWithPath:@"file://path/to/image.png"];
[manager POST:@"http://example.com/resources.json" parameters:parameters constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileURL:filePath name:@"image" error:nil];
} success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"Success: %@", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"Error: %@", error);
}];
```
---
### AFURLSessionManager
`AFURLSessionManager` creates and manages an `NSURLSession` object based on a specified `NSURLSessionConfiguration` object, which conforms to `<NSURLSessionTaskDelegate>`, `<NSURLSessionDataDelegate>`, `<NSURLSessionDownloadDelegate>`, and `<NSURLSessionDelegate>`.
#### Creating a Download Task
```objective-c
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:@"http://example.com/download.zip"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
return [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
NSLog(@"File downloaded to: %@", filePath);
}];
[downloadTask resume];
```
#### Creating an Upload Task
```objective-c
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:@"http://example.com/upload"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURL *filePath = [NSURL fileURLWithPath:@"file://path/to/image.png"];
NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithRequest:request fromFile:filePath progress:nil completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
NSLog(@"Error: %@", error);
} else {
NSLog(@"Success: %@ %@", response, responseObject);
}
}];
[uploadTask resume];
```
#### Creating an Upload Task for a Multi-Part Request, with Progress
```objective-c
NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:@"http://example.com/upload" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileURL:[NSURL fileURLWithPath:@"file://path/to/image.jpg"] name:@"file" fileName:@"filename.jpg" mimeType:@"image/jpeg" error:nil];
} error:nil];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSProgress *progress = nil;
NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithStreamedRequest:request progress:&progress completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
NSLog(@"Error: %@", error);
} else {
NSLog(@"%@ %@", response, responseObject);
}
}];
[uploadTask resume];
```
#### Creating a Data Task
```objective-c
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
NSURL *URL = [NSURL URLWithString:@"http://example.com/upload"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
NSLog(@"Error: %@", error);
} else {
NSLog(@"%@ %@", response, responseObject);
}
}];
[dataTask resume];
```
---
### Request Serialization
Request serializers create requests from URL strings, encoding parameters as either a query string or HTTP body.
```objective-c
NSString *URLString = @"http://example.com";
NSDictionary *parameters = @{@"foo": @"bar", @"baz": @[@1, @2, @3]};
```
#### Query String Parameter Encoding
```objective-c
[[AFHTTPRequestSerializer serializer] requestWithMethod:@"GET" URLString:URLString parameters:parameters error:nil];
```
GET http://example.com?foo=bar&baz[]=1&baz[]=2&baz[]=3
#### URL Form Parameter Encoding
```objective-c
[[AFHTTPRequestSerializer serializer] requestWithMethod:@"POST" URLString:URLString parameters:parameters];
```
POST http://example.com/
Content-Type: application/x-www-form-urlencoded
foo=bar&baz[]=1&baz[]=2&baz[]=3
#### JSON Parameter Encoding
```objective-c
[[AFJSONRequestSerializer serializer] requestWithMethod:@"POST" URLString:URLString parameters:parameters];
```
POST http://example.com/
Content-Type: application/json
{"foo": "bar", "baz": [1,2,3]}
---
### Network Reachability Manager
`AFNetworkReachabilityManager` monitors the reachability of domains, and addresses for both WWAN and WiFi network interfaces.
* Do not use Reachability to determine if the original request should be sent.
* You should try to send it.
* You can use Reachability to determine when a request should be automatically retried.
* Although it may still fail, a Reachability notification that the connectivity is available is a good time to retry something.
* Network reachability is a useful tool for determining why a request might have failed.
* After a network request has failed, telling the user they're offline is better than giving them a more technical but accurate error, such as "request timed out."
See also [WWDC 2012 session 706, "Networking Best Practices."](https://developer.apple.com/videos/play/wwdc2012-706/).
#### Shared Network Reachability
```objective-c
[[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
NSLog(@"Reachability: %@", AFStringFromNetworkReachabilityStatus(status));
}];
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
```
#### HTTP Manager Reachability
```objective-c
NSURL *baseURL = [NSURL URLWithString:@"http://example.com/"];
AFHTTPRequestOperationManager *manager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:baseURL];
NSOperationQueue *operationQueue = manager.operationQueue;
[manager.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
switch (status) {
case AFNetworkReachabilityStatusReachableViaWWAN:
case AFNetworkReachabilityStatusReachableViaWiFi:
[operationQueue setSuspended:NO];
break;
case AFNetworkReachabilityStatusNotReachable:
default:
[operationQueue setSuspended:YES];
break;
}
}];
[manager.reachabilityManager startMonitoring];
```
---
### Security Policy
`AFSecurityPolicy` evaluates server trust against pinned X.509 certificates and public keys over secure connections.
Adding pinned SSL certificates to your app helps prevent man-in-the-middle attacks and other vulnerabilities. Applications dealing with sensitive customer data or financial information are strongly encouraged to route all communication over an HTTPS connection with SSL pinning configured and enabled.
#### Allowing Invalid SSL Certificates
```objective-c
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.securityPolicy.allowInvalidCertificates = YES; // not recommended for production
```
---
### AFHTTPRequestOperation
`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.
Although `AFHTTPRequestOperationManager` is usually the best way to go about making requests, `AFHTTPRequestOperation` can be used by itself.
#### `GET` with `AFHTTPRequestOperation`
```objective-c
NSURL *URL = [NSURL URLWithString:@"http://example.com/resources/123.json"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
AFHTTPRequestOperation *op = [[AFHTTPRequestOperation alloc] initWithRequest:request];
op.responseSerializer = [AFJSONResponseSerializer serializer];
[op setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(@"JSON: %@", responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(@"Error: %@", error);
}];
[[NSOperationQueue mainQueue] addOperation:op];
```
#### Batch of Operations
```objective-c
NSMutableArray *mutableOperations = [NSMutableArray array];
for (NSURL *fileURL in filesToUpload) {
NSURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:@"http://example.com/upload" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileURL:fileURL name:@"images[]" error:nil];
}];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[mutableOperations addObject:operation];
}
NSArray *operations = [AFURLConnectionOperation batchOfRequestOperations:@[...] progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations) {
NSLog(@"%lu of %lu complete", numberOfFinishedOperations, totalNumberOfOperations);
} completionBlock:^(NSArray *operations) {
NSLog(@"All operations in batch complete");
}];
[[NSOperationQueue mainQueue] addOperations:operations waitUntilFinished:NO];
```
## Unit Tests
AFNetworking includes a suite of unit tests within the Tests subdirectory. In order to run the unit tests, you must install the testing dependencies via [CocoaPods](https://cocoapods.org/):
$ cd Tests
$ pod install
Once testing dependencies are installed, you can execute the test suite via the 'iOS Tests' and 'OS X Tests' schemes within Xcode.
### Running Tests from the Command Line
Tests can also be run from the command line or within a continuous integration environment. The [`xcpretty`](https://github.com/supermarin/xcpretty) utility needs to be installed before running the tests from the command line:
$ gem install xcpretty
Once `xcpretty` is installed, you can execute the suite via `rake test`.
## Credits
AFNetworking is owned and maintained by the [Alamofire Software Foundation](http://alamofire.org).
AFNetworking was originally created by [Scott Raymond](https://github.com/sco/) and [Mattt Thompson](https://github.com/mattt/) in the development of [Gowalla for iPhone](https://en.wikipedia.org/wiki/Gowalla).
AFNetworking's logo was designed by [Alan Defibaugh](http://www.alandefibaugh.com/).
And most of all, thanks to AFNetworking's [growing list of contributors](https://github.com/AFNetworking/AFNetworking/graphs/contributors).
### Security Disclosure
If you believe you have identified a security vulnerability with AFNetworking, you should report it as soon as possible via email to security@alamofire.org. Please do not post it to a public issue tracker.
## License
AFNetworking is released under the MIT license. See LICENSE for details.

View File

@ -0,0 +1,80 @@
// AFNetworkActivityIndicatorManager.h
// Copyright (c) 20112015 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
#import <Availability.h>
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
/**
`AFNetworkActivityIndicatorManager` manages the state of the network activity indicator in the status bar. When enabled, it will listen for notifications indicating that a network request operation has started or finished, and start or stop animating the indicator accordingly. The number of active requests is incremented and decremented much like a stack or a semaphore, and the activity indicator will animate so long as that number is greater than zero.
You should enable the shared instance of `AFNetworkActivityIndicatorManager` when your application finishes launching. In `AppDelegate application:didFinishLaunchingWithOptions:` you can do so with the following code:
[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
By setting `enabled` to `YES` for `sharedManager`, the network activity indicator will show and hide automatically as requests start and finish. You should not ever need to call `incrementActivityCount` or `decrementActivityCount` yourself.
See the Apple Human Interface Guidelines section about the Network Activity Indicator for more information:
http://developer.apple.com/library/iOS/#documentation/UserExperience/Conceptual/MobileHIG/UIElementGuidelines/UIElementGuidelines.html#//apple_ref/doc/uid/TP40006556-CH13-SW44
*/
NS_EXTENSION_UNAVAILABLE_IOS("Use view controller based solutions where appropriate instead.")
@interface AFNetworkActivityIndicatorManager : NSObject
/**
A Boolean value indicating whether the manager is enabled.
If YES, the manager will change status bar network activity indicator according to network operation notifications it receives. The default value is NO.
*/
@property (nonatomic, assign, getter = isEnabled) BOOL enabled;
/**
A Boolean value indicating whether the network activity indicator is currently displayed in the status bar.
*/
@property (readonly, nonatomic, assign) BOOL isNetworkActivityIndicatorVisible;
/**
Returns the shared network activity indicator manager object for the system.
@return The systemwide network activity indicator manager.
*/
+ (instancetype)sharedManager;
/**
Increments the number of active network requests. If this number was zero before incrementing, this will start animating the status bar network activity indicator.
*/
- (void)incrementActivityCount;
/**
Decrements the number of active network requests. If this number becomes zero after decrementing, this will stop animating the status bar network activity indicator.
*/
- (void)decrementActivityCount;
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,170 @@
// AFNetworkActivityIndicatorManager.m
// Copyright (c) 20112015 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "AFNetworkActivityIndicatorManager.h"
#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED)
#import "AFHTTPRequestOperation.h"
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000
#import "AFURLSessionManager.h"
#endif
static NSTimeInterval const kAFNetworkActivityIndicatorInvisibilityDelay = 0.17;
static NSURLRequest * AFNetworkRequestFromNotification(NSNotification *notification) {
if ([[notification object] isKindOfClass:[AFURLConnectionOperation class]]) {
return [(AFURLConnectionOperation *)[notification object] request];
}
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000
if ([[notification object] respondsToSelector:@selector(originalRequest)]) {
return [(NSURLSessionTask *)[notification object] originalRequest];
}
#endif
return nil;
}
@interface AFNetworkActivityIndicatorManager ()
@property (readwrite, nonatomic, assign) NSInteger activityCount;
@property (readwrite, nonatomic, strong) NSTimer *activityIndicatorVisibilityTimer;
@property (readonly, nonatomic, getter = isNetworkActivityIndicatorVisible) BOOL networkActivityIndicatorVisible;
- (void)updateNetworkActivityIndicatorVisibility;
- (void)updateNetworkActivityIndicatorVisibilityDelayed;
@end
@implementation AFNetworkActivityIndicatorManager
@dynamic networkActivityIndicatorVisible;
+ (instancetype)sharedManager {
static AFNetworkActivityIndicatorManager *_sharedManager = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedManager = [[self alloc] init];
});
return _sharedManager;
}
+ (NSSet *)keyPathsForValuesAffectingIsNetworkActivityIndicatorVisible {
return [NSSet setWithObject:@"activityCount"];
}
- (id)init {
self = [super init];
if (!self) {
return nil;
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidStart:) name:AFNetworkingOperationDidStartNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingOperationDidFinishNotification object:nil];
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidStart:) name:AFNetworkingTaskDidResumeNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidSuspendNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidCompleteNotification object:nil];
#endif
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_activityIndicatorVisibilityTimer invalidate];
}
- (void)updateNetworkActivityIndicatorVisibilityDelayed {
if (self.enabled) {
// Delay hiding of activity indicator for a short interval, to avoid flickering
if (![self isNetworkActivityIndicatorVisible]) {
[self.activityIndicatorVisibilityTimer invalidate];
self.activityIndicatorVisibilityTimer = [NSTimer timerWithTimeInterval:kAFNetworkActivityIndicatorInvisibilityDelay target:self selector:@selector(updateNetworkActivityIndicatorVisibility) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:self.activityIndicatorVisibilityTimer forMode:NSRunLoopCommonModes];
} else {
[self performSelectorOnMainThread:@selector(updateNetworkActivityIndicatorVisibility) withObject:nil waitUntilDone:NO modes:@[NSRunLoopCommonModes]];
}
}
}
- (BOOL)isNetworkActivityIndicatorVisible {
return self.activityCount > 0;
}
- (void)updateNetworkActivityIndicatorVisibility {
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:[self isNetworkActivityIndicatorVisible]];
}
- (void)setActivityCount:(NSInteger)activityCount {
@synchronized(self) {
_activityCount = activityCount;
}
dispatch_async(dispatch_get_main_queue(), ^{
[self updateNetworkActivityIndicatorVisibilityDelayed];
});
}
- (void)incrementActivityCount {
[self willChangeValueForKey:@"activityCount"];
@synchronized(self) {
_activityCount++;
}
[self didChangeValueForKey:@"activityCount"];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateNetworkActivityIndicatorVisibilityDelayed];
});
}
- (void)decrementActivityCount {
[self willChangeValueForKey:@"activityCount"];
@synchronized(self) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
_activityCount = MAX(_activityCount - 1, 0);
#pragma clang diagnostic pop
}
[self didChangeValueForKey:@"activityCount"];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateNetworkActivityIndicatorVisibilityDelayed];
});
}
- (void)networkRequestDidStart:(NSNotification *)notification {
if ([AFNetworkRequestFromNotification(notification) URL]) {
[self incrementActivityCount];
}
}
- (void)networkRequestDidFinish:(NSNotification *)notification {
if ([AFNetworkRequestFromNotification(notification) URL]) {
[self decrementActivityCount];
}
}
@end
#endif

View File

@ -0,0 +1,63 @@
// UIActivityIndicatorView+AFNetworking.h
// Copyright (c) 20112015 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
#import <Availability.h>
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
#import <UIKit/UIKit.h>
@class AFURLConnectionOperation;
/**
This category adds methods to the UIKit framework's `UIActivityIndicatorView` class. The methods in this category provide support for automatically starting and stopping animation depending on the loading state of a request operation or session task.
*/
@interface UIActivityIndicatorView (AFNetworking)
///----------------------------------
/// @name Animating for Session Tasks
///----------------------------------
/**
Binds the animating state to the state of the specified task.
@param task The task. If `nil`, automatic updating from any previously specified operation will be disabled.
*/
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
- (void)setAnimatingWithStateOfTask:(nullable NSURLSessionTask *)task;
#endif
///---------------------------------------
/// @name Animating for Request Operations
///---------------------------------------
/**
Binds the animating state to the execution state of the specified operation.
@param operation The operation. If `nil`, automatic updating from any previously specified operation will be disabled.
*/
- (void)setAnimatingWithStateOfOperation:(nullable AFURLConnectionOperation *)operation;
@end
#endif

View File

@ -0,0 +1,171 @@
// UIActivityIndicatorView+AFNetworking.m
// Copyright (c) 20112015 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "UIActivityIndicatorView+AFNetworking.h"
#import <objc/runtime.h>
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
#import "AFHTTPRequestOperation.h"
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
#import "AFURLSessionManager.h"
#endif
@interface AFActivityIndicatorViewNotificationObserver : NSObject
@property (readonly, nonatomic, weak) UIActivityIndicatorView *activityIndicatorView;
- (instancetype)initWithActivityIndicatorView:(UIActivityIndicatorView *)activityIndicatorView;
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
- (void)setAnimatingWithStateOfTask:(NSURLSessionTask *)task;
#endif
- (void)setAnimatingWithStateOfOperation:(AFURLConnectionOperation *)operation;
@end
@implementation UIActivityIndicatorView (AFNetworking)
- (AFActivityIndicatorViewNotificationObserver *)af_notificationObserver {
AFActivityIndicatorViewNotificationObserver *notificationObserver = objc_getAssociatedObject(self, @selector(af_notificationObserver));
if (notificationObserver == nil) {
notificationObserver = [[AFActivityIndicatorViewNotificationObserver alloc] initWithActivityIndicatorView:self];
objc_setAssociatedObject(self, @selector(af_notificationObserver), notificationObserver, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return notificationObserver;
}
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
- (void)setAnimatingWithStateOfTask:(NSURLSessionTask *)task {
[[self af_notificationObserver] setAnimatingWithStateOfTask:task];
}
#endif
- (void)setAnimatingWithStateOfOperation:(AFURLConnectionOperation *)operation {
[[self af_notificationObserver] setAnimatingWithStateOfOperation:operation];
}
@end
@implementation AFActivityIndicatorViewNotificationObserver
- (instancetype)initWithActivityIndicatorView:(UIActivityIndicatorView *)activityIndicatorView
{
self = [super init];
if (self) {
_activityIndicatorView = activityIndicatorView;
}
return self;
}
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
- (void)setAnimatingWithStateOfTask:(NSURLSessionTask *)task {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter removeObserver:self name:AFNetworkingTaskDidResumeNotification object:nil];
[notificationCenter removeObserver:self name:AFNetworkingTaskDidSuspendNotification object:nil];
[notificationCenter removeObserver:self name:AFNetworkingTaskDidCompleteNotification object:nil];
if (task) {
if (task.state != NSURLSessionTaskStateCompleted) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreceiver-is-weak"
#pragma clang diagnostic ignored "-Warc-repeated-use-of-weak"
if (task.state == NSURLSessionTaskStateRunning) {
[self.activityIndicatorView startAnimating];
} else {
[self.activityIndicatorView stopAnimating];
}
#pragma clang diagnostic pop
[notificationCenter addObserver:self selector:@selector(af_startAnimating) name:AFNetworkingTaskDidResumeNotification object:task];
[notificationCenter addObserver:self selector:@selector(af_stopAnimating) name:AFNetworkingTaskDidCompleteNotification object:task];
[notificationCenter addObserver:self selector:@selector(af_stopAnimating) name:AFNetworkingTaskDidSuspendNotification object:task];
}
}
}
#endif
#pragma mark -
- (void)setAnimatingWithStateOfOperation:(AFURLConnectionOperation *)operation {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter removeObserver:self name:AFNetworkingOperationDidStartNotification object:nil];
[notificationCenter removeObserver:self name:AFNetworkingOperationDidFinishNotification object:nil];
if (operation) {
if (![operation isFinished]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreceiver-is-weak"
#pragma clang diagnostic ignored "-Warc-repeated-use-of-weak"
if ([operation isExecuting]) {
[self.activityIndicatorView startAnimating];
} else {
[self.activityIndicatorView stopAnimating];
}
#pragma clang diagnostic pop
[notificationCenter addObserver:self selector:@selector(af_startAnimating) name:AFNetworkingOperationDidStartNotification object:operation];
[notificationCenter addObserver:self selector:@selector(af_stopAnimating) name:AFNetworkingOperationDidFinishNotification object:operation];
}
}
}
#pragma mark -
- (void)af_startAnimating {
dispatch_async(dispatch_get_main_queue(), ^{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreceiver-is-weak"
[self.activityIndicatorView startAnimating];
#pragma clang diagnostic pop
});
}
- (void)af_stopAnimating {
dispatch_async(dispatch_get_main_queue(), ^{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreceiver-is-weak"
[self.activityIndicatorView stopAnimating];
#pragma clang diagnostic pop
});
}
#pragma mark -
- (void)dealloc {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
[notificationCenter removeObserver:self name:AFNetworkingTaskDidCompleteNotification object:nil];
[notificationCenter removeObserver:self name:AFNetworkingTaskDidResumeNotification object:nil];
[notificationCenter removeObserver:self name:AFNetworkingTaskDidSuspendNotification object:nil];
#endif
[notificationCenter removeObserver:self name:AFNetworkingOperationDidStartNotification object:nil];
[notificationCenter removeObserver:self name:AFNetworkingOperationDidFinishNotification object:nil];
}
@end
#endif

View File

@ -0,0 +1,99 @@
// UIAlertView+AFNetworking.h
// Copyright (c) 20112015 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
#import <Availability.h>
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@class AFURLConnectionOperation;
/**
This category adds methods to the UIKit framework's `UIAlertView` class. The methods in this category provide support for automatically showing an alert if a session task or request operation finishes with an error. Alert title and message are filled from the corresponding `localizedDescription` & `localizedRecoverySuggestion` or `localizedFailureReason` of the error.
*/
@interface UIAlertView (AFNetworking)
///-------------------------------------
/// @name Showing Alert for Session Task
///-------------------------------------
/**
Shows an alert view with the error of the specified session task, if any.
@param task The session task.
@param delegate The alert view delegate.
*/
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
+ (void)showAlertViewForTaskWithErrorOnCompletion:(NSURLSessionTask *)task
delegate:(nullable id)delegate NS_EXTENSION_UNAVAILABLE_IOS("Not available in app extensions.");
#endif
/**
Shows an alert view with the error of the specified session task, if any, with a custom cancel button title and other button titles.
@param task The session task.
@param delegate The alert view delegate.
@param cancelButtonTitle The title of the cancel button or nil if there is no cancel button. Using this argument is equivalent to setting the cancel button index to the value returned by invoking addButtonWithTitle: specifying this title.
@param otherButtonTitles The title of another button. Using this argument is equivalent to invoking addButtonWithTitle: with this title to add more buttons. Too many buttons can cause the alert view to scroll. For guidelines on the best ways to use an alert in an app, see "Temporary Views". Titles of additional buttons to add to the receiver, terminated with `nil`.
*/
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
+ (void)showAlertViewForTaskWithErrorOnCompletion:(NSURLSessionTask *)task
delegate:(nullable id)delegate
cancelButtonTitle:(nullable NSString *)cancelButtonTitle
otherButtonTitles:(nullable NSString *)otherButtonTitles, ... NS_REQUIRES_NIL_TERMINATION NS_EXTENSION_UNAVAILABLE_IOS("Not available in app extensions.");
#endif
///------------------------------------------
/// @name Showing Alert for Request Operation
///------------------------------------------
/**
Shows an alert view with the error of the specified request operation, if any.
@param operation The request operation.
@param delegate The alert view delegate.
*/
+ (void)showAlertViewForRequestOperationWithErrorOnCompletion:(AFURLConnectionOperation *)operation
delegate:(nullable id)delegate NS_EXTENSION_UNAVAILABLE_IOS("Not available in app extensions.");
/**
Shows an alert view with the error of the specified request operation, if any, with a custom cancel button title and other button titles.
@param operation The request operation.
@param delegate The alert view delegate.
@param cancelButtonTitle The title of the cancel button or nil if there is no cancel button. Using this argument is equivalent to setting the cancel button index to the value returned by invoking addButtonWithTitle: specifying this title.
@param otherButtonTitles The title of another button. Using this argument is equivalent to invoking addButtonWithTitle: with this title to add more buttons. Too many buttons can cause the alert view to scroll. For guidelines on the best ways to use an alert in an app, see "Temporary Views". Titles of additional buttons to add to the receiver, terminated with `nil`.
*/
+ (void)showAlertViewForRequestOperationWithErrorOnCompletion:(AFURLConnectionOperation *)operation
delegate:(nullable id)delegate
cancelButtonTitle:(nullable NSString *)cancelButtonTitle
otherButtonTitles:(nullable NSString *)otherButtonTitles, ... NS_REQUIRES_NIL_TERMINATION NS_EXTENSION_UNAVAILABLE_IOS("Not available in app extensions.");
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,141 @@
// UIAlertView+AFNetworking.m
// Copyright (c) 20112015 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "UIAlertView+AFNetworking.h"
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
#import "AFURLConnectionOperation.h"
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
#import "AFURLSessionManager.h"
#endif
static void AFGetAlertViewTitleAndMessageFromError(NSError *error, NSString * __autoreleasing *title, NSString * __autoreleasing *message) {
if (error.localizedDescription && (error.localizedRecoverySuggestion || error.localizedFailureReason)) {
*title = error.localizedDescription;
if (error.localizedRecoverySuggestion) {
*message = error.localizedRecoverySuggestion;
} else {
*message = error.localizedFailureReason;
}
} else if (error.localizedDescription) {
*title = NSLocalizedStringFromTable(@"Error", @"AFNetworking", @"Fallback Error Description");
*message = error.localizedDescription;
} else {
*title = NSLocalizedStringFromTable(@"Error", @"AFNetworking", @"Fallback Error Description");
*message = [NSString stringWithFormat:NSLocalizedStringFromTable(@"%@ Error: %ld", @"AFNetworking", @"Fallback Error Failure Reason Format"), error.domain, (long)error.code];
}
}
@implementation UIAlertView (AFNetworking)
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
+ (void)showAlertViewForTaskWithErrorOnCompletion:(NSURLSessionTask *)task
delegate:(id)delegate
{
[self showAlertViewForTaskWithErrorOnCompletion:task delegate:delegate cancelButtonTitle:NSLocalizedStringFromTable(@"Dismiss", @"AFNetworking", @"UIAlertView Cancel Button Title") otherButtonTitles:nil, nil];
}
+ (void)showAlertViewForTaskWithErrorOnCompletion:(NSURLSessionTask *)task
delegate:(id)delegate
cancelButtonTitle:(NSString *)cancelButtonTitle
otherButtonTitles:(NSString *)otherButtonTitles, ... NS_REQUIRES_NIL_TERMINATION
{
NSMutableArray *mutableOtherTitles = [NSMutableArray array];
va_list otherButtonTitleList;
va_start(otherButtonTitleList, otherButtonTitles);
{
for (NSString *otherButtonTitle = otherButtonTitles; otherButtonTitle != nil; otherButtonTitle = va_arg(otherButtonTitleList, NSString *)) {
[mutableOtherTitles addObject:otherButtonTitle];
}
}
va_end(otherButtonTitleList);
__block __weak id<NSObject> observer = [[NSNotificationCenter defaultCenter] addObserverForName:AFNetworkingTaskDidCompleteNotification object:task queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notification) {
NSError *error = notification.userInfo[AFNetworkingTaskDidCompleteErrorKey];
if (error) {
NSString *title, *message;
AFGetAlertViewTitleAndMessageFromError(error, &title, &message);
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil message:nil delegate:delegate cancelButtonTitle:cancelButtonTitle otherButtonTitles:nil, nil];
for (NSString *otherButtonTitle in mutableOtherTitles) {
[alertView addButtonWithTitle:otherButtonTitle];
}
[alertView setTitle:title];
[alertView setMessage:message];
[alertView show];
}
[[NSNotificationCenter defaultCenter] removeObserver:observer];
}];
}
#endif
#pragma mark -
+ (void)showAlertViewForRequestOperationWithErrorOnCompletion:(AFURLConnectionOperation *)operation
delegate:(id)delegate
{
[self showAlertViewForRequestOperationWithErrorOnCompletion:operation delegate:delegate cancelButtonTitle:NSLocalizedStringFromTable(@"Dismiss", @"AFNetworking", @"UIAlertView Cancel Button Title") otherButtonTitles:nil, nil];
}
+ (void)showAlertViewForRequestOperationWithErrorOnCompletion:(AFURLConnectionOperation *)operation
delegate:(id)delegate
cancelButtonTitle:(NSString *)cancelButtonTitle
otherButtonTitles:(NSString *)otherButtonTitles, ... NS_REQUIRES_NIL_TERMINATION
{
NSMutableArray *mutableOtherTitles = [NSMutableArray array];
va_list otherButtonTitleList;
va_start(otherButtonTitleList, otherButtonTitles);
{
for (NSString *otherButtonTitle = otherButtonTitles; otherButtonTitle != nil; otherButtonTitle = va_arg(otherButtonTitleList, NSString *)) {
[mutableOtherTitles addObject:otherButtonTitle];
}
}
va_end(otherButtonTitleList);
__block __weak id<NSObject> observer = [[NSNotificationCenter defaultCenter] addObserverForName:AFNetworkingOperationDidFinishNotification object:operation queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notification) {
if (notification.object && [notification.object isKindOfClass:[AFURLConnectionOperation class]]) {
NSError *error = [(AFURLConnectionOperation *)notification.object error];
if (error) {
NSString *title, *message;
AFGetAlertViewTitleAndMessageFromError(error, &title, &message);
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil message:nil delegate:delegate cancelButtonTitle:cancelButtonTitle otherButtonTitles:nil, nil];
for (NSString *otherButtonTitle in mutableOtherTitles) {
[alertView addButtonWithTitle:otherButtonTitle];
}
[alertView setTitle:title];
[alertView setMessage:message];
[alertView show];
}
}
[[NSNotificationCenter defaultCenter] removeObserver:observer];
}];
}
@end
#endif

View File

@ -0,0 +1,186 @@
// UIButton+AFNetworking.h
// Copyright (c) 20112015 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
#import <Availability.h>
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@protocol AFURLResponseSerialization, AFImageCache;
/**
This category adds methods to the UIKit framework's `UIButton` class. The methods in this category provide support for loading remote images and background images asynchronously from a URL.
@warning Compound values for control `state` (such as `UIControlStateHighlighted | UIControlStateDisabled`) are unsupported.
*/
@interface UIButton (AFNetworking)
///----------------------------
/// @name Accessing Image Cache
///----------------------------
/**
The image cache used to improve image loading performance on scroll views. By default, `UIButton` will use the `sharedImageCache` of `UIImageView`.
*/
+ (id <AFImageCache>)sharedImageCache;
/**
Set the cache used for image loading.
@param imageCache The image cache.
*/
+ (void)setSharedImageCache:(__nullable id <AFImageCache>)imageCache;
///------------------------------------
/// @name Accessing Response Serializer
///------------------------------------
/**
The response serializer used to create an image representation from the server response and response data. By default, this is an instance of `AFImageResponseSerializer`.
@discussion Subclasses of `AFImageResponseSerializer` could be used to perform post-processing, such as color correction, face detection, or other effects. See https://github.com/AFNetworking/AFCoreImageSerializer
*/
@property (nonatomic, strong) id <AFURLResponseSerialization> imageResponseSerializer;
///--------------------
/// @name Setting Image
///--------------------
/**
Asynchronously downloads an image from the specified URL, and sets it as the image for the specified state once the request is finished. Any previous image request for the receiver will be cancelled.
If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished.
@param state The control state.
@param url The URL used for the image request.
*/
- (void)setImageForState:(UIControlState)state
withURL:(NSURL *)url;
/**
Asynchronously downloads an image from the specified URL, and sets it as the image for the specified state once the request is finished. Any previous image request for the receiver will be cancelled.
If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished.
@param state The control state.
@param url The URL used for the image request.
@param placeholderImage The image to be set initially, until the image request finishes. If `nil`, the button will not change its image until the image request finishes.
*/
- (void)setImageForState:(UIControlState)state
withURL:(NSURL *)url
placeholderImage:(nullable UIImage *)placeholderImage;
/**
Asynchronously downloads an image from the specified URL request, and sets it as the image for the specified state once the request is finished. Any previous image request for the receiver will be cancelled.
If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished.
If a success block is specified, it is the responsibility of the block to set the image of the button before returning. If no success block is specified, the default behavior of setting the image with `setImage:forState:` is applied.
@param state The control state.
@param urlRequest The URL request used for the image request.
@param placeholderImage The image to be set initially, until the image request finishes. If `nil`, the button will not change its image until the image request finishes.
@param success A block to be executed when the image request operation finishes successfully. This block has no return value and takes two arguments: the server response and the image. If the image was returned from cache, the response parameter will be `nil`.
@param failure A block object to be executed when the image request operation finishes unsuccessfully, or that finishes successfully. This block has no return value and takes a single argument: the error that occurred.
*/
- (void)setImageForState:(UIControlState)state
withURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(nullable UIImage *)placeholderImage
success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * __nullable response, UIImage *image))success
failure:(nullable void (^)(NSError *error))failure;
///-------------------------------
/// @name Setting Background Image
///-------------------------------
/**
Asynchronously downloads an image from the specified URL, and sets it as the background image for the specified state once the request is finished. Any previous background image request for the receiver will be cancelled.
If the background image is cached locally, the background image is set immediately, otherwise the specified placeholder background image will be set immediately, and then the remote background image will be set once the request is finished.
@param state The control state.
@param url The URL used for the background image request.
*/
- (void)setBackgroundImageForState:(UIControlState)state
withURL:(NSURL *)url;
/**
Asynchronously downloads an image from the specified URL, and sets it as the background image for the specified state once the request is finished. Any previous image request for the receiver will be cancelled.
If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished.
@param state The control state.
@param url The URL used for the background image request.
@param placeholderImage The background image to be set initially, until the background image request finishes. If `nil`, the button will not change its background image until the background image request finishes.
*/
- (void)setBackgroundImageForState:(UIControlState)state
withURL:(NSURL *)url
placeholderImage:(nullable UIImage *)placeholderImage;
/**
Asynchronously downloads an image from the specified URL request, and sets it as the image for the specified state once the request is finished. Any previous image request for the receiver will be cancelled.
If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished.
If a success block is specified, it is the responsibility of the block to set the image of the button before returning. If no success block is specified, the default behavior of setting the image with `setBackgroundImage:forState:` is applied.
@param state The control state.
@param urlRequest The URL request used for the image request.
@param placeholderImage The background image to be set initially, until the background image request finishes. If `nil`, the button will not change its background image until the background image request finishes.
@param success A block to be executed when the image request operation finishes successfully. This block has no return value and takes two arguments: the server response and the image. If the image was returned from cache, the response parameter will be `nil`.
@param failure A block object to be executed when the image request operation finishes unsuccessfully, or that finishes successfully. This block has no return value and takes a single argument: the error that occurred.
*/
- (void)setBackgroundImageForState:(UIControlState)state
withURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(nullable UIImage *)placeholderImage
success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * __nullable response, UIImage *image))success
failure:(nullable void (^)(NSError *error))failure;
///------------------------------
/// @name Canceling Image Loading
///------------------------------
/**
Cancels any executing image operation for the specified control state of the receiver, if one exists.
@param state The control state.
*/
- (void)cancelImageRequestOperationForState:(UIControlState)state;
/**
Cancels any executing background image operation for the specified control state of the receiver, if one exists.
@param state The control state.
*/
- (void)cancelBackgroundImageRequestOperationForState:(UIControlState)state;
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,293 @@
// UIButton+AFNetworking.m
// Copyright (c) 20112015 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "UIButton+AFNetworking.h"
#import <objc/runtime.h>
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
#import "AFURLResponseSerialization.h"
#import "AFHTTPRequestOperation.h"
#import "UIImageView+AFNetworking.h"
@interface UIButton (_AFNetworking)
@end
@implementation UIButton (_AFNetworking)
+ (NSOperationQueue *)af_sharedImageRequestOperationQueue {
static NSOperationQueue *_af_sharedImageRequestOperationQueue = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_af_sharedImageRequestOperationQueue = [[NSOperationQueue alloc] init];
_af_sharedImageRequestOperationQueue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount;
});
return _af_sharedImageRequestOperationQueue;
}
#pragma mark -
static char AFImageRequestOperationNormal;
static char AFImageRequestOperationHighlighted;
static char AFImageRequestOperationSelected;
static char AFImageRequestOperationDisabled;
static const char * af_imageRequestOperationKeyForState(UIControlState state) {
switch (state) {
case UIControlStateHighlighted:
return &AFImageRequestOperationHighlighted;
case UIControlStateSelected:
return &AFImageRequestOperationSelected;
case UIControlStateDisabled:
return &AFImageRequestOperationDisabled;
case UIControlStateNormal:
default:
return &AFImageRequestOperationNormal;
}
}
- (AFHTTPRequestOperation *)af_imageRequestOperationForState:(UIControlState)state {
return (AFHTTPRequestOperation *)objc_getAssociatedObject(self, af_imageRequestOperationKeyForState(state));
}
- (void)af_setImageRequestOperation:(AFHTTPRequestOperation *)imageRequestOperation
forState:(UIControlState)state
{
objc_setAssociatedObject(self, af_imageRequestOperationKeyForState(state), imageRequestOperation, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#pragma mark -
static char AFBackgroundImageRequestOperationNormal;
static char AFBackgroundImageRequestOperationHighlighted;
static char AFBackgroundImageRequestOperationSelected;
static char AFBackgroundImageRequestOperationDisabled;
static const char * af_backgroundImageRequestOperationKeyForState(UIControlState state) {
switch (state) {
case UIControlStateHighlighted:
return &AFBackgroundImageRequestOperationHighlighted;
case UIControlStateSelected:
return &AFBackgroundImageRequestOperationSelected;
case UIControlStateDisabled:
return &AFBackgroundImageRequestOperationDisabled;
case UIControlStateNormal:
default:
return &AFBackgroundImageRequestOperationNormal;
}
}
- (AFHTTPRequestOperation *)af_backgroundImageRequestOperationForState:(UIControlState)state {
return (AFHTTPRequestOperation *)objc_getAssociatedObject(self, af_backgroundImageRequestOperationKeyForState(state));
}
- (void)af_setBackgroundImageRequestOperation:(AFHTTPRequestOperation *)imageRequestOperation
forState:(UIControlState)state
{
objc_setAssociatedObject(self, af_backgroundImageRequestOperationKeyForState(state), imageRequestOperation, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
#pragma mark -
@implementation UIButton (AFNetworking)
+ (id <AFImageCache>)sharedImageCache {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
return objc_getAssociatedObject(self, @selector(sharedImageCache)) ?: [UIImageView sharedImageCache];
#pragma clang diagnostic pop
}
+ (void)setSharedImageCache:(__nullable id <AFImageCache>)imageCache {
objc_setAssociatedObject(self, @selector(sharedImageCache), imageCache, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#pragma mark -
- (id <AFURLResponseSerialization>)imageResponseSerializer {
static id <AFURLResponseSerialization> _af_defaultImageResponseSerializer = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_af_defaultImageResponseSerializer = [AFImageResponseSerializer serializer];
});
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
return objc_getAssociatedObject(self, @selector(imageResponseSerializer)) ?: _af_defaultImageResponseSerializer;
#pragma clang diagnostic pop
}
- (void)setImageResponseSerializer:(id <AFURLResponseSerialization>)serializer {
objc_setAssociatedObject(self, @selector(imageResponseSerializer), serializer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#pragma mark -
- (void)setImageForState:(UIControlState)state
withURL:(NSURL *)url
{
[self setImageForState:state withURL:url placeholderImage:nil];
}
- (void)setImageForState:(UIControlState)state
withURL:(NSURL *)url
placeholderImage:(UIImage *)placeholderImage
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request addValue:@"image/*" forHTTPHeaderField:@"Accept"];
[self setImageForState:state withURLRequest:request placeholderImage:placeholderImage success:nil failure:nil];
}
- (void)setImageForState:(UIControlState)state
withURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(UIImage *)placeholderImage
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse * __nullable response, UIImage *image))success
failure:(void (^)(NSError *error))failure
{
[self cancelImageRequestOperationForState:state];
UIImage *cachedImage = [[[self class] sharedImageCache] cachedImageForRequest:urlRequest];
if (cachedImage) {
if (success) {
success(urlRequest, nil, cachedImage);
} else {
[self setImage:cachedImage forState:state];
}
[self af_setImageRequestOperation:nil forState:state];
} else {
if (placeholderImage) {
[self setImage:placeholderImage forState:state];
}
__weak __typeof(self)weakSelf = self;
AFHTTPRequestOperation *imageRequestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:urlRequest];
imageRequestOperation.responseSerializer = self.imageResponseSerializer;
[imageRequestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if ([[urlRequest URL] isEqual:[operation.request URL]]) {
if (success) {
success(operation.request, operation.response, responseObject);
} else if (responseObject) {
[strongSelf setImage:responseObject forState:state];
}
}
[[[strongSelf class] sharedImageCache] cacheImage:responseObject forRequest:urlRequest];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if ([[urlRequest URL] isEqual:[operation.request URL]]) {
if (failure) {
failure(error);
}
}
}];
[self af_setImageRequestOperation:imageRequestOperation forState:state];
[[[self class] af_sharedImageRequestOperationQueue] addOperation:imageRequestOperation];
}
}
#pragma mark -
- (void)setBackgroundImageForState:(UIControlState)state
withURL:(NSURL *)url
{
[self setBackgroundImageForState:state withURL:url placeholderImage:nil];
}
- (void)setBackgroundImageForState:(UIControlState)state
withURL:(NSURL *)url
placeholderImage:(UIImage *)placeholderImage
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request addValue:@"image/*" forHTTPHeaderField:@"Accept"];
[self setBackgroundImageForState:state withURLRequest:request placeholderImage:placeholderImage success:nil failure:nil];
}
- (void)setBackgroundImageForState:(UIControlState)state
withURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(UIImage *)placeholderImage
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse * __nullable response, UIImage *image))success
failure:(void (^)(NSError *error))failure
{
[self cancelBackgroundImageRequestOperationForState:state];
UIImage *cachedImage = [[[self class] sharedImageCache] cachedImageForRequest:urlRequest];
if (cachedImage) {
if (success) {
success(urlRequest, nil, cachedImage);
} else {
[self setBackgroundImage:cachedImage forState:state];
}
[self af_setBackgroundImageRequestOperation:nil forState:state];
} else {
if (placeholderImage) {
[self setBackgroundImage:placeholderImage forState:state];
}
__weak __typeof(self)weakSelf = self;
AFHTTPRequestOperation *backgroundImageRequestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:urlRequest];
backgroundImageRequestOperation.responseSerializer = self.imageResponseSerializer;
[backgroundImageRequestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if ([[urlRequest URL] isEqual:[operation.request URL]]) {
if (success) {
success(operation.request, operation.response, responseObject);
} else if (responseObject) {
[strongSelf setBackgroundImage:responseObject forState:state];
}
}
[[[strongSelf class] sharedImageCache] cacheImage:responseObject forRequest:urlRequest];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if ([[urlRequest URL] isEqual:[operation.request URL]]) {
if (failure) {
failure(error);
}
}
}];
[self af_setBackgroundImageRequestOperation:backgroundImageRequestOperation forState:state];
[[[self class] af_sharedImageRequestOperationQueue] addOperation:backgroundImageRequestOperation];
}
}
#pragma mark -
- (void)cancelImageRequestOperationForState:(UIControlState)state {
[[self af_imageRequestOperationForState:state] cancel];
[self af_setImageRequestOperation:nil forState:state];
}
- (void)cancelBackgroundImageRequestOperationForState:(UIControlState)state {
[[self af_backgroundImageRequestOperationForState:state] cancel];
[self af_setBackgroundImageRequestOperation:nil forState:state];
}
@end
#endif

View File

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

View File

@ -0,0 +1,146 @@
// UIImageView+AFNetworking.h
// Copyright (c) 20112015 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
#import <Availability.h>
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@protocol AFURLResponseSerialization, AFImageCache;
/**
This category adds methods to the UIKit framework's `UIImageView` class. The methods in this category provide support for loading remote images asynchronously from a URL.
*/
@interface UIImageView (AFNetworking)
///----------------------------
/// @name Accessing Image Cache
///----------------------------
/**
The image cache used to improve image loading performance on scroll views. By default, this is an `NSCache` subclass conforming to the `AFImageCache` protocol, which listens for notification warnings and evicts objects accordingly.
*/
+ (id <AFImageCache>)sharedImageCache;
/**
Set the cache used for image loading.
@param imageCache The image cache.
*/
+ (void)setSharedImageCache:(__nullable id <AFImageCache>)imageCache;
///------------------------------------
/// @name Accessing Response Serializer
///------------------------------------
/**
The response serializer used to create an image representation from the server response and response data. By default, this is an instance of `AFImageResponseSerializer`.
@discussion Subclasses of `AFImageResponseSerializer` could be used to perform post-processing, such as color correction, face detection, or other effects. See https://github.com/AFNetworking/AFCoreImageSerializer
*/
@property (nonatomic, strong) id <AFURLResponseSerialization> imageResponseSerializer;
///--------------------
/// @name Setting Image
///--------------------
/**
Asynchronously downloads an image from the specified URL, and sets it once the request is finished. Any previous image request for the receiver will be cancelled.
If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished.
By default, URL requests have a `Accept` header field value of "image / *", a cache policy of `NSURLCacheStorageAllowed` and a timeout interval of 30 seconds, and are set not handle cookies. To configure URL requests differently, use `setImageWithURLRequest:placeholderImage:success:failure:`
@param url The URL used for the image request.
*/
- (void)setImageWithURL:(NSURL *)url;
/**
Asynchronously downloads an image from the specified URL, and sets it once the request is finished. Any previous image request for the receiver will be cancelled.
If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished.
By default, URL requests have a `Accept` header field value of "image / *", a cache policy of `NSURLCacheStorageAllowed` and a timeout interval of 30 seconds, and are set not handle cookies. To configure URL requests differently, use `setImageWithURLRequest:placeholderImage:success:failure:`
@param url The URL used for the image request.
@param placeholderImage The image to be set initially, until the image request finishes. If `nil`, the image view will not change its image until the image request finishes.
*/
- (void)setImageWithURL:(NSURL *)url
placeholderImage:(nullable UIImage *)placeholderImage;
/**
Asynchronously downloads an image from the specified URL request, and sets it once the request is finished. Any previous image request for the receiver will be cancelled.
If the image is cached locally, the image is set immediately, otherwise the specified placeholder image will be set immediately, and then the remote image will be set once the request is finished.
If a success block is specified, it is the responsibility of the block to set the image of the image view before returning. If no success block is specified, the default behavior of setting the image with `self.image = image` is applied.
@param urlRequest The URL request used for the image request.
@param placeholderImage The image to be set initially, until the image request finishes. If `nil`, the image view will not change its image until the image request finishes.
@param success A block to be executed when the image request operation finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the image created from the response data of request. If the image was returned from cache, the response parameter will be `nil`.
@param failure A block object to be executed when the image request operation finishes unsuccessfully, or that finishes successfully. This block has no return value and takes three arguments: the request sent from the client, the response received from the server, and the error object describing the network or parsing error that occurred.
*/
- (void)setImageWithURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(nullable UIImage *)placeholderImage
success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * __nullable response, UIImage *image))success
failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * __nullable response, NSError *error))failure;
/**
Cancels any executing image operation for the receiver, if one exists.
*/
- (void)cancelImageRequestOperation;
@end
#pragma mark -
/**
The `AFImageCache` protocol is adopted by an object used to cache images loaded by the AFNetworking category on `UIImageView`.
*/
@protocol AFImageCache <NSObject>
/**
Returns a cached image for the specified request, if available.
@param request The image request.
@return The cached image.
*/
- (nullable UIImage *)cachedImageForRequest:(NSURLRequest *)request;
/**
Caches a particular image for the specified request.
@param image The image to cache.
@param request The request to be used as a cache key.
*/
- (void)cacheImage:(UIImage *)image
forRequest:(NSURLRequest *)request;
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,215 @@
// UIImageView+AFNetworking.m
// Copyright (c) 20112015 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "UIImageView+AFNetworking.h"
#import <objc/runtime.h>
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
#import "AFHTTPRequestOperation.h"
@interface AFImageCache : NSCache <AFImageCache>
@end
#pragma mark -
@interface UIImageView (_AFNetworking)
@property (readwrite, nonatomic, strong, setter = af_setImageRequestOperation:) AFHTTPRequestOperation *af_imageRequestOperation;
@end
@implementation UIImageView (_AFNetworking)
+ (NSOperationQueue *)af_sharedImageRequestOperationQueue {
static NSOperationQueue *_af_sharedImageRequestOperationQueue = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_af_sharedImageRequestOperationQueue = [[NSOperationQueue alloc] init];
_af_sharedImageRequestOperationQueue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount;
});
return _af_sharedImageRequestOperationQueue;
}
- (AFHTTPRequestOperation *)af_imageRequestOperation {
return (AFHTTPRequestOperation *)objc_getAssociatedObject(self, @selector(af_imageRequestOperation));
}
- (void)af_setImageRequestOperation:(AFHTTPRequestOperation *)imageRequestOperation {
objc_setAssociatedObject(self, @selector(af_imageRequestOperation), imageRequestOperation, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
#pragma mark -
@implementation UIImageView (AFNetworking)
@dynamic imageResponseSerializer;
+ (id <AFImageCache>)sharedImageCache {
static AFImageCache *_af_defaultImageCache = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_af_defaultImageCache = [[AFImageCache alloc] init];
[[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidReceiveMemoryWarningNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * __unused notification) {
[_af_defaultImageCache removeAllObjects];
}];
});
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
return objc_getAssociatedObject(self, @selector(sharedImageCache)) ?: _af_defaultImageCache;
#pragma clang diagnostic pop
}
+ (void)setSharedImageCache:(__nullable id <AFImageCache>)imageCache {
objc_setAssociatedObject(self, @selector(sharedImageCache), imageCache, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#pragma mark -
- (id <AFURLResponseSerialization>)imageResponseSerializer {
static id <AFURLResponseSerialization> _af_defaultImageResponseSerializer = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_af_defaultImageResponseSerializer = [AFImageResponseSerializer serializer];
});
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
return objc_getAssociatedObject(self, @selector(imageResponseSerializer)) ?: _af_defaultImageResponseSerializer;
#pragma clang diagnostic pop
}
- (void)setImageResponseSerializer:(id <AFURLResponseSerialization>)serializer {
objc_setAssociatedObject(self, @selector(imageResponseSerializer), serializer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#pragma mark -
- (void)setImageWithURL:(NSURL *)url {
[self setImageWithURL:url placeholderImage:nil];
}
- (void)setImageWithURL:(NSURL *)url
placeholderImage:(UIImage *)placeholderImage
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request addValue:@"image/*" forHTTPHeaderField:@"Accept"];
[self setImageWithURLRequest:request placeholderImage:placeholderImage success:nil failure:nil];
}
- (void)setImageWithURLRequest:(NSURLRequest *)urlRequest
placeholderImage:(UIImage *)placeholderImage
success:(void (^)(NSURLRequest *request, NSHTTPURLResponse * __nullable response, UIImage *image))success
failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse * __nullable response, NSError *error))failure
{
[self cancelImageRequestOperation];
UIImage *cachedImage = [[[self class] sharedImageCache] cachedImageForRequest:urlRequest];
if (cachedImage) {
if (success) {
success(urlRequest, nil, cachedImage);
} else {
self.image = cachedImage;
}
self.af_imageRequestOperation = nil;
} else {
if (placeholderImage) {
self.image = placeholderImage;
}
__weak __typeof(self)weakSelf = self;
self.af_imageRequestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:urlRequest];
self.af_imageRequestOperation.responseSerializer = self.imageResponseSerializer;
[self.af_imageRequestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if ([[urlRequest URL] isEqual:[strongSelf.af_imageRequestOperation.request URL]]) {
if (success) {
success(urlRequest, operation.response, responseObject);
} else if (responseObject) {
strongSelf.image = responseObject;
}
if (operation == strongSelf.af_imageRequestOperation){
strongSelf.af_imageRequestOperation = nil;
}
}
[[[strongSelf class] sharedImageCache] cacheImage:responseObject forRequest:urlRequest];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
if ([[urlRequest URL] isEqual:[strongSelf.af_imageRequestOperation.request URL]]) {
if (failure) {
failure(urlRequest, operation.response, error);
}
if (operation == strongSelf.af_imageRequestOperation){
strongSelf.af_imageRequestOperation = nil;
}
}
}];
[[[self class] af_sharedImageRequestOperationQueue] addOperation:self.af_imageRequestOperation];
}
}
- (void)cancelImageRequestOperation {
[self.af_imageRequestOperation cancel];
self.af_imageRequestOperation = nil;
}
@end
#pragma mark -
static inline NSString * AFImageCacheKeyFromURLRequest(NSURLRequest *request) {
return [[request URL] absoluteString];
}
@implementation AFImageCache
- (UIImage *)cachedImageForRequest:(NSURLRequest *)request {
switch ([request cachePolicy]) {
case NSURLRequestReloadIgnoringCacheData:
case NSURLRequestReloadIgnoringLocalAndRemoteCacheData:
return nil;
default:
break;
}
return [self objectForKey:AFImageCacheKeyFromURLRequest(request)];
}
- (void)cacheImage:(UIImage *)image
forRequest:(NSURLRequest *)request
{
if (image && request) {
[self setObject:image forKey:AFImageCacheKeyFromURLRequest(request)];
}
}
@end
#endif

View File

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

View File

@ -0,0 +1,91 @@
// UIProgressView+AFNetworking.h
// Copyright (c) 20112015 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
#import <Availability.h>
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@class AFURLConnectionOperation;
/**
This category adds methods to the UIKit framework's `UIProgressView` class. The methods in this category provide support for binding the progress to the upload and download progress of a session task or request operation.
*/
@interface UIProgressView (AFNetworking)
///------------------------------------
/// @name Setting Session Task Progress
///------------------------------------
/**
Binds the progress to the upload progress of the specified session task.
@param task The session task.
@param animated `YES` if the change should be animated, `NO` if the change should happen immediately.
*/
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
- (void)setProgressWithUploadProgressOfTask:(NSURLSessionUploadTask *)task
animated:(BOOL)animated;
#endif
/**
Binds the progress to the download progress of the specified session task.
@param task The session task.
@param animated `YES` if the change should be animated, `NO` if the change should happen immediately.
*/
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
- (void)setProgressWithDownloadProgressOfTask:(NSURLSessionDownloadTask *)task
animated:(BOOL)animated;
#endif
///------------------------------------
/// @name Setting Session Task Progress
///------------------------------------
/**
Binds the progress to the upload progress of the specified request operation.
@param operation The request operation.
@param animated `YES` if the change should be animated, `NO` if the change should happen immediately.
*/
- (void)setProgressWithUploadProgressOfOperation:(AFURLConnectionOperation *)operation
animated:(BOOL)animated;
/**
Binds the progress to the download progress of the specified request operation.
@param operation The request operation.
@param animated `YES` if the change should be animated, `NO` if the change should happen immediately.
*/
- (void)setProgressWithDownloadProgressOfOperation:(AFURLConnectionOperation *)operation
animated:(BOOL)animated;
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,182 @@
// UIProgressView+AFNetworking.m
// Copyright (c) 20112015 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "UIProgressView+AFNetworking.h"
#import <objc/runtime.h>
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
#import "AFURLConnectionOperation.h"
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
#import "AFURLSessionManager.h"
#endif
static void * AFTaskCountOfBytesSentContext = &AFTaskCountOfBytesSentContext;
static void * AFTaskCountOfBytesReceivedContext = &AFTaskCountOfBytesReceivedContext;
@interface AFURLConnectionOperation (_UIProgressView)
@property (readwrite, nonatomic, copy) void (^uploadProgress)(NSUInteger bytes, long long totalBytes, long long totalBytesExpected);
@property (readwrite, nonatomic, assign, setter = af_setUploadProgressAnimated:) BOOL af_uploadProgressAnimated;
@property (readwrite, nonatomic, copy) void (^downloadProgress)(NSUInteger bytes, long long totalBytes, long long totalBytesExpected);
@property (readwrite, nonatomic, assign, setter = af_setDownloadProgressAnimated:) BOOL af_downloadProgressAnimated;
@end
@implementation AFURLConnectionOperation (_UIProgressView)
@dynamic uploadProgress; // Implemented in AFURLConnectionOperation
@dynamic af_uploadProgressAnimated;
@dynamic downloadProgress; // Implemented in AFURLConnectionOperation
@dynamic af_downloadProgressAnimated;
@end
#pragma mark -
@implementation UIProgressView (AFNetworking)
- (BOOL)af_uploadProgressAnimated {
return [(NSNumber *)objc_getAssociatedObject(self, @selector(af_uploadProgressAnimated)) boolValue];
}
- (void)af_setUploadProgressAnimated:(BOOL)animated {
objc_setAssociatedObject(self, @selector(af_uploadProgressAnimated), @(animated), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL)af_downloadProgressAnimated {
return [(NSNumber *)objc_getAssociatedObject(self, @selector(af_downloadProgressAnimated)) boolValue];
}
- (void)af_setDownloadProgressAnimated:(BOOL)animated {
objc_setAssociatedObject(self, @selector(af_downloadProgressAnimated), @(animated), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#pragma mark -
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
- (void)setProgressWithUploadProgressOfTask:(NSURLSessionUploadTask *)task
animated:(BOOL)animated
{
[task addObserver:self forKeyPath:@"state" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesSentContext];
[task addObserver:self forKeyPath:@"countOfBytesSent" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesSentContext];
[self af_setUploadProgressAnimated:animated];
}
- (void)setProgressWithDownloadProgressOfTask:(NSURLSessionDownloadTask *)task
animated:(BOOL)animated
{
[task addObserver:self forKeyPath:@"state" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesReceivedContext];
[task addObserver:self forKeyPath:@"countOfBytesReceived" options:(NSKeyValueObservingOptions)0 context:AFTaskCountOfBytesReceivedContext];
[self af_setDownloadProgressAnimated:animated];
}
#endif
#pragma mark -
- (void)setProgressWithUploadProgressOfOperation:(AFURLConnectionOperation *)operation
animated:(BOOL)animated
{
__weak __typeof(self)weakSelf = self;
void (^original)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) = [operation.uploadProgress copy];
[operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
if (original) {
original(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
}
dispatch_async(dispatch_get_main_queue(), ^{
if (totalBytesExpectedToWrite > 0) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
[strongSelf setProgress:(totalBytesWritten / (totalBytesExpectedToWrite * 1.0f)) animated:animated];
}
});
}];
}
- (void)setProgressWithDownloadProgressOfOperation:(AFURLConnectionOperation *)operation
animated:(BOOL)animated
{
__weak __typeof(self)weakSelf = self;
void (^original)(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) = [operation.downloadProgress copy];
[operation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
if (original) {
original(bytesRead, totalBytesRead, totalBytesExpectedToRead);
}
dispatch_async(dispatch_get_main_queue(), ^{
if (totalBytesExpectedToRead > 0) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
[strongSelf setProgress:(totalBytesRead / (totalBytesExpectedToRead * 1.0f)) animated:animated];
}
});
}];
}
#pragma mark - NSKeyValueObserving
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(__unused NSDictionary *)change
context:(void *)context
{
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
if (context == AFTaskCountOfBytesSentContext || context == AFTaskCountOfBytesReceivedContext) {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) {
if ([object countOfBytesExpectedToSend] > 0) {
dispatch_async(dispatch_get_main_queue(), ^{
[self setProgress:[object countOfBytesSent] / ([object countOfBytesExpectedToSend] * 1.0f) animated:self.af_uploadProgressAnimated];
});
}
}
if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) {
if ([object countOfBytesExpectedToReceive] > 0) {
dispatch_async(dispatch_get_main_queue(), ^{
[self setProgress:[object countOfBytesReceived] / ([object countOfBytesExpectedToReceive] * 1.0f) animated:self.af_downloadProgressAnimated];
});
}
}
if ([keyPath isEqualToString:NSStringFromSelector(@selector(state))]) {
if ([(NSURLSessionTask *)object state] == NSURLSessionTaskStateCompleted) {
@try {
[object removeObserver:self forKeyPath:NSStringFromSelector(@selector(state))];
if (context == AFTaskCountOfBytesSentContext) {
[object removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesSent))];
}
if (context == AFTaskCountOfBytesReceivedContext) {
[object removeObserver:self forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))];
}
}
@catch (NSException * __unused exception) {}
}
}
}
#endif
}
@end
#endif

View File

@ -0,0 +1,68 @@
// UIRefreshControl+AFNetworking.m
//
// Copyright (c) 2014 AFNetworking (http://afnetworking.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
#import <Availability.h>
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@class AFURLConnectionOperation;
/**
This category adds methods to the UIKit framework's `UIRefreshControl` class. The methods in this category provide support for automatically beginning and ending refreshing depending on the loading state of a request operation or session task.
*/
@interface UIRefreshControl (AFNetworking)
///-----------------------------------
/// @name Refreshing for Session Tasks
///-----------------------------------
/**
Binds the refreshing state to the state of the specified task.
@param task The task. If `nil`, automatic updating from any previously specified operation will be disabled.
*/
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
- (void)setRefreshingWithStateOfTask:(NSURLSessionTask *)task;
#endif
///----------------------------------------
/// @name Refreshing for Request Operations
///----------------------------------------
/**
Binds the refreshing state to the execution state of the specified operation.
@param operation The operation. If `nil`, automatic updating from any previously specified operation will be disabled.
*/
- (void)setRefreshingWithStateOfOperation:(AFURLConnectionOperation *)operation;
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,166 @@
// UIRefreshControl+AFNetworking.m
//
// Copyright (c) 2014 AFNetworking (http://afnetworking.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "UIRefreshControl+AFNetworking.h"
#import <objc/runtime.h>
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
#import "AFHTTPRequestOperation.h"
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
#import "AFURLSessionManager.h"
#endif
@interface AFRefreshControlNotificationObserver : NSObject
@property (readonly, nonatomic, weak) UIRefreshControl *refreshControl;
- (instancetype)initWithActivityRefreshControl:(UIRefreshControl *)refreshControl;
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
- (void)setRefreshingWithStateOfTask:(NSURLSessionTask *)task;
#endif
- (void)setRefreshingWithStateOfOperation:(AFURLConnectionOperation *)operation;
@end
@implementation UIRefreshControl (AFNetworking)
- (AFRefreshControlNotificationObserver *)af_notificationObserver {
AFRefreshControlNotificationObserver *notificationObserver = objc_getAssociatedObject(self, @selector(af_notificationObserver));
if (notificationObserver == nil) {
notificationObserver = [[AFRefreshControlNotificationObserver alloc] initWithActivityRefreshControl:self];
objc_setAssociatedObject(self, @selector(af_notificationObserver), notificationObserver, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return notificationObserver;
}
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
- (void)setRefreshingWithStateOfTask:(NSURLSessionTask *)task {
[[self af_notificationObserver] setRefreshingWithStateOfTask:task];
}
#endif
- (void)setRefreshingWithStateOfOperation:(AFURLConnectionOperation *)operation {
[[self af_notificationObserver] setRefreshingWithStateOfOperation:operation];
}
@end
@implementation AFRefreshControlNotificationObserver
- (instancetype)initWithActivityRefreshControl:(UIRefreshControl *)refreshControl
{
self = [super init];
if (self) {
_refreshControl = refreshControl;
}
return self;
}
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
- (void)setRefreshingWithStateOfTask:(NSURLSessionTask *)task {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter removeObserver:self name:AFNetworkingTaskDidResumeNotification object:nil];
[notificationCenter removeObserver:self name:AFNetworkingTaskDidSuspendNotification object:nil];
[notificationCenter removeObserver:self name:AFNetworkingTaskDidCompleteNotification object:nil];
if (task) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreceiver-is-weak"
#pragma clang diagnostic ignored "-Warc-repeated-use-of-weak"
if (task.state == NSURLSessionTaskStateRunning) {
[self.refreshControl beginRefreshing];
[notificationCenter addObserver:self selector:@selector(af_beginRefreshing) name:AFNetworkingTaskDidResumeNotification object:task];
[notificationCenter addObserver:self selector:@selector(af_endRefreshing) name:AFNetworkingTaskDidCompleteNotification object:task];
[notificationCenter addObserver:self selector:@selector(af_endRefreshing) name:AFNetworkingTaskDidSuspendNotification object:task];
} else {
[self.refreshControl endRefreshing];
}
#pragma clang diagnostic pop
}
}
#endif
- (void)setRefreshingWithStateOfOperation:(AFURLConnectionOperation *)operation {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter removeObserver:self name:AFNetworkingOperationDidStartNotification object:nil];
[notificationCenter removeObserver:self name:AFNetworkingOperationDidFinishNotification object:nil];
if (operation) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreceiver-is-weak"
#pragma clang diagnostic ignored "-Warc-repeated-use-of-weak"
if (![operation isFinished]) {
if ([operation isExecuting]) {
[self.refreshControl beginRefreshing];
} else {
[self.refreshControl endRefreshing];
}
[notificationCenter addObserver:self selector:@selector(af_beginRefreshing) name:AFNetworkingOperationDidStartNotification object:operation];
[notificationCenter addObserver:self selector:@selector(af_endRefreshing) name:AFNetworkingOperationDidFinishNotification object:operation];
}
#pragma clang diagnostic pop
}
}
#pragma mark -
- (void)af_beginRefreshing {
dispatch_async(dispatch_get_main_queue(), ^{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreceiver-is-weak"
[self.refreshControl beginRefreshing];
#pragma clang diagnostic pop
});
}
- (void)af_endRefreshing {
dispatch_async(dispatch_get_main_queue(), ^{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreceiver-is-weak"
[self.refreshControl endRefreshing];
#pragma clang diagnostic pop
});
}
#pragma mark -
- (void)dealloc {
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 70000
[notificationCenter removeObserver:self name:AFNetworkingTaskDidCompleteNotification object:nil];
[notificationCenter removeObserver:self name:AFNetworkingTaskDidResumeNotification object:nil];
[notificationCenter removeObserver:self name:AFNetworkingTaskDidSuspendNotification object:nil];
#endif
[notificationCenter removeObserver:self name:AFNetworkingOperationDidStartNotification object:nil];
[notificationCenter removeObserver:self name:AFNetworkingOperationDidFinishNotification object:nil];
}
@end
#endif

View File

@ -0,0 +1,86 @@
// UIWebView+AFNetworking.h
// Copyright (c) 20112015 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import <Foundation/Foundation.h>
#import <Availability.h>
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@class AFHTTPRequestSerializer, AFHTTPResponseSerializer;
@protocol AFURLRequestSerialization, AFURLResponseSerialization;
/**
This category adds methods to the UIKit framework's `UIWebView` class. The methods in this category provide increased control over the request cycle, including progress monitoring and success / failure handling.
@discussion When using these category methods, make sure to assign `delegate` for the web view, which implements `webView:shouldStartLoadWithRequest:navigationType:` appropriately. This allows for tapped links to be loaded through AFNetworking, and can ensure that `canGoBack` & `canGoForward` update their values correctly.
*/
@interface UIWebView (AFNetworking)
/**
The request serializer used to serialize requests made with the `-loadRequest:...` category methods. By default, this is an instance of `AFHTTPRequestSerializer`.
*/
@property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;
/**
The response serializer used to serialize responses made with the `-loadRequest:...` category methods. By default, this is an instance of `AFHTTPResponseSerializer`.
*/
@property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;
/**
Asynchronously loads the specified request.
@param request A URL request identifying the location of the content to load. This must not be `nil`.
@param progress 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.
@param success A block object to be executed when the request finishes loading successfully. This block returns the HTML string to be loaded by the web view, and takes two arguments: the response, and the response string.
@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 a single argument: the error that occurred.
*/
- (void)loadRequest:(NSURLRequest *)request
progress:(nullable void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))progress
success:(nullable NSString * (^)(NSHTTPURLResponse *response, NSString *HTML))success
failure:(nullable void (^)(NSError *error))failure;
/**
Asynchronously loads the data associated with a particular request with a specified MIME type and text encoding.
@param request A URL request identifying the location of the content to load. This must not be `nil`.
@param MIMEType The MIME type of the content. Defaults to the content type of the response if not specified.
@param textEncodingName The IANA encoding name, as in `utf-8` or `utf-16`. Defaults to the response text encoding if not specified.
@param progress 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.
@param success A block object to be executed when the request finishes loading successfully. This block returns the data to be loaded by the web view and takes two arguments: the response, and the downloaded data.
@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 a single argument: the error that occurred.
*/
- (void)loadRequest:(NSURLRequest *)request
MIMEType:(nullable NSString *)MIMEType
textEncodingName:(nullable NSString *)textEncodingName
progress:(nullable void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))progress
success:(nullable NSData * (^)(NSHTTPURLResponse *response, NSData *data))success
failure:(nullable void (^)(NSError *error))failure;
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -0,0 +1,159 @@
// UIWebView+AFNetworking.m
// Copyright (c) 20112015 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
#import "UIWebView+AFNetworking.h"
#import <objc/runtime.h>
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
#import "AFHTTPRequestOperation.h"
#import "AFURLResponseSerialization.h"
#import "AFURLRequestSerialization.h"
@interface UIWebView (_AFNetworking)
@property (readwrite, nonatomic, strong, setter = af_setHTTPRequestOperation:) AFHTTPRequestOperation *af_HTTPRequestOperation;
@end
@implementation UIWebView (_AFNetworking)
- (AFHTTPRequestOperation *)af_HTTPRequestOperation {
return (AFHTTPRequestOperation *)objc_getAssociatedObject(self, @selector(af_HTTPRequestOperation));
}
- (void)af_setHTTPRequestOperation:(AFHTTPRequestOperation *)operation {
objc_setAssociatedObject(self, @selector(af_HTTPRequestOperation), operation, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
#pragma mark -
@implementation UIWebView (AFNetworking)
- (AFHTTPRequestSerializer <AFURLRequestSerialization> *)requestSerializer {
static AFHTTPRequestSerializer <AFURLRequestSerialization> *_af_defaultRequestSerializer = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_af_defaultRequestSerializer = [AFHTTPRequestSerializer serializer];
});
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
return objc_getAssociatedObject(self, @selector(requestSerializer)) ?: _af_defaultRequestSerializer;
#pragma clang diagnostic pop
}
- (void)setRequestSerializer:(AFHTTPRequestSerializer<AFURLRequestSerialization> *)requestSerializer {
objc_setAssociatedObject(self, @selector(requestSerializer), requestSerializer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (AFHTTPResponseSerializer <AFURLResponseSerialization> *)responseSerializer {
static AFHTTPResponseSerializer <AFURLResponseSerialization> *_af_defaultResponseSerializer = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_af_defaultResponseSerializer = [AFHTTPResponseSerializer serializer];
});
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
return objc_getAssociatedObject(self, @selector(responseSerializer)) ?: _af_defaultResponseSerializer;
#pragma clang diagnostic pop
}
- (void)setResponseSerializer:(AFHTTPResponseSerializer<AFURLResponseSerialization> *)responseSerializer {
objc_setAssociatedObject(self, @selector(responseSerializer), responseSerializer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#pragma mark -
- (void)loadRequest:(NSURLRequest *)request
progress:(void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))progress
success:(NSString * (^)(NSHTTPURLResponse *response, NSString *HTML))success
failure:(void (^)(NSError *error))failure
{
[self loadRequest:request MIMEType:nil textEncodingName:nil progress:progress success:^NSData *(NSHTTPURLResponse *response, NSData *data) {
NSStringEncoding stringEncoding = NSUTF8StringEncoding;
if (response.textEncodingName) {
CFStringEncoding encoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
if (encoding != kCFStringEncodingInvalidId) {
stringEncoding = CFStringConvertEncodingToNSStringEncoding(encoding);
}
}
NSString *string = [[NSString alloc] initWithData:data encoding:stringEncoding];
if (success) {
string = success(response, string);
}
return [string dataUsingEncoding:stringEncoding];
} failure:failure];
}
- (void)loadRequest:(NSURLRequest *)request
MIMEType:(NSString *)MIMEType
textEncodingName:(NSString *)textEncodingName
progress:(void (^)(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite))progress
success:(NSData * (^)(NSHTTPURLResponse *response, NSData *data))success
failure:(void (^)(NSError *error))failure
{
NSParameterAssert(request);
if (self.af_HTTPRequestOperation) {
[self.af_HTTPRequestOperation cancel];
}
request = [self.requestSerializer requestBySerializingRequest:request withParameters:nil error:nil];
self.af_HTTPRequestOperation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
self.af_HTTPRequestOperation.responseSerializer = self.responseSerializer;
__weak __typeof(self)weakSelf = self;
[self.af_HTTPRequestOperation setDownloadProgressBlock:progress];
[self.af_HTTPRequestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id __unused responseObject) {
NSData *data = success ? success(operation.response, operation.responseData) : operation.responseData;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
__strong __typeof(weakSelf) strongSelf = weakSelf;
[strongSelf loadData:data MIMEType:(MIMEType ?: [operation.response MIMEType]) textEncodingName:(textEncodingName ?: [operation.response textEncodingName]) baseURL:[operation.response URL]];
if ([strongSelf.delegate respondsToSelector:@selector(webViewDidFinishLoad:)]) {
[strongSelf.delegate webViewDidFinishLoad:strongSelf];
}
#pragma clang diagnostic pop
} failure:^(AFHTTPRequestOperation * __unused operation, NSError *error) {
if (failure) {
failure(error);
}
}];
[self.af_HTTPRequestOperation start];
if ([self.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) {
[self.delegate webViewDidStartLoad:self];
}
}
@end
#endif

19
Pods/Alamofire-SwiftyJSON/LICENSE generated Normal file
View File

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

47
Pods/Alamofire-SwiftyJSON/README.md generated Normal file
View File

@ -0,0 +1,47 @@
#AlamofireSwiftyJSON
[![Build Status](https://travis-ci.org/starboychina/AlamofireSwiftyJSON.svg)](https://travis-ci.org/starboychina/AlamofireSwiftyJSON)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
[![GitHub release](https://img.shields.io/github/release/starboychina/AlamofireSwiftyJSON.svg)](https://github.com/starboychina/AlamofireSwiftyJSON/releases)
[![SwiftLint](https://img.shields.io/badge/SwiftLint-passing-brightgreen.svg)](https://github.com/realm/SwiftLint)
---
Easy way to use both [Alamofire](https://github.com/Alamofire/Alamofire) and [SwiftyJSON](https://github.com/SwiftyJSON/SwiftyJSON)
## Requirements
- iOS 8.0+ / Mac OS X 10.9+
- Xcode 7.0
## Install
- CocoaPods
```
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
use_frameworks!
target "target name" do
pod 'AlamofireSwiftyJSON'
end
```
- Carthage
[Carthage](https://github.com/Carthage/Carthage)
```ogdl
github "starboychina/AlamofireSwiftyJSON"
```
## Usage
```swift
let URL = "http://httpbin.org/get"
Alamofire.request(.GET, URL, parameters: ["foo": "bar"]).responseSwiftyJSON { response in
print("###Success: \(response.result.isSuccess)")
//now response.result.value is SwiftyJSON.JSON type
print("###Value: \(response.result.value?["args"].array)")
}
```

View File

@ -0,0 +1,78 @@
//
// AlamofireSwiftyJSON.swift
// AlamofireSwiftyJSON
//
// Created by Hikaru on 2016/01/29.
// Copyright © 2016 Hikaru. All rights reserved.
//
import Foundation
import Alamofire
import SwiftyJSON
// MARK: - Request for Swift JSON
extension Request {
/**
Adds a handler to be called once the request has finished.
- parameter queue: The queue on which the completion handler is dispatched.
- parameter options: The JSON serialization reading options.
`.AllowFragments` by default.
- parameter completionHandler: A closure to be executed once the request has finished.
- returns: The request.
*/
public func responseSwiftyJSON(
queue: dispatch_queue_t? = nil,
options: NSJSONReadingOptions = .AllowFragments,
completionHandler: Response<JSON, NSError> -> Void)
-> Self {
return response(
queue: queue,
responseSerializer: Request.SwiftyJSONResponseSerializer(options: options),
completionHandler: completionHandler
)
}
/**
Creates a response serializer that returns a SwiftyJSON object constructed
from the response data using `NSJSONSerialization`
with the specified reading options.
- parameter options: The SwiftyJSON serialization reading options.
`.AllowFragments` by default.
- returns: A SwiftyJSON object response serializer.
*/
public static func SwiftyJSONResponseSerializer(
options options: NSJSONReadingOptions = .AllowFragments)
-> ResponseSerializer<JSON, NSError> {
return ResponseSerializer { _, response, data, error in
guard error == nil else { return .Failure(error!) }
if let response = response where response.statusCode == 204 {
return .Success(JSON.null)
}
guard let validData = data where validData.length > 0 else {
let failureReason = "JSON could not be serialized. " +
"Input data was nil or zero length."
let userInfo = [NSLocalizedFailureReasonErrorKey: failureReason]
let code = Error.Code.JSONSerializationFailed.rawValue
let e = NSError(domain: Error.Domain, code: code, userInfo: userInfo)
return .Failure(e)
}
do {
let json = try NSJSONSerialization.JSONObjectWithData(validData, options: options)
return .Success(JSON(json))
} catch {
return .Failure(error as NSError)
}
}
}
}

19
Pods/Alamofire/LICENSE generated Normal file
View File

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

1270
Pods/Alamofire/README.md generated Normal file

File diff suppressed because it is too large Load Diff

370
Pods/Alamofire/Source/Alamofire.swift generated Normal file
View File

@ -0,0 +1,370 @@
//
// Alamofire.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
// MARK: - URLStringConvertible
/**
Types adopting the `URLStringConvertible` protocol can be used to construct URL strings, which are then used to
construct URL requests.
*/
public protocol URLStringConvertible {
/**
A URL that conforms to RFC 2396.
Methods accepting a `URLStringConvertible` type parameter parse it according to RFCs 1738 and 1808.
See https://tools.ietf.org/html/rfc2396
See https://tools.ietf.org/html/rfc1738
See https://tools.ietf.org/html/rfc1808
*/
var URLString: String { get }
}
extension String: URLStringConvertible {
public var URLString: String {
return self
}
}
extension NSURL: URLStringConvertible {
public var URLString: String {
return absoluteString
}
}
extension NSURLComponents: URLStringConvertible {
public var URLString: String {
return URL!.URLString
}
}
extension NSURLRequest: URLStringConvertible {
public var URLString: String {
return URL!.URLString
}
}
// MARK: - URLRequestConvertible
/**
Types adopting the `URLRequestConvertible` protocol can be used to construct URL requests.
*/
public protocol URLRequestConvertible {
/// The URL request.
var URLRequest: NSMutableURLRequest { get }
}
extension NSURLRequest: URLRequestConvertible {
public var URLRequest: NSMutableURLRequest {
return self.mutableCopy() as! NSMutableURLRequest
}
}
// MARK: - Convenience
func URLRequest(
method: Method,
_ URLString: URLStringConvertible,
headers: [String: String]? = nil)
-> NSMutableURLRequest
{
let mutableURLRequest = NSMutableURLRequest(URL: NSURL(string: URLString.URLString)!)
mutableURLRequest.HTTPMethod = method.rawValue
if let headers = headers {
for (headerField, headerValue) in headers {
mutableURLRequest.setValue(headerValue, forHTTPHeaderField: headerField)
}
}
return mutableURLRequest
}
// MARK: - Request Methods
/**
Creates a request using the shared manager instance for the specified method, URL string, parameters, and
parameter encoding.
- parameter method: The HTTP method.
- parameter URLString: The URL string.
- parameter parameters: The parameters. `nil` by default.
- parameter encoding: The parameter encoding. `.URL` by default.
- parameter headers: The HTTP headers. `nil` by default.
- returns: The created request.
*/
public func request(
method: Method,
_ URLString: URLStringConvertible,
parameters: [String: AnyObject]? = nil,
encoding: ParameterEncoding = .URL,
headers: [String: String]? = nil)
-> Request
{
return Manager.sharedInstance.request(
method,
URLString,
parameters: parameters,
encoding: encoding,
headers: headers
)
}
/**
Creates a request using the shared manager instance for the specified URL request.
If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
- parameter URLRequest: The URL request
- returns: The created request.
*/
public func request(URLRequest: URLRequestConvertible) -> Request {
return Manager.sharedInstance.request(URLRequest.URLRequest)
}
// MARK: - Upload Methods
// MARK: File
/**
Creates an upload request using the shared manager instance for the specified method, URL string, and file.
- parameter method: The HTTP method.
- parameter URLString: The URL string.
- parameter headers: The HTTP headers. `nil` by default.
- parameter file: The file to upload.
- returns: The created upload request.
*/
public func upload(
method: Method,
_ URLString: URLStringConvertible,
headers: [String: String]? = nil,
file: NSURL)
-> Request
{
return Manager.sharedInstance.upload(method, URLString, headers: headers, file: file)
}
/**
Creates an upload request using the shared manager instance for the specified URL request and file.
- parameter URLRequest: The URL request.
- parameter file: The file to upload.
- returns: The created upload request.
*/
public func upload(URLRequest: URLRequestConvertible, file: NSURL) -> Request {
return Manager.sharedInstance.upload(URLRequest, file: file)
}
// MARK: Data
/**
Creates an upload request using the shared manager instance for the specified method, URL string, and data.
- parameter method: The HTTP method.
- parameter URLString: The URL string.
- parameter headers: The HTTP headers. `nil` by default.
- parameter data: The data to upload.
- returns: The created upload request.
*/
public func upload(
method: Method,
_ URLString: URLStringConvertible,
headers: [String: String]? = nil,
data: NSData)
-> Request
{
return Manager.sharedInstance.upload(method, URLString, headers: headers, data: data)
}
/**
Creates an upload request using the shared manager instance for the specified URL request and data.
- parameter URLRequest: The URL request.
- parameter data: The data to upload.
- returns: The created upload request.
*/
public func upload(URLRequest: URLRequestConvertible, data: NSData) -> Request {
return Manager.sharedInstance.upload(URLRequest, data: data)
}
// MARK: Stream
/**
Creates an upload request using the shared manager instance for the specified method, URL string, and stream.
- parameter method: The HTTP method.
- parameter URLString: The URL string.
- parameter headers: The HTTP headers. `nil` by default.
- parameter stream: The stream to upload.
- returns: The created upload request.
*/
public func upload(
method: Method,
_ URLString: URLStringConvertible,
headers: [String: String]? = nil,
stream: NSInputStream)
-> Request
{
return Manager.sharedInstance.upload(method, URLString, headers: headers, stream: stream)
}
/**
Creates an upload request using the shared manager instance for the specified URL request and stream.
- parameter URLRequest: The URL request.
- parameter stream: The stream to upload.
- returns: The created upload request.
*/
public func upload(URLRequest: URLRequestConvertible, stream: NSInputStream) -> Request {
return Manager.sharedInstance.upload(URLRequest, stream: stream)
}
// MARK: MultipartFormData
/**
Creates an upload request using the shared manager instance for the specified method and URL string.
- parameter method: The HTTP method.
- parameter URLString: The URL string.
- parameter headers: The HTTP headers. `nil` by default.
- parameter multipartFormData: The closure used to append body parts to the `MultipartFormData`.
- parameter encodingMemoryThreshold: The encoding memory threshold in bytes.
`MultipartFormDataEncodingMemoryThreshold` by default.
- parameter encodingCompletion: The closure called when the `MultipartFormData` encoding is complete.
*/
public func upload(
method: Method,
_ URLString: URLStringConvertible,
headers: [String: String]? = nil,
multipartFormData: MultipartFormData -> Void,
encodingMemoryThreshold: UInt64 = Manager.MultipartFormDataEncodingMemoryThreshold,
encodingCompletion: (Manager.MultipartFormDataEncodingResult -> Void)?)
{
return Manager.sharedInstance.upload(
method,
URLString,
headers: headers,
multipartFormData: multipartFormData,
encodingMemoryThreshold: encodingMemoryThreshold,
encodingCompletion: encodingCompletion
)
}
/**
Creates an upload request using the shared manager instance for the specified method and URL string.
- parameter URLRequest: The URL request.
- parameter multipartFormData: The closure used to append body parts to the `MultipartFormData`.
- parameter encodingMemoryThreshold: The encoding memory threshold in bytes.
`MultipartFormDataEncodingMemoryThreshold` by default.
- parameter encodingCompletion: The closure called when the `MultipartFormData` encoding is complete.
*/
public func upload(
URLRequest: URLRequestConvertible,
multipartFormData: MultipartFormData -> Void,
encodingMemoryThreshold: UInt64 = Manager.MultipartFormDataEncodingMemoryThreshold,
encodingCompletion: (Manager.MultipartFormDataEncodingResult -> Void)?)
{
return Manager.sharedInstance.upload(
URLRequest,
multipartFormData: multipartFormData,
encodingMemoryThreshold: encodingMemoryThreshold,
encodingCompletion: encodingCompletion
)
}
// MARK: - Download Methods
// MARK: URL Request
/**
Creates a download request using the shared manager instance for the specified method and URL string.
- parameter method: The HTTP method.
- parameter URLString: The URL string.
- parameter parameters: The parameters. `nil` by default.
- parameter encoding: The parameter encoding. `.URL` by default.
- parameter headers: The HTTP headers. `nil` by default.
- parameter destination: The closure used to determine the destination of the downloaded file.
- returns: The created download request.
*/
public func download(
method: Method,
_ URLString: URLStringConvertible,
parameters: [String: AnyObject]? = nil,
encoding: ParameterEncoding = .URL,
headers: [String: String]? = nil,
destination: Request.DownloadFileDestination)
-> Request
{
return Manager.sharedInstance.download(
method,
URLString,
parameters: parameters,
encoding: encoding,
headers: headers,
destination: destination
)
}
/**
Creates a download request using the shared manager instance for the specified URL request.
- parameter URLRequest: The URL request.
- parameter destination: The closure used to determine the destination of the downloaded file.
- returns: The created download request.
*/
public func download(URLRequest: URLRequestConvertible, destination: Request.DownloadFileDestination) -> Request {
return Manager.sharedInstance.download(URLRequest, destination: destination)
}
// MARK: Resume Data
/**
Creates a request using the shared manager instance for downloading from the resume data produced from a
previous request cancellation.
- parameter resumeData: The resume data. This is an opaque data blob produced by `NSURLSessionDownloadTask`
when a task is cancelled. See `NSURLSession -downloadTaskWithResumeData:` for additional
information.
- parameter destination: The closure used to determine the destination of the downloaded file.
- returns: The created download request.
*/
public func download(resumeData data: NSData, destination: Request.DownloadFileDestination) -> Request {
return Manager.sharedInstance.download(data, destination: destination)
}

248
Pods/Alamofire/Source/Download.swift generated Normal file
View File

@ -0,0 +1,248 @@
//
// Download.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
extension Manager {
private enum Downloadable {
case Request(NSURLRequest)
case ResumeData(NSData)
}
private func download(downloadable: Downloadable, destination: Request.DownloadFileDestination) -> Request {
var downloadTask: NSURLSessionDownloadTask!
switch downloadable {
case .Request(let request):
dispatch_sync(queue) {
downloadTask = self.session.downloadTaskWithRequest(request)
}
case .ResumeData(let resumeData):
dispatch_sync(queue) {
downloadTask = self.session.downloadTaskWithResumeData(resumeData)
}
}
let request = Request(session: session, task: downloadTask)
if let downloadDelegate = request.delegate as? Request.DownloadTaskDelegate {
downloadDelegate.downloadTaskDidFinishDownloadingToURL = { session, downloadTask, URL in
return destination(URL, downloadTask.response as! NSHTTPURLResponse)
}
}
delegate[request.delegate.task] = request.delegate
if startRequestsImmediately {
request.resume()
}
return request
}
// MARK: Request
/**
Creates a download request for the specified method, URL string, parameters, parameter encoding, headers
and destination.
If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
- parameter method: The HTTP method.
- parameter URLString: The URL string.
- parameter parameters: The parameters. `nil` by default.
- parameter encoding: The parameter encoding. `.URL` by default.
- parameter headers: The HTTP headers. `nil` by default.
- parameter destination: The closure used to determine the destination of the downloaded file.
- returns: The created download request.
*/
public func download(
method: Method,
_ URLString: URLStringConvertible,
parameters: [String: AnyObject]? = nil,
encoding: ParameterEncoding = .URL,
headers: [String: String]? = nil,
destination: Request.DownloadFileDestination)
-> Request
{
let mutableURLRequest = URLRequest(method, URLString, headers: headers)
let encodedURLRequest = encoding.encode(mutableURLRequest, parameters: parameters).0
return download(encodedURLRequest, destination: destination)
}
/**
Creates a request for downloading from the specified URL request.
If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
- parameter URLRequest: The URL request
- parameter destination: The closure used to determine the destination of the downloaded file.
- returns: The created download request.
*/
public func download(URLRequest: URLRequestConvertible, destination: Request.DownloadFileDestination) -> Request {
return download(.Request(URLRequest.URLRequest), destination: destination)
}
// MARK: Resume Data
/**
Creates a request for downloading from the resume data produced from a previous request cancellation.
If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
- parameter resumeData: The resume data. This is an opaque data blob produced by `NSURLSessionDownloadTask`
when a task is cancelled. See `NSURLSession -downloadTaskWithResumeData:` for
additional information.
- parameter destination: The closure used to determine the destination of the downloaded file.
- returns: The created download request.
*/
public func download(resumeData: NSData, destination: Request.DownloadFileDestination) -> Request {
return download(.ResumeData(resumeData), destination: destination)
}
}
// MARK: -
extension Request {
/**
A closure executed once a request has successfully completed in order to determine where to move the temporary
file written to during the download process. The closure takes two arguments: the temporary file URL and the URL
response, and returns a single argument: the file URL where the temporary file should be moved.
*/
public typealias DownloadFileDestination = (NSURL, NSHTTPURLResponse) -> NSURL
/**
Creates a download file destination closure which uses the default file manager to move the temporary file to a
file URL in the first available directory with the specified search path directory and search path domain mask.
- parameter directory: The search path directory. `.DocumentDirectory` by default.
- parameter domain: The search path domain mask. `.UserDomainMask` by default.
- returns: A download file destination closure.
*/
public class func suggestedDownloadDestination(
directory directory: NSSearchPathDirectory = .DocumentDirectory,
domain: NSSearchPathDomainMask = .UserDomainMask)
-> DownloadFileDestination
{
return { temporaryURL, response -> NSURL in
let directoryURLs = NSFileManager.defaultManager().URLsForDirectory(directory, inDomains: domain)
if !directoryURLs.isEmpty {
return directoryURLs[0].URLByAppendingPathComponent(response.suggestedFilename!)
}
return temporaryURL
}
}
/// The resume data of the underlying download task if available after a failure.
public var resumeData: NSData? {
var data: NSData?
if let delegate = delegate as? DownloadTaskDelegate {
data = delegate.resumeData
}
return data
}
// MARK: - DownloadTaskDelegate
class DownloadTaskDelegate: TaskDelegate, NSURLSessionDownloadDelegate {
var downloadTask: NSURLSessionDownloadTask? { return task as? NSURLSessionDownloadTask }
var downloadProgress: ((Int64, Int64, Int64) -> Void)?
var resumeData: NSData?
override var data: NSData? { return resumeData }
// MARK: - NSURLSessionDownloadDelegate
// MARK: Override Closures
var downloadTaskDidFinishDownloadingToURL: ((NSURLSession, NSURLSessionDownloadTask, NSURL) -> NSURL)?
var downloadTaskDidWriteData: ((NSURLSession, NSURLSessionDownloadTask, Int64, Int64, Int64) -> Void)?
var downloadTaskDidResumeAtOffset: ((NSURLSession, NSURLSessionDownloadTask, Int64, Int64) -> Void)?
// MARK: Delegate Methods
func URLSession(
session: NSURLSession,
downloadTask: NSURLSessionDownloadTask,
didFinishDownloadingToURL location: NSURL)
{
if let downloadTaskDidFinishDownloadingToURL = downloadTaskDidFinishDownloadingToURL {
do {
let destination = downloadTaskDidFinishDownloadingToURL(session, downloadTask, location)
try NSFileManager.defaultManager().moveItemAtURL(location, toURL: destination)
} catch {
self.error = error as NSError
}
}
}
func URLSession(
session: NSURLSession,
downloadTask: NSURLSessionDownloadTask,
didWriteData bytesWritten: Int64,
totalBytesWritten: Int64,
totalBytesExpectedToWrite: Int64)
{
if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
if let downloadTaskDidWriteData = downloadTaskDidWriteData {
downloadTaskDidWriteData(
session,
downloadTask,
bytesWritten,
totalBytesWritten,
totalBytesExpectedToWrite
)
} else {
progress.totalUnitCount = totalBytesExpectedToWrite
progress.completedUnitCount = totalBytesWritten
downloadProgress?(bytesWritten, totalBytesWritten, totalBytesExpectedToWrite)
}
}
func URLSession(
session: NSURLSession,
downloadTask: NSURLSessionDownloadTask,
didResumeAtOffset fileOffset: Int64,
expectedTotalBytes: Int64)
{
if let downloadTaskDidResumeAtOffset = downloadTaskDidResumeAtOffset {
downloadTaskDidResumeAtOffset(session, downloadTask, fileOffset, expectedTotalBytes)
} else {
progress.totalUnitCount = expectedTotalBytes
progress.completedUnitCount = fileOffset
}
}
}
}

88
Pods/Alamofire/Source/Error.swift generated Normal file
View File

@ -0,0 +1,88 @@
//
// Error.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// The `Error` struct provides a convenience for creating custom Alamofire NSErrors.
public struct Error {
/// The domain used for creating all Alamofire errors.
public static let Domain = "com.alamofire.error"
/// The custom error codes generated by Alamofire.
public enum Code: Int {
case InputStreamReadFailed = -6000
case OutputStreamWriteFailed = -6001
case ContentTypeValidationFailed = -6002
case StatusCodeValidationFailed = -6003
case DataSerializationFailed = -6004
case StringSerializationFailed = -6005
case JSONSerializationFailed = -6006
case PropertyListSerializationFailed = -6007
}
/// Custom keys contained within certain NSError `userInfo` dictionaries generated by Alamofire.
public struct UserInfoKeys {
/// The content type user info key for a `.ContentTypeValidationFailed` error stored as a `String` value.
public static let ContentType = "ContentType"
/// The status code user info key for a `.StatusCodeValidationFailed` error stored as an `Int` value.
public static let StatusCode = "StatusCode"
}
/**
Creates an `NSError` with the given error code and failure reason.
- parameter code: The error code.
- parameter failureReason: The failure reason.
- returns: An `NSError` with the given error code and failure reason.
*/
@available(*, deprecated=3.4.0)
public static func errorWithCode(code: Code, failureReason: String) -> NSError {
return errorWithCode(code.rawValue, failureReason: failureReason)
}
/**
Creates an `NSError` with the given error code and failure reason.
- parameter code: The error code.
- parameter failureReason: The failure reason.
- returns: An `NSError` with the given error code and failure reason.
*/
@available(*, deprecated=3.4.0)
public static func errorWithCode(code: Int, failureReason: String) -> NSError {
let userInfo = [NSLocalizedFailureReasonErrorKey: failureReason]
return NSError(domain: Domain, code: code, userInfo: userInfo)
}
static func error(domain domain: String = Error.Domain, code: Code, failureReason: String) -> NSError {
return error(domain: domain, code: code.rawValue, failureReason: failureReason)
}
static func error(domain domain: String = Error.Domain, code: Int, failureReason: String) -> NSError {
let userInfo = [NSLocalizedFailureReasonErrorKey: failureReason]
return NSError(domain: domain, code: code, userInfo: userInfo)
}
}

755
Pods/Alamofire/Source/Manager.swift generated Normal file
View File

@ -0,0 +1,755 @@
//
// Manager.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/**
Responsible for creating and managing `Request` objects, as well as their underlying `NSURLSession`.
*/
public class Manager {
// MARK: - Properties
/**
A shared instance of `Manager`, used by top-level Alamofire request methods, and suitable for use directly
for any ad hoc requests.
*/
public static let sharedInstance: Manager = {
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.HTTPAdditionalHeaders = Manager.defaultHTTPHeaders
return Manager(configuration: configuration)
}()
/**
Creates default values for the "Accept-Encoding", "Accept-Language" and "User-Agent" headers.
*/
public static let defaultHTTPHeaders: [String: String] = {
// Accept-Encoding HTTP Header; see https://tools.ietf.org/html/rfc7230#section-4.2.3
let acceptEncoding: String = "gzip;q=1.0, compress;q=0.5"
// Accept-Language HTTP Header; see https://tools.ietf.org/html/rfc7231#section-5.3.5
let acceptLanguage = NSLocale.preferredLanguages().prefix(6).enumerate().map { index, languageCode in
let quality = 1.0 - (Double(index) * 0.1)
return "\(languageCode);q=\(quality)"
}.joinWithSeparator(", ")
// User-Agent Header; see https://tools.ietf.org/html/rfc7231#section-5.5.3
let userAgent: String = {
if let info = NSBundle.mainBundle().infoDictionary {
let executable = info[kCFBundleExecutableKey as String] as? String ?? "Unknown"
let bundle = info[kCFBundleIdentifierKey as String] as? String ?? "Unknown"
let version = info[kCFBundleVersionKey as String] as? String ?? "Unknown"
let os = NSProcessInfo.processInfo().operatingSystemVersionString
var mutableUserAgent = NSMutableString(string: "\(executable)/\(bundle) (\(version); OS \(os))") as CFMutableString
let transform = NSString(string: "Any-Latin; Latin-ASCII; [:^ASCII:] Remove") as CFString
if CFStringTransform(mutableUserAgent, UnsafeMutablePointer<CFRange>(nil), transform, false) {
return mutableUserAgent as String
}
}
return "Alamofire"
}()
return [
"Accept-Encoding": acceptEncoding,
"Accept-Language": acceptLanguage,
"User-Agent": userAgent
]
}()
let queue = dispatch_queue_create(nil, DISPATCH_QUEUE_SERIAL)
/// The underlying session.
public let session: NSURLSession
/// The session delegate handling all the task and session delegate callbacks.
public let delegate: SessionDelegate
/// Whether to start requests immediately after being constructed. `true` by default.
public var startRequestsImmediately: Bool = true
/**
The background completion handler closure provided by the UIApplicationDelegate
`application:handleEventsForBackgroundURLSession:completionHandler:` method. By setting the background
completion handler, the SessionDelegate `sessionDidFinishEventsForBackgroundURLSession` closure implementation
will automatically call the handler.
If you need to handle your own events before the handler is called, then you need to override the
SessionDelegate `sessionDidFinishEventsForBackgroundURLSession` and manually call the handler when finished.
`nil` by default.
*/
public var backgroundCompletionHandler: (() -> Void)?
// MARK: - Lifecycle
/**
Initializes the `Manager` instance with the specified configuration, delegate and server trust policy.
- parameter configuration: The configuration used to construct the managed session.
`NSURLSessionConfiguration.defaultSessionConfiguration()` by default.
- parameter delegate: The delegate used when initializing the session. `SessionDelegate()` by
default.
- parameter serverTrustPolicyManager: The server trust policy manager to use for evaluating all server trust
challenges. `nil` by default.
- returns: The new `Manager` instance.
*/
public init(
configuration: NSURLSessionConfiguration = NSURLSessionConfiguration.defaultSessionConfiguration(),
delegate: SessionDelegate = SessionDelegate(),
serverTrustPolicyManager: ServerTrustPolicyManager? = nil)
{
self.delegate = delegate
self.session = NSURLSession(configuration: configuration, delegate: delegate, delegateQueue: nil)
commonInit(serverTrustPolicyManager: serverTrustPolicyManager)
}
/**
Initializes the `Manager` instance with the specified session, delegate and server trust policy.
- parameter session: The URL session.
- parameter delegate: The delegate of the URL session. Must equal the URL session's delegate.
- parameter serverTrustPolicyManager: The server trust policy manager to use for evaluating all server trust
challenges. `nil` by default.
- returns: The new `Manager` instance if the URL session's delegate matches the delegate parameter.
*/
public init?(
session: NSURLSession,
delegate: SessionDelegate,
serverTrustPolicyManager: ServerTrustPolicyManager? = nil)
{
guard delegate === session.delegate else { return nil }
self.delegate = delegate
self.session = session
commonInit(serverTrustPolicyManager: serverTrustPolicyManager)
}
private func commonInit(serverTrustPolicyManager serverTrustPolicyManager: ServerTrustPolicyManager?) {
session.serverTrustPolicyManager = serverTrustPolicyManager
delegate.sessionDidFinishEventsForBackgroundURLSession = { [weak self] session in
guard let strongSelf = self else { return }
dispatch_async(dispatch_get_main_queue()) { strongSelf.backgroundCompletionHandler?() }
}
}
deinit {
session.invalidateAndCancel()
}
// MARK: - Request
/**
Creates a request for the specified method, URL string, parameters, parameter encoding and headers.
- parameter method: The HTTP method.
- parameter URLString: The URL string.
- parameter parameters: The parameters. `nil` by default.
- parameter encoding: The parameter encoding. `.URL` by default.
- parameter headers: The HTTP headers. `nil` by default.
- returns: The created request.
*/
public func request(
method: Method,
_ URLString: URLStringConvertible,
parameters: [String: AnyObject]? = nil,
encoding: ParameterEncoding = .URL,
headers: [String: String]? = nil)
-> Request
{
let mutableURLRequest = URLRequest(method, URLString, headers: headers)
let encodedURLRequest = encoding.encode(mutableURLRequest, parameters: parameters).0
return request(encodedURLRequest)
}
/**
Creates a request for the specified URL request.
If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
- parameter URLRequest: The URL request
- returns: The created request.
*/
public func request(URLRequest: URLRequestConvertible) -> Request {
var dataTask: NSURLSessionDataTask!
dispatch_sync(queue) { dataTask = self.session.dataTaskWithRequest(URLRequest.URLRequest) }
let request = Request(session: session, task: dataTask)
delegate[request.delegate.task] = request.delegate
if startRequestsImmediately {
request.resume()
}
return request
}
// MARK: - SessionDelegate
/**
Responsible for handling all delegate callbacks for the underlying session.
*/
public class SessionDelegate: NSObject, NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate {
private var subdelegates: [Int: Request.TaskDelegate] = [:]
private let subdelegateQueue = dispatch_queue_create(nil, DISPATCH_QUEUE_CONCURRENT)
/// Access the task delegate for the specified task in a thread-safe manner.
public subscript(task: NSURLSessionTask) -> Request.TaskDelegate? {
get {
var subdelegate: Request.TaskDelegate?
dispatch_sync(subdelegateQueue) { subdelegate = self.subdelegates[task.taskIdentifier] }
return subdelegate
}
set {
dispatch_barrier_async(subdelegateQueue) { self.subdelegates[task.taskIdentifier] = newValue }
}
}
/**
Initializes the `SessionDelegate` instance.
- returns: The new `SessionDelegate` instance.
*/
public override init() {
super.init()
}
// MARK: - NSURLSessionDelegate
// MARK: Override Closures
/// Overrides default behavior for NSURLSessionDelegate method `URLSession:didBecomeInvalidWithError:`.
public var sessionDidBecomeInvalidWithError: ((NSURLSession, NSError?) -> Void)?
/// Overrides default behavior for NSURLSessionDelegate method `URLSession:didReceiveChallenge:completionHandler:`.
public var sessionDidReceiveChallenge: ((NSURLSession, NSURLAuthenticationChallenge) -> (NSURLSessionAuthChallengeDisposition, NSURLCredential?))?
/// Overrides all behavior for NSURLSessionDelegate method `URLSession:didReceiveChallenge:completionHandler:` and requires the caller to call the `completionHandler`.
public var sessionDidReceiveChallengeWithCompletion: ((NSURLSession, NSURLAuthenticationChallenge, (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) -> Void)?
/// Overrides default behavior for NSURLSessionDelegate method `URLSessionDidFinishEventsForBackgroundURLSession:`.
public var sessionDidFinishEventsForBackgroundURLSession: ((NSURLSession) -> Void)?
// MARK: Delegate Methods
/**
Tells the delegate that the session has been invalidated.
- parameter session: The session object that was invalidated.
- parameter error: The error that caused invalidation, or nil if the invalidation was explicit.
*/
public func URLSession(session: NSURLSession, didBecomeInvalidWithError error: NSError?) {
sessionDidBecomeInvalidWithError?(session, error)
}
/**
Requests credentials from the delegate in response to a session-level authentication request from the remote server.
- parameter session: The session containing the task that requested authentication.
- parameter challenge: An object that contains the request for authentication.
- parameter completionHandler: A handler that your delegate method must call providing the disposition and credential.
*/
public func URLSession(
session: NSURLSession,
didReceiveChallenge challenge: NSURLAuthenticationChallenge,
completionHandler: ((NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void))
{
guard sessionDidReceiveChallengeWithCompletion == nil else {
sessionDidReceiveChallengeWithCompletion?(session, challenge, completionHandler)
return
}
var disposition: NSURLSessionAuthChallengeDisposition = .PerformDefaultHandling
var credential: NSURLCredential?
if let sessionDidReceiveChallenge = sessionDidReceiveChallenge {
(disposition, credential) = sessionDidReceiveChallenge(session, challenge)
} else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
let host = challenge.protectionSpace.host
if let
serverTrustPolicy = session.serverTrustPolicyManager?.serverTrustPolicyForHost(host),
serverTrust = challenge.protectionSpace.serverTrust
{
if serverTrustPolicy.evaluateServerTrust(serverTrust, isValidForHost: host) {
disposition = .UseCredential
credential = NSURLCredential(forTrust: serverTrust)
} else {
disposition = .CancelAuthenticationChallenge
}
}
}
completionHandler(disposition, credential)
}
/**
Tells the delegate that all messages enqueued for a session have been delivered.
- parameter session: The session that no longer has any outstanding requests.
*/
public func URLSessionDidFinishEventsForBackgroundURLSession(session: NSURLSession) {
sessionDidFinishEventsForBackgroundURLSession?(session)
}
// MARK: - NSURLSessionTaskDelegate
// MARK: Override Closures
/// Overrides default behavior for NSURLSessionTaskDelegate method `URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:`.
public var taskWillPerformHTTPRedirection: ((NSURLSession, NSURLSessionTask, NSHTTPURLResponse, NSURLRequest) -> NSURLRequest?)?
/// Overrides all behavior for NSURLSessionTaskDelegate method `URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:` and
/// requires the caller to call the `completionHandler`.
public var taskWillPerformHTTPRedirectionWithCompletion: ((NSURLSession, NSURLSessionTask, NSHTTPURLResponse, NSURLRequest, NSURLRequest? -> Void) -> Void)?
/// Overrides default behavior for NSURLSessionTaskDelegate method `URLSession:task:didReceiveChallenge:completionHandler:`.
public var taskDidReceiveChallenge: ((NSURLSession, NSURLSessionTask, NSURLAuthenticationChallenge) -> (NSURLSessionAuthChallengeDisposition, NSURLCredential?))?
/// Overrides all behavior for NSURLSessionTaskDelegate method `URLSession:task:didReceiveChallenge:completionHandler:` and
/// requires the caller to call the `completionHandler`.
public var taskDidReceiveChallengeWithCompletion: ((NSURLSession, NSURLSessionTask, NSURLAuthenticationChallenge, (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) -> Void)?
/// Overrides default behavior for NSURLSessionTaskDelegate method `URLSession:session:task:needNewBodyStream:`.
public var taskNeedNewBodyStream: ((NSURLSession, NSURLSessionTask) -> NSInputStream?)?
/// Overrides all behavior for NSURLSessionTaskDelegate method `URLSession:session:task:needNewBodyStream:` and
/// requires the caller to call the `completionHandler`.
public var taskNeedNewBodyStreamWithCompletion: ((NSURLSession, NSURLSessionTask, NSInputStream? -> Void) -> Void)?
/// Overrides default behavior for NSURLSessionTaskDelegate method `URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:`.
public var taskDidSendBodyData: ((NSURLSession, NSURLSessionTask, Int64, Int64, Int64) -> Void)?
/// Overrides default behavior for NSURLSessionTaskDelegate method `URLSession:task:didCompleteWithError:`.
public var taskDidComplete: ((NSURLSession, NSURLSessionTask, NSError?) -> Void)?
// MARK: Delegate Methods
/**
Tells the delegate that the remote server requested an HTTP redirect.
- parameter session: The session containing the task whose request resulted in a redirect.
- parameter task: The task whose request resulted in a redirect.
- parameter response: An object containing the servers response to the original request.
- parameter request: A URL request object filled out with the new location.
- parameter completionHandler: A closure that your handler should call with either the value of the request
parameter, a modified URL request object, or NULL to refuse the redirect and
return the body of the redirect response.
*/
public func URLSession(
session: NSURLSession,
task: NSURLSessionTask,
willPerformHTTPRedirection response: NSHTTPURLResponse,
newRequest request: NSURLRequest,
completionHandler: NSURLRequest? -> Void)
{
guard taskWillPerformHTTPRedirectionWithCompletion == nil else {
taskWillPerformHTTPRedirectionWithCompletion?(session, task, response, request, completionHandler)
return
}
var redirectRequest: NSURLRequest? = request
if let taskWillPerformHTTPRedirection = taskWillPerformHTTPRedirection {
redirectRequest = taskWillPerformHTTPRedirection(session, task, response, request)
}
completionHandler(redirectRequest)
}
/**
Requests credentials from the delegate in response to an authentication request from the remote server.
- parameter session: The session containing the task whose request requires authentication.
- parameter task: The task whose request requires authentication.
- parameter challenge: An object that contains the request for authentication.
- parameter completionHandler: A handler that your delegate method must call providing the disposition and credential.
*/
public func URLSession(
session: NSURLSession,
task: NSURLSessionTask,
didReceiveChallenge challenge: NSURLAuthenticationChallenge,
completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void)
{
guard taskDidReceiveChallengeWithCompletion == nil else {
taskDidReceiveChallengeWithCompletion?(session, task, challenge, completionHandler)
return
}
if let taskDidReceiveChallenge = taskDidReceiveChallenge {
let result = taskDidReceiveChallenge(session, task, challenge)
completionHandler(result.0, result.1)
} else if let delegate = self[task] {
delegate.URLSession(
session,
task: task,
didReceiveChallenge: challenge,
completionHandler: completionHandler
)
} else {
URLSession(session, didReceiveChallenge: challenge, completionHandler: completionHandler)
}
}
/**
Tells the delegate when a task requires a new request body stream to send to the remote server.
- parameter session: The session containing the task that needs a new body stream.
- parameter task: The task that needs a new body stream.
- parameter completionHandler: A completion handler that your delegate method should call with the new body stream.
*/
public func URLSession(
session: NSURLSession,
task: NSURLSessionTask,
needNewBodyStream completionHandler: NSInputStream? -> Void)
{
guard taskNeedNewBodyStreamWithCompletion == nil else {
taskNeedNewBodyStreamWithCompletion?(session, task, completionHandler)
return
}
if let taskNeedNewBodyStream = taskNeedNewBodyStream {
completionHandler(taskNeedNewBodyStream(session, task))
} else if let delegate = self[task] {
delegate.URLSession(session, task: task, needNewBodyStream: completionHandler)
}
}
/**
Periodically informs the delegate of the progress of sending body content to the server.
- parameter session: The session containing the data task.
- parameter task: The data task.
- parameter bytesSent: The number of bytes sent since the last time this delegate method was called.
- parameter totalBytesSent: The total number of bytes sent so far.
- parameter totalBytesExpectedToSend: The expected length of the body data.
*/
public func URLSession(
session: NSURLSession,
task: NSURLSessionTask,
didSendBodyData bytesSent: Int64,
totalBytesSent: Int64,
totalBytesExpectedToSend: Int64)
{
if let taskDidSendBodyData = taskDidSendBodyData {
taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalBytesExpectedToSend)
} else if let delegate = self[task] as? Request.UploadTaskDelegate {
delegate.URLSession(
session,
task: task,
didSendBodyData: bytesSent,
totalBytesSent: totalBytesSent,
totalBytesExpectedToSend: totalBytesExpectedToSend
)
}
}
/**
Tells the delegate that the task finished transferring data.
- parameter session: The session containing the task whose request finished transferring data.
- parameter task: The task whose request finished transferring data.
- parameter error: If an error occurred, an error object indicating how the transfer failed, otherwise nil.
*/
public func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
if let taskDidComplete = taskDidComplete {
taskDidComplete(session, task, error)
} else if let delegate = self[task] {
delegate.URLSession(session, task: task, didCompleteWithError: error)
}
NSNotificationCenter.defaultCenter().postNotificationName(Notifications.Task.DidComplete, object: task)
self[task] = nil
}
// MARK: - NSURLSessionDataDelegate
// MARK: Override Closures
/// Overrides default behavior for NSURLSessionDataDelegate method `URLSession:dataTask:didReceiveResponse:completionHandler:`.
public var dataTaskDidReceiveResponse: ((NSURLSession, NSURLSessionDataTask, NSURLResponse) -> NSURLSessionResponseDisposition)?
/// Overrides all behavior for NSURLSessionDataDelegate method `URLSession:dataTask:didReceiveResponse:completionHandler:` and
/// requires caller to call the `completionHandler`.
public var dataTaskDidReceiveResponseWithCompletion: ((NSURLSession, NSURLSessionDataTask, NSURLResponse, NSURLSessionResponseDisposition -> Void) -> Void)?
/// Overrides default behavior for NSURLSessionDataDelegate method `URLSession:dataTask:didBecomeDownloadTask:`.
public var dataTaskDidBecomeDownloadTask: ((NSURLSession, NSURLSessionDataTask, NSURLSessionDownloadTask) -> Void)?
/// Overrides default behavior for NSURLSessionDataDelegate method `URLSession:dataTask:didReceiveData:`.
public var dataTaskDidReceiveData: ((NSURLSession, NSURLSessionDataTask, NSData) -> Void)?
/// Overrides default behavior for NSURLSessionDataDelegate method `URLSession:dataTask:willCacheResponse:completionHandler:`.
public var dataTaskWillCacheResponse: ((NSURLSession, NSURLSessionDataTask, NSCachedURLResponse) -> NSCachedURLResponse?)?
/// Overrides all behavior for NSURLSessionDataDelegate method `URLSession:dataTask:willCacheResponse:completionHandler:` and
/// requires caller to call the `completionHandler`.
public var dataTaskWillCacheResponseWithCompletion: ((NSURLSession, NSURLSessionDataTask, NSCachedURLResponse, NSCachedURLResponse? -> Void) -> Void)?
// MARK: Delegate Methods
/**
Tells the delegate that the data task received the initial reply (headers) from the server.
- parameter session: The session containing the data task that received an initial reply.
- parameter dataTask: The data task that received an initial reply.
- parameter response: A URL response object populated with headers.
- parameter completionHandler: A completion handler that your code calls to continue the transfer, passing a
constant to indicate whether the transfer should continue as a data task or
should become a download task.
*/
public func URLSession(
session: NSURLSession,
dataTask: NSURLSessionDataTask,
didReceiveResponse response: NSURLResponse,
completionHandler: NSURLSessionResponseDisposition -> Void)
{
guard dataTaskDidReceiveResponseWithCompletion == nil else {
dataTaskDidReceiveResponseWithCompletion?(session, dataTask, response, completionHandler)
return
}
var disposition: NSURLSessionResponseDisposition = .Allow
if let dataTaskDidReceiveResponse = dataTaskDidReceiveResponse {
disposition = dataTaskDidReceiveResponse(session, dataTask, response)
}
completionHandler(disposition)
}
/**
Tells the delegate that the data task was changed to a download task.
- parameter session: The session containing the task that was replaced by a download task.
- parameter dataTask: The data task that was replaced by a download task.
- parameter downloadTask: The new download task that replaced the data task.
*/
public func URLSession(
session: NSURLSession,
dataTask: NSURLSessionDataTask,
didBecomeDownloadTask downloadTask: NSURLSessionDownloadTask)
{
if let dataTaskDidBecomeDownloadTask = dataTaskDidBecomeDownloadTask {
dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask)
} else {
let downloadDelegate = Request.DownloadTaskDelegate(task: downloadTask)
self[downloadTask] = downloadDelegate
}
}
/**
Tells the delegate that the data task has received some of the expected data.
- parameter session: The session containing the data task that provided data.
- parameter dataTask: The data task that provided data.
- parameter data: A data object containing the transferred data.
*/
public func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
if let dataTaskDidReceiveData = dataTaskDidReceiveData {
dataTaskDidReceiveData(session, dataTask, data)
} else if let delegate = self[dataTask] as? Request.DataTaskDelegate {
delegate.URLSession(session, dataTask: dataTask, didReceiveData: data)
}
}
/**
Asks the delegate whether the data (or upload) task should store the response in the cache.
- parameter session: The session containing the data (or upload) task.
- parameter dataTask: The data (or upload) task.
- parameter proposedResponse: The default caching behavior. This behavior is determined based on the current
caching policy and the values of certain received headers, such as the Pragma
and Cache-Control headers.
- parameter completionHandler: A block that your handler must call, providing either the original proposed
response, a modified version of that response, or NULL to prevent caching the
response. If your delegate implements this method, it must call this completion
handler; otherwise, your app leaks memory.
*/
public func URLSession(
session: NSURLSession,
dataTask: NSURLSessionDataTask,
willCacheResponse proposedResponse: NSCachedURLResponse,
completionHandler: NSCachedURLResponse? -> Void)
{
guard dataTaskWillCacheResponseWithCompletion == nil else {
dataTaskWillCacheResponseWithCompletion?(session, dataTask, proposedResponse, completionHandler)
return
}
if let dataTaskWillCacheResponse = dataTaskWillCacheResponse {
completionHandler(dataTaskWillCacheResponse(session, dataTask, proposedResponse))
} else if let delegate = self[dataTask] as? Request.DataTaskDelegate {
delegate.URLSession(
session,
dataTask: dataTask,
willCacheResponse: proposedResponse,
completionHandler: completionHandler
)
} else {
completionHandler(proposedResponse)
}
}
// MARK: - NSURLSessionDownloadDelegate
// MARK: Override Closures
/// Overrides default behavior for NSURLSessionDownloadDelegate method `URLSession:downloadTask:didFinishDownloadingToURL:`.
public var downloadTaskDidFinishDownloadingToURL: ((NSURLSession, NSURLSessionDownloadTask, NSURL) -> Void)?
/// Overrides default behavior for NSURLSessionDownloadDelegate method `URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:`.
public var downloadTaskDidWriteData: ((NSURLSession, NSURLSessionDownloadTask, Int64, Int64, Int64) -> Void)?
/// Overrides default behavior for NSURLSessionDownloadDelegate method `URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:`.
public var downloadTaskDidResumeAtOffset: ((NSURLSession, NSURLSessionDownloadTask, Int64, Int64) -> Void)?
// MARK: Delegate Methods
/**
Tells the delegate that a download task has finished downloading.
- parameter session: The session containing the download task that finished.
- parameter downloadTask: The download task that finished.
- parameter location: A file URL for the temporary file. Because the file is temporary, you must either
open the file for reading or move it to a permanent location in your apps sandbox
container directory before returning from this delegate method.
*/
public func URLSession(
session: NSURLSession,
downloadTask: NSURLSessionDownloadTask,
didFinishDownloadingToURL location: NSURL)
{
if let downloadTaskDidFinishDownloadingToURL = downloadTaskDidFinishDownloadingToURL {
downloadTaskDidFinishDownloadingToURL(session, downloadTask, location)
} else if let delegate = self[downloadTask] as? Request.DownloadTaskDelegate {
delegate.URLSession(session, downloadTask: downloadTask, didFinishDownloadingToURL: location)
}
}
/**
Periodically informs the delegate about the downloads progress.
- parameter session: The session containing the download task.
- parameter downloadTask: The download task.
- parameter bytesWritten: The number of bytes transferred since the last time this delegate
method was called.
- parameter totalBytesWritten: The total number of bytes transferred so far.
- parameter totalBytesExpectedToWrite: The expected length of the file, as provided by the Content-Length
header. If this header was not provided, the value is
`NSURLSessionTransferSizeUnknown`.
*/
public func URLSession(
session: NSURLSession,
downloadTask: NSURLSessionDownloadTask,
didWriteData bytesWritten: Int64,
totalBytesWritten: Int64,
totalBytesExpectedToWrite: Int64)
{
if let downloadTaskDidWriteData = downloadTaskDidWriteData {
downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite)
} else if let delegate = self[downloadTask] as? Request.DownloadTaskDelegate {
delegate.URLSession(
session,
downloadTask: downloadTask,
didWriteData: bytesWritten,
totalBytesWritten: totalBytesWritten,
totalBytesExpectedToWrite: totalBytesExpectedToWrite
)
}
}
/**
Tells the delegate that the download task has resumed downloading.
- parameter session: The session containing the download task that finished.
- parameter downloadTask: The download task that resumed. See explanation in the discussion.
- parameter fileOffset: If the file's cache policy or last modified date prevents reuse of the
existing content, then this value is zero. Otherwise, this value is an
integer representing the number of bytes on disk that do not need to be
retrieved again.
- parameter expectedTotalBytes: The expected length of the file, as provided by the Content-Length header.
If this header was not provided, the value is NSURLSessionTransferSizeUnknown.
*/
public func URLSession(
session: NSURLSession,
downloadTask: NSURLSessionDownloadTask,
didResumeAtOffset fileOffset: Int64,
expectedTotalBytes: Int64)
{
if let downloadTaskDidResumeAtOffset = downloadTaskDidResumeAtOffset {
downloadTaskDidResumeAtOffset(session, downloadTask, fileOffset, expectedTotalBytes)
} else if let delegate = self[downloadTask] as? Request.DownloadTaskDelegate {
delegate.URLSession(
session,
downloadTask: downloadTask,
didResumeAtOffset: fileOffset,
expectedTotalBytes: expectedTotalBytes
)
}
}
// MARK: - NSURLSessionStreamDelegate
var _streamTaskReadClosed: Any?
var _streamTaskWriteClosed: Any?
var _streamTaskBetterRouteDiscovered: Any?
var _streamTaskDidBecomeInputStream: Any?
// MARK: - NSObject
public override func respondsToSelector(selector: Selector) -> Bool {
#if !os(OSX)
if selector == #selector(NSURLSessionDelegate.URLSessionDidFinishEventsForBackgroundURLSession(_:)) {
return sessionDidFinishEventsForBackgroundURLSession != nil
}
#endif
switch selector {
case #selector(NSURLSessionDelegate.URLSession(_:didBecomeInvalidWithError:)):
return sessionDidBecomeInvalidWithError != nil
case #selector(NSURLSessionDelegate.URLSession(_:didReceiveChallenge:completionHandler:)):
return (sessionDidReceiveChallenge != nil || sessionDidReceiveChallengeWithCompletion != nil)
case #selector(NSURLSessionTaskDelegate.URLSession(_:task:willPerformHTTPRedirection:newRequest:completionHandler:)):
return (taskWillPerformHTTPRedirection != nil || taskWillPerformHTTPRedirectionWithCompletion != nil)
case #selector(NSURLSessionDataDelegate.URLSession(_:dataTask:didReceiveResponse:completionHandler:)):
return (dataTaskDidReceiveResponse != nil || dataTaskDidReceiveResponseWithCompletion != nil)
default:
return self.dynamicType.instancesRespondToSelector(selector)
}
}
}
}

View File

@ -0,0 +1,659 @@
//
// MultipartFormData.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
#if os(iOS) || os(watchOS) || os(tvOS)
import MobileCoreServices
#elseif os(OSX)
import CoreServices
#endif
/**
Constructs `multipart/form-data` for uploads within an HTTP or HTTPS body. There are currently two ways to encode
multipart form data. The first way is to encode the data directly in memory. This is very efficient, but can lead
to memory issues if the dataset is too large. The second way is designed for larger datasets and will write all the
data to a single file on disk with all the proper boundary segmentation. The second approach MUST be used for
larger datasets such as video content, otherwise your app may run out of memory when trying to encode the dataset.
For more information on `multipart/form-data` in general, please refer to the RFC-2388 and RFC-2045 specs as well
and the w3 form documentation.
- https://www.ietf.org/rfc/rfc2388.txt
- https://www.ietf.org/rfc/rfc2045.txt
- https://www.w3.org/TR/html401/interact/forms.html#h-17.13
*/
public class MultipartFormData {
// MARK: - Helper Types
struct EncodingCharacters {
static let CRLF = "\r\n"
}
struct BoundaryGenerator {
enum BoundaryType {
case Initial, Encapsulated, Final
}
static func randomBoundary() -> String {
return String(format: "alamofire.boundary.%08x%08x", arc4random(), arc4random())
}
static func boundaryData(boundaryType boundaryType: BoundaryType, boundary: String) -> NSData {
let boundaryText: String
switch boundaryType {
case .Initial:
boundaryText = "--\(boundary)\(EncodingCharacters.CRLF)"
case .Encapsulated:
boundaryText = "\(EncodingCharacters.CRLF)--\(boundary)\(EncodingCharacters.CRLF)"
case .Final:
boundaryText = "\(EncodingCharacters.CRLF)--\(boundary)--\(EncodingCharacters.CRLF)"
}
return boundaryText.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
}
}
class BodyPart {
let headers: [String: String]
let bodyStream: NSInputStream
let bodyContentLength: UInt64
var hasInitialBoundary = false
var hasFinalBoundary = false
init(headers: [String: String], bodyStream: NSInputStream, bodyContentLength: UInt64) {
self.headers = headers
self.bodyStream = bodyStream
self.bodyContentLength = bodyContentLength
}
}
// MARK: - Properties
/// The `Content-Type` header value containing the boundary used to generate the `multipart/form-data`.
public var contentType: String { return "multipart/form-data; boundary=\(boundary)" }
/// The content length of all body parts used to generate the `multipart/form-data` not including the boundaries.
public var contentLength: UInt64 { return bodyParts.reduce(0) { $0 + $1.bodyContentLength } }
/// The boundary used to separate the body parts in the encoded form data.
public let boundary: String
private var bodyParts: [BodyPart]
private var bodyPartError: NSError?
private let streamBufferSize: Int
// MARK: - Lifecycle
/**
Creates a multipart form data object.
- returns: The multipart form data object.
*/
public init() {
self.boundary = BoundaryGenerator.randomBoundary()
self.bodyParts = []
/**
* The optimal read/write buffer size in bytes for input and output streams is 1024 (1KB). For more
* information, please refer to the following article:
* - https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Streams/Articles/ReadingInputStreams.html
*/
self.streamBufferSize = 1024
}
// MARK: - Body Parts
/**
Creates a body part from the data and appends it to the multipart form data object.
The body part data will be encoded using the following format:
- `Content-Disposition: form-data; name=#{name}` (HTTP Header)
- Encoded data
- Multipart form boundary
- parameter data: The data to encode into the multipart form data.
- parameter name: The name to associate with the data in the `Content-Disposition` HTTP header.
*/
public func appendBodyPart(data data: NSData, name: String) {
let headers = contentHeaders(name: name)
let stream = NSInputStream(data: data)
let length = UInt64(data.length)
appendBodyPart(stream: stream, length: length, headers: headers)
}
/**
Creates a body part from the data and appends it to the multipart form data object.
The body part data will be encoded using the following format:
- `Content-Disposition: form-data; name=#{name}` (HTTP Header)
- `Content-Type: #{generated mimeType}` (HTTP Header)
- Encoded data
- Multipart form boundary
- parameter data: The data to encode into the multipart form data.
- parameter name: The name to associate with the data in the `Content-Disposition` HTTP header.
- parameter mimeType: The MIME type to associate with the data content type in the `Content-Type` HTTP header.
*/
public func appendBodyPart(data data: NSData, name: String, mimeType: String) {
let headers = contentHeaders(name: name, mimeType: mimeType)
let stream = NSInputStream(data: data)
let length = UInt64(data.length)
appendBodyPart(stream: stream, length: length, headers: headers)
}
/**
Creates a body part from the data and appends it to the multipart form data object.
The body part data will be encoded using the following format:
- `Content-Disposition: form-data; name=#{name}; filename=#{filename}` (HTTP Header)
- `Content-Type: #{mimeType}` (HTTP Header)
- Encoded file data
- Multipart form boundary
- parameter data: The data to encode into the multipart form data.
- parameter name: The name to associate with the data in the `Content-Disposition` HTTP header.
- parameter fileName: The filename to associate with the data in the `Content-Disposition` HTTP header.
- parameter mimeType: The MIME type to associate with the data in the `Content-Type` HTTP header.
*/
public func appendBodyPart(data data: NSData, name: String, fileName: String, mimeType: String) {
let headers = contentHeaders(name: name, fileName: fileName, mimeType: mimeType)
let stream = NSInputStream(data: data)
let length = UInt64(data.length)
appendBodyPart(stream: stream, length: length, headers: headers)
}
/**
Creates a body part from the file and appends it to the multipart form data object.
The body part data will be encoded using the following format:
- `Content-Disposition: form-data; name=#{name}; filename=#{generated filename}` (HTTP Header)
- `Content-Type: #{generated mimeType}` (HTTP Header)
- Encoded file data
- Multipart form boundary
The filename in the `Content-Disposition` HTTP header is generated from the last path component of the
`fileURL`. The `Content-Type` HTTP header MIME type is generated by mapping the `fileURL` extension to the
system associated MIME type.
- parameter fileURL: The URL of the file whose content will be encoded into the multipart form data.
- parameter name: The name to associate with the file content in the `Content-Disposition` HTTP header.
*/
public func appendBodyPart(fileURL fileURL: NSURL, name: String) {
if let
fileName = fileURL.lastPathComponent,
pathExtension = fileURL.pathExtension
{
let mimeType = mimeTypeForPathExtension(pathExtension)
appendBodyPart(fileURL: fileURL, name: name, fileName: fileName, mimeType: mimeType)
} else {
let failureReason = "Failed to extract the fileName of the provided URL: \(fileURL)"
setBodyPartError(code: NSURLErrorBadURL, failureReason: failureReason)
}
}
/**
Creates a body part from the file and appends it to the multipart form data object.
The body part data will be encoded using the following format:
- Content-Disposition: form-data; name=#{name}; filename=#{filename} (HTTP Header)
- Content-Type: #{mimeType} (HTTP Header)
- Encoded file data
- Multipart form boundary
- parameter fileURL: The URL of the file whose content will be encoded into the multipart form data.
- parameter name: The name to associate with the file content in the `Content-Disposition` HTTP header.
- parameter fileName: The filename to associate with the file content in the `Content-Disposition` HTTP header.
- parameter mimeType: The MIME type to associate with the file content in the `Content-Type` HTTP header.
*/
public func appendBodyPart(fileURL fileURL: NSURL, name: String, fileName: String, mimeType: String) {
let headers = contentHeaders(name: name, fileName: fileName, mimeType: mimeType)
//============================================================
// Check 1 - is file URL?
//============================================================
guard fileURL.fileURL else {
let failureReason = "The file URL does not point to a file URL: \(fileURL)"
setBodyPartError(code: NSURLErrorBadURL, failureReason: failureReason)
return
}
//============================================================
// Check 2 - is file URL reachable?
//============================================================
var isReachable = true
if #available(OSX 10.10, *) {
isReachable = fileURL.checkPromisedItemIsReachableAndReturnError(nil)
}
guard isReachable else {
setBodyPartError(code: NSURLErrorBadURL, failureReason: "The file URL is not reachable: \(fileURL)")
return
}
//============================================================
// Check 3 - is file URL a directory?
//============================================================
var isDirectory: ObjCBool = false
guard let
path = fileURL.path
where NSFileManager.defaultManager().fileExistsAtPath(path, isDirectory: &isDirectory) && !isDirectory else
{
let failureReason = "The file URL is a directory, not a file: \(fileURL)"
setBodyPartError(code: NSURLErrorBadURL, failureReason: failureReason)
return
}
//============================================================
// Check 4 - can the file size be extracted?
//============================================================
var bodyContentLength: UInt64?
do {
if let
path = fileURL.path,
fileSize = try NSFileManager.defaultManager().attributesOfItemAtPath(path)[NSFileSize] as? NSNumber
{
bodyContentLength = fileSize.unsignedLongLongValue
}
} catch {
// No-op
}
guard let length = bodyContentLength else {
let failureReason = "Could not fetch attributes from the file URL: \(fileURL)"
setBodyPartError(code: NSURLErrorBadURL, failureReason: failureReason)
return
}
//============================================================
// Check 5 - can a stream be created from file URL?
//============================================================
guard let stream = NSInputStream(URL: fileURL) else {
let failureReason = "Failed to create an input stream from the file URL: \(fileURL)"
setBodyPartError(code: NSURLErrorCannotOpenFile, failureReason: failureReason)
return
}
appendBodyPart(stream: stream, length: length, headers: headers)
}
/**
Creates a body part from the stream and appends it to the multipart form data object.
The body part data will be encoded using the following format:
- `Content-Disposition: form-data; name=#{name}; filename=#{filename}` (HTTP Header)
- `Content-Type: #{mimeType}` (HTTP Header)
- Encoded stream data
- Multipart form boundary
- parameter stream: The input stream to encode in the multipart form data.
- parameter length: The content length of the stream.
- parameter name: The name to associate with the stream content in the `Content-Disposition` HTTP header.
- parameter fileName: The filename to associate with the stream content in the `Content-Disposition` HTTP header.
- parameter mimeType: The MIME type to associate with the stream content in the `Content-Type` HTTP header.
*/
public func appendBodyPart(
stream stream: NSInputStream,
length: UInt64,
name: String,
fileName: String,
mimeType: String)
{
let headers = contentHeaders(name: name, fileName: fileName, mimeType: mimeType)
appendBodyPart(stream: stream, length: length, headers: headers)
}
/**
Creates a body part with the headers, stream and length and appends it to the multipart form data object.
The body part data will be encoded using the following format:
- HTTP headers
- Encoded stream data
- Multipart form boundary
- parameter stream: The input stream to encode in the multipart form data.
- parameter length: The content length of the stream.
- parameter headers: The HTTP headers for the body part.
*/
public func appendBodyPart(stream stream: NSInputStream, length: UInt64, headers: [String: String]) {
let bodyPart = BodyPart(headers: headers, bodyStream: stream, bodyContentLength: length)
bodyParts.append(bodyPart)
}
// MARK: - Data Encoding
/**
Encodes all the appended body parts into a single `NSData` object.
It is important to note that this method will load all the appended body parts into memory all at the same
time. This method should only be used when the encoded data will have a small memory footprint. For large data
cases, please use the `writeEncodedDataToDisk(fileURL:completionHandler:)` method.
- throws: An `NSError` if encoding encounters an error.
- returns: The encoded `NSData` if encoding is successful.
*/
public func encode() throws -> NSData {
if let bodyPartError = bodyPartError {
throw bodyPartError
}
let encoded = NSMutableData()
bodyParts.first?.hasInitialBoundary = true
bodyParts.last?.hasFinalBoundary = true
for bodyPart in bodyParts {
let encodedData = try encodeBodyPart(bodyPart)
encoded.appendData(encodedData)
}
return encoded
}
/**
Writes the appended body parts into the given file URL.
This process is facilitated by reading and writing with input and output streams, respectively. Thus,
this approach is very memory efficient and should be used for large body part data.
- parameter fileURL: The file URL to write the multipart form data into.
- throws: An `NSError` if encoding encounters an error.
*/
public func writeEncodedDataToDisk(fileURL: NSURL) throws {
if let bodyPartError = bodyPartError {
throw bodyPartError
}
if let path = fileURL.path where NSFileManager.defaultManager().fileExistsAtPath(path) {
let failureReason = "A file already exists at the given file URL: \(fileURL)"
throw Error.error(domain: NSURLErrorDomain, code: NSURLErrorBadURL, failureReason: failureReason)
} else if !fileURL.fileURL {
let failureReason = "The URL does not point to a valid file: \(fileURL)"
throw Error.error(domain: NSURLErrorDomain, code: NSURLErrorBadURL, failureReason: failureReason)
}
let outputStream: NSOutputStream
if let possibleOutputStream = NSOutputStream(URL: fileURL, append: false) {
outputStream = possibleOutputStream
} else {
let failureReason = "Failed to create an output stream with the given URL: \(fileURL)"
throw Error.error(domain: NSURLErrorDomain, code: NSURLErrorCannotOpenFile, failureReason: failureReason)
}
outputStream.open()
self.bodyParts.first?.hasInitialBoundary = true
self.bodyParts.last?.hasFinalBoundary = true
for bodyPart in self.bodyParts {
try writeBodyPart(bodyPart, toOutputStream: outputStream)
}
outputStream.close()
}
// MARK: - Private - Body Part Encoding
private func encodeBodyPart(bodyPart: BodyPart) throws -> NSData {
let encoded = NSMutableData()
let initialData = bodyPart.hasInitialBoundary ? initialBoundaryData() : encapsulatedBoundaryData()
encoded.appendData(initialData)
let headerData = encodeHeaderDataForBodyPart(bodyPart)
encoded.appendData(headerData)
let bodyStreamData = try encodeBodyStreamDataForBodyPart(bodyPart)
encoded.appendData(bodyStreamData)
if bodyPart.hasFinalBoundary {
encoded.appendData(finalBoundaryData())
}
return encoded
}
private func encodeHeaderDataForBodyPart(bodyPart: BodyPart) -> NSData {
var headerText = ""
for (key, value) in bodyPart.headers {
headerText += "\(key): \(value)\(EncodingCharacters.CRLF)"
}
headerText += EncodingCharacters.CRLF
return headerText.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!
}
private func encodeBodyStreamDataForBodyPart(bodyPart: BodyPart) throws -> NSData {
let inputStream = bodyPart.bodyStream
inputStream.open()
var error: NSError?
let encoded = NSMutableData()
while inputStream.hasBytesAvailable {
var buffer = [UInt8](count: streamBufferSize, repeatedValue: 0)
let bytesRead = inputStream.read(&buffer, maxLength: streamBufferSize)
if inputStream.streamError != nil {
error = inputStream.streamError
break
}
if bytesRead > 0 {
encoded.appendBytes(buffer, length: bytesRead)
} else if bytesRead < 0 {
let failureReason = "Failed to read from input stream: \(inputStream)"
error = Error.error(domain: NSURLErrorDomain, code: .InputStreamReadFailed, failureReason: failureReason)
break
} else {
break
}
}
inputStream.close()
if let error = error {
throw error
}
return encoded
}
// MARK: - Private - Writing Body Part to Output Stream
private func writeBodyPart(bodyPart: BodyPart, toOutputStream outputStream: NSOutputStream) throws {
try writeInitialBoundaryDataForBodyPart(bodyPart, toOutputStream: outputStream)
try writeHeaderDataForBodyPart(bodyPart, toOutputStream: outputStream)
try writeBodyStreamForBodyPart(bodyPart, toOutputStream: outputStream)
try writeFinalBoundaryDataForBodyPart(bodyPart, toOutputStream: outputStream)
}
private func writeInitialBoundaryDataForBodyPart(
bodyPart: BodyPart,
toOutputStream outputStream: NSOutputStream)
throws
{
let initialData = bodyPart.hasInitialBoundary ? initialBoundaryData() : encapsulatedBoundaryData()
return try writeData(initialData, toOutputStream: outputStream)
}
private func writeHeaderDataForBodyPart(bodyPart: BodyPart, toOutputStream outputStream: NSOutputStream) throws {
let headerData = encodeHeaderDataForBodyPart(bodyPart)
return try writeData(headerData, toOutputStream: outputStream)
}
private func writeBodyStreamForBodyPart(bodyPart: BodyPart, toOutputStream outputStream: NSOutputStream) throws {
let inputStream = bodyPart.bodyStream
inputStream.open()
while inputStream.hasBytesAvailable {
var buffer = [UInt8](count: streamBufferSize, repeatedValue: 0)
let bytesRead = inputStream.read(&buffer, maxLength: streamBufferSize)
if let streamError = inputStream.streamError {
throw streamError
}
if bytesRead > 0 {
if buffer.count != bytesRead {
buffer = Array(buffer[0..<bytesRead])
}
try writeBuffer(&buffer, toOutputStream: outputStream)
} else if bytesRead < 0 {
let failureReason = "Failed to read from input stream: \(inputStream)"
throw Error.error(domain: NSURLErrorDomain, code: .InputStreamReadFailed, failureReason: failureReason)
} else {
break
}
}
inputStream.close()
}
private func writeFinalBoundaryDataForBodyPart(
bodyPart: BodyPart,
toOutputStream outputStream: NSOutputStream)
throws
{
if bodyPart.hasFinalBoundary {
return try writeData(finalBoundaryData(), toOutputStream: outputStream)
}
}
// MARK: - Private - Writing Buffered Data to Output Stream
private func writeData(data: NSData, toOutputStream outputStream: NSOutputStream) throws {
var buffer = [UInt8](count: data.length, repeatedValue: 0)
data.getBytes(&buffer, length: data.length)
return try writeBuffer(&buffer, toOutputStream: outputStream)
}
private func writeBuffer(inout buffer: [UInt8], toOutputStream outputStream: NSOutputStream) throws {
var bytesToWrite = buffer.count
while bytesToWrite > 0 {
if outputStream.hasSpaceAvailable {
let bytesWritten = outputStream.write(buffer, maxLength: bytesToWrite)
if let streamError = outputStream.streamError {
throw streamError
}
if bytesWritten < 0 {
let failureReason = "Failed to write to output stream: \(outputStream)"
throw Error.error(domain: NSURLErrorDomain, code: .OutputStreamWriteFailed, failureReason: failureReason)
}
bytesToWrite -= bytesWritten
if bytesToWrite > 0 {
buffer = Array(buffer[bytesWritten..<buffer.count])
}
} else if let streamError = outputStream.streamError {
throw streamError
}
}
}
// MARK: - Private - Mime Type
private func mimeTypeForPathExtension(pathExtension: String) -> String {
if let
id = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension, nil)?.takeRetainedValue(),
contentType = UTTypeCopyPreferredTagWithClass(id, kUTTagClassMIMEType)?.takeRetainedValue()
{
return contentType as String
}
return "application/octet-stream"
}
// MARK: - Private - Content Headers
private func contentHeaders(name name: String) -> [String: String] {
return ["Content-Disposition": "form-data; name=\"\(name)\""]
}
private func contentHeaders(name name: String, mimeType: String) -> [String: String] {
return [
"Content-Disposition": "form-data; name=\"\(name)\"",
"Content-Type": "\(mimeType)"
]
}
private func contentHeaders(name name: String, fileName: String, mimeType: String) -> [String: String] {
return [
"Content-Disposition": "form-data; name=\"\(name)\"; filename=\"\(fileName)\"",
"Content-Type": "\(mimeType)"
]
}
// MARK: - Private - Boundary Encoding
private func initialBoundaryData() -> NSData {
return BoundaryGenerator.boundaryData(boundaryType: .Initial, boundary: boundary)
}
private func encapsulatedBoundaryData() -> NSData {
return BoundaryGenerator.boundaryData(boundaryType: .Encapsulated, boundary: boundary)
}
private func finalBoundaryData() -> NSData {
return BoundaryGenerator.boundaryData(boundaryType: .Final, boundary: boundary)
}
// MARK: - Private - Errors
private func setBodyPartError(code code: Int, failureReason: String) {
guard bodyPartError == nil else { return }
bodyPartError = Error.error(domain: NSURLErrorDomain, code: code, failureReason: failureReason)
}
}

View File

@ -0,0 +1,253 @@
//
// NetworkReachabilityManager.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
#if !os(watchOS)
import Foundation
import SystemConfiguration
/**
The `NetworkReachabilityManager` class listens for reachability changes of hosts and addresses for both WWAN and
WiFi network interfaces.
Reachability can be used to determine background information about why a network operation failed, or to retry
network requests when a connection is established. It should not be used to prevent a user from initiating a network
request, as it's possible that an initial request may be required to establish reachability.
*/
public class NetworkReachabilityManager {
/**
Defines the various states of network reachability.
- Unknown: It is unknown whether the network is reachable.
- NotReachable: The network is not reachable.
- ReachableOnWWAN: The network is reachable over the WWAN connection.
- ReachableOnWiFi: The network is reachable over the WiFi connection.
*/
public enum NetworkReachabilityStatus {
case Unknown
case NotReachable
case Reachable(ConnectionType)
}
/**
Defines the various connection types detected by reachability flags.
- EthernetOrWiFi: The connection type is either over Ethernet or WiFi.
- WWAN: The connection type is a WWAN connection.
*/
public enum ConnectionType {
case EthernetOrWiFi
case WWAN
}
/// A closure executed when the network reachability status changes. The closure takes a single argument: the
/// network reachability status.
public typealias Listener = NetworkReachabilityStatus -> Void
// MARK: - Properties
/// Whether the network is currently reachable.
public var isReachable: Bool { return isReachableOnWWAN || isReachableOnEthernetOrWiFi }
/// Whether the network is currently reachable over the WWAN interface.
public var isReachableOnWWAN: Bool { return networkReachabilityStatus == .Reachable(.WWAN) }
/// Whether the network is currently reachable over Ethernet or WiFi interface.
public var isReachableOnEthernetOrWiFi: Bool { return networkReachabilityStatus == .Reachable(.EthernetOrWiFi) }
/// The current network reachability status.
public var networkReachabilityStatus: NetworkReachabilityStatus {
guard let flags = self.flags else { return .Unknown }
return networkReachabilityStatusForFlags(flags)
}
/// The dispatch queue to execute the `listener` closure on.
public var listenerQueue: dispatch_queue_t = dispatch_get_main_queue()
/// A closure executed when the network reachability status changes.
public var listener: Listener?
private var flags: SCNetworkReachabilityFlags? {
var flags = SCNetworkReachabilityFlags()
if SCNetworkReachabilityGetFlags(reachability, &flags) {
return flags
}
return nil
}
private let reachability: SCNetworkReachability
private var previousFlags: SCNetworkReachabilityFlags
// MARK: - Initialization
/**
Creates a `NetworkReachabilityManager` instance with the specified host.
- parameter host: The host used to evaluate network reachability.
- returns: The new `NetworkReachabilityManager` instance.
*/
public convenience init?(host: String) {
guard let reachability = SCNetworkReachabilityCreateWithName(nil, host) else { return nil }
self.init(reachability: reachability)
}
/**
Creates a `NetworkReachabilityManager` instance with the default socket IPv4 or IPv6 address.
- returns: The new `NetworkReachabilityManager` instance.
*/
public convenience init?() {
if #available(iOS 9.0, OSX 10.10, *) {
var address = sockaddr_in6()
address.sin6_len = UInt8(sizeofValue(address))
address.sin6_family = sa_family_t(AF_INET6)
guard let reachability = withUnsafePointer(&address, {
SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0))
}) else { return nil }
self.init(reachability: reachability)
} else {
var address = sockaddr_in()
address.sin_len = UInt8(sizeofValue(address))
address.sin_family = sa_family_t(AF_INET)
guard let reachability = withUnsafePointer(&address, {
SCNetworkReachabilityCreateWithAddress(nil, UnsafePointer($0))
}) else { return nil }
self.init(reachability: reachability)
}
}
private init(reachability: SCNetworkReachability) {
self.reachability = reachability
self.previousFlags = SCNetworkReachabilityFlags()
}
deinit {
stopListening()
}
// MARK: - Listening
/**
Starts listening for changes in network reachability status.
- returns: `true` if listening was started successfully, `false` otherwise.
*/
public func startListening() -> Bool {
var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
context.info = UnsafeMutablePointer(Unmanaged.passUnretained(self).toOpaque())
let callbackEnabled = SCNetworkReachabilitySetCallback(
reachability,
{ (_, flags, info) in
let reachability = Unmanaged<NetworkReachabilityManager>.fromOpaque(COpaquePointer(info)).takeUnretainedValue()
reachability.notifyListener(flags)
},
&context
)
let queueEnabled = SCNetworkReachabilitySetDispatchQueue(reachability, listenerQueue)
dispatch_async(listenerQueue) {
self.previousFlags = SCNetworkReachabilityFlags()
self.notifyListener(self.flags ?? SCNetworkReachabilityFlags())
}
return callbackEnabled && queueEnabled
}
/**
Stops listening for changes in network reachability status.
*/
public func stopListening() {
SCNetworkReachabilitySetCallback(reachability, nil, nil)
SCNetworkReachabilitySetDispatchQueue(reachability, nil)
}
// MARK: - Internal - Listener Notification
func notifyListener(flags: SCNetworkReachabilityFlags) {
guard previousFlags != flags else { return }
previousFlags = flags
listener?(networkReachabilityStatusForFlags(flags))
}
// MARK: - Internal - Network Reachability Status
func networkReachabilityStatusForFlags(flags: SCNetworkReachabilityFlags) -> NetworkReachabilityStatus {
guard flags.contains(.Reachable) else { return .NotReachable }
var networkStatus: NetworkReachabilityStatus = .NotReachable
if !flags.contains(.ConnectionRequired) { networkStatus = .Reachable(.EthernetOrWiFi) }
if flags.contains(.ConnectionOnDemand) || flags.contains(.ConnectionOnTraffic) {
if !flags.contains(.InterventionRequired) { networkStatus = .Reachable(.EthernetOrWiFi) }
}
#if os(iOS)
if flags.contains(.IsWWAN) { networkStatus = .Reachable(.WWAN) }
#endif
return networkStatus
}
}
// MARK: -
extension NetworkReachabilityManager.NetworkReachabilityStatus: Equatable {}
/**
Returns whether the two network reachability status values are equal.
- parameter lhs: The left-hand side value to compare.
- parameter rhs: The right-hand side value to compare.
- returns: `true` if the two values are equal, `false` otherwise.
*/
public func ==(
lhs: NetworkReachabilityManager.NetworkReachabilityStatus,
rhs: NetworkReachabilityManager.NetworkReachabilityStatus)
-> Bool
{
switch (lhs, rhs) {
case (.Unknown, .Unknown):
return true
case (.NotReachable, .NotReachable):
return true
case let (.Reachable(lhsConnectionType), .Reachable(rhsConnectionType)):
return lhsConnectionType == rhsConnectionType
default:
return false
}
}
#endif

View File

@ -0,0 +1,47 @@
//
// Notifications.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// Contains all the `NSNotification` names posted by Alamofire with descriptions of each notification's payload.
public struct Notifications {
/// Used as a namespace for all `NSURLSessionTask` related notifications.
public struct Task {
/// Notification posted when an `NSURLSessionTask` is resumed. The notification `object` contains the resumed
/// `NSURLSessionTask`.
public static let DidResume = "com.alamofire.notifications.task.didResume"
/// Notification posted when an `NSURLSessionTask` is suspended. The notification `object` contains the
/// suspended `NSURLSessionTask`.
public static let DidSuspend = "com.alamofire.notifications.task.didSuspend"
/// Notification posted when an `NSURLSessionTask` is cancelled. The notification `object` contains the
/// cancelled `NSURLSessionTask`.
public static let DidCancel = "com.alamofire.notifications.task.didCancel"
/// Notification posted when an `NSURLSessionTask` is completed. The notification `object` contains the
/// completed `NSURLSessionTask`.
public static let DidComplete = "com.alamofire.notifications.task.didComplete"
}
}

View File

@ -0,0 +1,261 @@
//
// ParameterEncoding.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/**
HTTP method definitions.
See https://tools.ietf.org/html/rfc7231#section-4.3
*/
public enum Method: String {
case OPTIONS, GET, HEAD, POST, PUT, PATCH, DELETE, TRACE, CONNECT
}
// MARK: ParameterEncoding
/**
Used to specify the way in which a set of parameters are applied to a URL request.
- `URL`: Creates a query string to be set as or appended to any existing URL query for `GET`, `HEAD`,
and `DELETE` requests, or set as the body for requests with any other HTTP method. The
`Content-Type` HTTP header field of an encoded request with HTTP body is set to
`application/x-www-form-urlencoded; charset=utf-8`. Since there is no published specification
for how to encode collection types, the convention of appending `[]` to the key for array
values (`foo[]=1&foo[]=2`), and appending the key surrounded by square brackets for nested
dictionary values (`foo[bar]=baz`).
- `URLEncodedInURL`: Creates query string to be set as or appended to any existing URL query. Uses the same
implementation as the `.URL` case, but always applies the encoded result to the URL.
- `JSON`: Uses `NSJSONSerialization` to create a JSON representation of the parameters object, which is
set as the body of the request. The `Content-Type` HTTP header field of an encoded request is
set to `application/json`.
- `PropertyList`: Uses `NSPropertyListSerialization` to create a plist representation of the parameters object,
according to the associated format and write options values, which is set as the body of the
request. The `Content-Type` HTTP header field of an encoded request is set to
`application/x-plist`.
- `Custom`: Uses the associated closure value to construct a new request given an existing request and
parameters.
*/
public enum ParameterEncoding {
case URL
case URLEncodedInURL
case JSON
case PropertyList(NSPropertyListFormat, NSPropertyListWriteOptions)
case Custom((URLRequestConvertible, [String: AnyObject]?) -> (NSMutableURLRequest, NSError?))
/**
Creates a URL request by encoding parameters and applying them onto an existing request.
- parameter URLRequest: The request to have parameters applied.
- parameter parameters: The parameters to apply.
- returns: A tuple containing the constructed request and the error that occurred during parameter encoding,
if any.
*/
public func encode(
URLRequest: URLRequestConvertible,
parameters: [String: AnyObject]?)
-> (NSMutableURLRequest, NSError?)
{
var mutableURLRequest = URLRequest.URLRequest
guard let parameters = parameters else { return (mutableURLRequest, nil) }
var encodingError: NSError? = nil
switch self {
case .URL, .URLEncodedInURL:
func query(parameters: [String: AnyObject]) -> String {
var components: [(String, String)] = []
for key in parameters.keys.sort(<) {
let value = parameters[key]!
components += queryComponents(key, value)
}
return (components.map { "\($0)=\($1)" } as [String]).joinWithSeparator("&")
}
func encodesParametersInURL(method: Method) -> Bool {
switch self {
case .URLEncodedInURL:
return true
default:
break
}
switch method {
case .GET, .HEAD, .DELETE:
return true
default:
return false
}
}
if let method = Method(rawValue: mutableURLRequest.HTTPMethod) where encodesParametersInURL(method) {
if let
URLComponents = NSURLComponents(URL: mutableURLRequest.URL!, resolvingAgainstBaseURL: false)
where !parameters.isEmpty
{
let percentEncodedQuery = (URLComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters)
URLComponents.percentEncodedQuery = percentEncodedQuery
mutableURLRequest.URL = URLComponents.URL
}
} else {
if mutableURLRequest.valueForHTTPHeaderField("Content-Type") == nil {
mutableURLRequest.setValue(
"application/x-www-form-urlencoded; charset=utf-8",
forHTTPHeaderField: "Content-Type"
)
}
mutableURLRequest.HTTPBody = query(parameters).dataUsingEncoding(
NSUTF8StringEncoding,
allowLossyConversion: false
)
}
case .JSON:
do {
let options = NSJSONWritingOptions()
let data = try NSJSONSerialization.dataWithJSONObject(parameters, options: options)
if mutableURLRequest.valueForHTTPHeaderField("Content-Type") == nil {
mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
mutableURLRequest.HTTPBody = data
} catch {
encodingError = error as NSError
}
case .PropertyList(let format, let options):
do {
let data = try NSPropertyListSerialization.dataWithPropertyList(
parameters,
format: format,
options: options
)
if mutableURLRequest.valueForHTTPHeaderField("Content-Type") == nil {
mutableURLRequest.setValue("application/x-plist", forHTTPHeaderField: "Content-Type")
}
mutableURLRequest.HTTPBody = data
} catch {
encodingError = error as NSError
}
case .Custom(let closure):
(mutableURLRequest, encodingError) = closure(mutableURLRequest, parameters)
}
return (mutableURLRequest, encodingError)
}
/**
Creates percent-escaped, URL encoded query string components from the given key-value pair using recursion.
- parameter key: The key of the query component.
- parameter value: The value of the query component.
- returns: The percent-escaped, URL encoded query string components.
*/
public func queryComponents(key: String, _ value: AnyObject) -> [(String, String)] {
var components: [(String, String)] = []
if let dictionary = value as? [String: AnyObject] {
for (nestedKey, value) in dictionary {
components += queryComponents("\(key)[\(nestedKey)]", value)
}
} else if let array = value as? [AnyObject] {
for value in array {
components += queryComponents("\(key)[]", value)
}
} else {
components.append((escape(key), escape("\(value)")))
}
return components
}
/**
Returns a percent-escaped string following RFC 3986 for a query string key or value.
RFC 3986 states that the following characters are "reserved" characters.
- General Delimiters: ":", "#", "[", "]", "@", "?", "/"
- Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="
In RFC 3986 - Section 3.4, it states that the "?" and "/" characters should not be escaped to allow
query strings to include a URL. Therefore, all "reserved" characters with the exception of "?" and "/"
should be percent-escaped in the query string.
- parameter string: The string to be percent-escaped.
- returns: The percent-escaped string.
*/
public func escape(string: String) -> String {
let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
let subDelimitersToEncode = "!$&'()*+,;="
let allowedCharacterSet = NSCharacterSet.URLQueryAllowedCharacterSet().mutableCopy() as! NSMutableCharacterSet
allowedCharacterSet.removeCharactersInString(generalDelimitersToEncode + subDelimitersToEncode)
var escaped = ""
//==========================================================================================================
//
// Batching is required for escaping due to an internal bug in iOS 8.1 and 8.2. Encoding more than a few
// hundred Chinense characters causes various malloc error crashes. To avoid this issue until iOS 8 is no
// longer supported, batching MUST be used for encoding. This introduces roughly a 20% overhead. For more
// info, please refer to:
//
// - https://github.com/Alamofire/Alamofire/issues/206
//
//==========================================================================================================
if #available(iOS 8.3, OSX 10.10, *) {
escaped = string.stringByAddingPercentEncodingWithAllowedCharacters(allowedCharacterSet) ?? string
} else {
let batchSize = 50
var index = string.startIndex
while index != string.endIndex {
let startIndex = index
let endIndex = index.advancedBy(batchSize, limit: string.endIndex)
let range = startIndex..<endIndex
let substring = string.substringWithRange(range)
escaped += substring.stringByAddingPercentEncodingWithAllowedCharacters(allowedCharacterSet) ?? substring
index = endIndex
}
}
return escaped
}
}

568
Pods/Alamofire/Source/Request.swift generated Normal file
View File

@ -0,0 +1,568 @@
//
// Request.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/**
Responsible for sending a request and receiving the response and associated data from the server, as well as
managing its underlying `NSURLSessionTask`.
*/
public class Request {
// MARK: - Properties
/// The delegate for the underlying task.
public let delegate: TaskDelegate
/// The underlying task.
public var task: NSURLSessionTask { return delegate.task }
/// The session belonging to the underlying task.
public let session: NSURLSession
/// The request sent or to be sent to the server.
public var request: NSURLRequest? { return task.originalRequest }
/// The response received from the server, if any.
public var response: NSHTTPURLResponse? { return task.response as? NSHTTPURLResponse }
/// The progress of the request lifecycle.
public var progress: NSProgress { return delegate.progress }
var startTime: CFAbsoluteTime?
var endTime: CFAbsoluteTime?
// MARK: - Lifecycle
init(session: NSURLSession, task: NSURLSessionTask) {
self.session = session
switch task {
case is NSURLSessionUploadTask:
delegate = UploadTaskDelegate(task: task)
case is NSURLSessionDataTask:
delegate = DataTaskDelegate(task: task)
case is NSURLSessionDownloadTask:
delegate = DownloadTaskDelegate(task: task)
default:
delegate = TaskDelegate(task: task)
}
delegate.queue.addOperationWithBlock { self.endTime = CFAbsoluteTimeGetCurrent() }
}
// MARK: - Authentication
/**
Associates an HTTP Basic credential with the request.
- parameter user: The user.
- parameter password: The password.
- parameter persistence: The URL credential persistence. `.ForSession` by default.
- returns: The request.
*/
public func authenticate(
user user: String,
password: String,
persistence: NSURLCredentialPersistence = .ForSession)
-> Self
{
let credential = NSURLCredential(user: user, password: password, persistence: persistence)
return authenticate(usingCredential: credential)
}
/**
Associates a specified credential with the request.
- parameter credential: The credential.
- returns: The request.
*/
public func authenticate(usingCredential credential: NSURLCredential) -> Self {
delegate.credential = credential
return self
}
/**
Returns a base64 encoded basic authentication credential as an authorization header dictionary.
- parameter user: The user.
- parameter password: The password.
- returns: A dictionary with Authorization key and credential value or empty dictionary if encoding fails.
*/
public static func authorizationHeader(user user: String, password: String) -> [String: String] {
guard let data = "\(user):\(password)".dataUsingEncoding(NSUTF8StringEncoding) else { return [:] }
let credential = data.base64EncodedStringWithOptions([])
return ["Authorization": "Basic \(credential)"]
}
// MARK: - Progress
/**
Sets a closure to be called periodically during the lifecycle of the request as data is written to or read
from the server.
- For uploads, the progress closure returns the bytes written, total bytes written, and total bytes expected
to write.
- For downloads and data tasks, the progress closure returns the bytes read, total bytes read, and total bytes
expected to read.
- parameter closure: The code to be executed periodically during the lifecycle of the request.
- returns: The request.
*/
public func progress(closure: ((Int64, Int64, Int64) -> Void)? = nil) -> Self {
if let uploadDelegate = delegate as? UploadTaskDelegate {
uploadDelegate.uploadProgress = closure
} else if let dataDelegate = delegate as? DataTaskDelegate {
dataDelegate.dataProgress = closure
} else if let downloadDelegate = delegate as? DownloadTaskDelegate {
downloadDelegate.downloadProgress = closure
}
return self
}
/**
Sets a closure to be called periodically during the lifecycle of the request as data is read from the server.
This closure returns the bytes most recently received from the server, not including data from previous calls.
If this closure is set, data will only be available within this closure, and will not be saved elsewhere. It is
also important to note that the `response` closure will be called with nil `responseData`.
- parameter closure: The code to be executed periodically during the lifecycle of the request.
- returns: The request.
*/
public func stream(closure: (NSData -> Void)? = nil) -> Self {
if let dataDelegate = delegate as? DataTaskDelegate {
dataDelegate.dataStream = closure
}
return self
}
// MARK: - State
/**
Resumes the request.
*/
public func resume() {
if startTime == nil { startTime = CFAbsoluteTimeGetCurrent() }
task.resume()
NSNotificationCenter.defaultCenter().postNotificationName(Notifications.Task.DidResume, object: task)
}
/**
Suspends the request.
*/
public func suspend() {
task.suspend()
NSNotificationCenter.defaultCenter().postNotificationName(Notifications.Task.DidSuspend, object: task)
}
/**
Cancels the request.
*/
public func cancel() {
if let
downloadDelegate = delegate as? DownloadTaskDelegate,
downloadTask = downloadDelegate.downloadTask
{
downloadTask.cancelByProducingResumeData { data in
downloadDelegate.resumeData = data
}
} else {
task.cancel()
}
NSNotificationCenter.defaultCenter().postNotificationName(Notifications.Task.DidCancel, object: task)
}
// MARK: - TaskDelegate
/**
The task delegate is responsible for handling all delegate callbacks for the underlying task as well as
executing all operations attached to the serial operation queue upon task completion.
*/
public class TaskDelegate: NSObject {
/// The serial operation queue used to execute all operations after the task completes.
public let queue: NSOperationQueue
let task: NSURLSessionTask
let progress: NSProgress
var data: NSData? { return nil }
var error: NSError?
var initialResponseTime: CFAbsoluteTime?
var credential: NSURLCredential?
init(task: NSURLSessionTask) {
self.task = task
self.progress = NSProgress(totalUnitCount: 0)
self.queue = {
let operationQueue = NSOperationQueue()
operationQueue.maxConcurrentOperationCount = 1
operationQueue.suspended = true
if #available(OSX 10.10, *) {
operationQueue.qualityOfService = NSQualityOfService.Utility
}
return operationQueue
}()
}
deinit {
queue.cancelAllOperations()
queue.suspended = false
}
// MARK: - NSURLSessionTaskDelegate
// MARK: Override Closures
var taskWillPerformHTTPRedirection: ((NSURLSession, NSURLSessionTask, NSHTTPURLResponse, NSURLRequest) -> NSURLRequest?)?
var taskDidReceiveChallenge: ((NSURLSession, NSURLSessionTask, NSURLAuthenticationChallenge) -> (NSURLSessionAuthChallengeDisposition, NSURLCredential?))?
var taskNeedNewBodyStream: ((NSURLSession, NSURLSessionTask) -> NSInputStream?)?
var taskDidCompleteWithError: ((NSURLSession, NSURLSessionTask, NSError?) -> Void)?
// MARK: Delegate Methods
func URLSession(
session: NSURLSession,
task: NSURLSessionTask,
willPerformHTTPRedirection response: NSHTTPURLResponse,
newRequest request: NSURLRequest,
completionHandler: ((NSURLRequest?) -> Void))
{
var redirectRequest: NSURLRequest? = request
if let taskWillPerformHTTPRedirection = taskWillPerformHTTPRedirection {
redirectRequest = taskWillPerformHTTPRedirection(session, task, response, request)
}
completionHandler(redirectRequest)
}
func URLSession(
session: NSURLSession,
task: NSURLSessionTask,
didReceiveChallenge challenge: NSURLAuthenticationChallenge,
completionHandler: ((NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void))
{
var disposition: NSURLSessionAuthChallengeDisposition = .PerformDefaultHandling
var credential: NSURLCredential?
if let taskDidReceiveChallenge = taskDidReceiveChallenge {
(disposition, credential) = taskDidReceiveChallenge(session, task, challenge)
} else if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
let host = challenge.protectionSpace.host
if let
serverTrustPolicy = session.serverTrustPolicyManager?.serverTrustPolicyForHost(host),
serverTrust = challenge.protectionSpace.serverTrust
{
if serverTrustPolicy.evaluateServerTrust(serverTrust, isValidForHost: host) {
disposition = .UseCredential
credential = NSURLCredential(forTrust: serverTrust)
} else {
disposition = .CancelAuthenticationChallenge
}
}
} else {
if challenge.previousFailureCount > 0 {
disposition = .RejectProtectionSpace
} else {
credential = self.credential ?? session.configuration.URLCredentialStorage?.defaultCredentialForProtectionSpace(challenge.protectionSpace)
if credential != nil {
disposition = .UseCredential
}
}
}
completionHandler(disposition, credential)
}
func URLSession(
session: NSURLSession,
task: NSURLSessionTask,
needNewBodyStream completionHandler: ((NSInputStream?) -> Void))
{
var bodyStream: NSInputStream?
if let taskNeedNewBodyStream = taskNeedNewBodyStream {
bodyStream = taskNeedNewBodyStream(session, task)
}
completionHandler(bodyStream)
}
func URLSession(session: NSURLSession, task: NSURLSessionTask, didCompleteWithError error: NSError?) {
if let taskDidCompleteWithError = taskDidCompleteWithError {
taskDidCompleteWithError(session, task, error)
} else {
if let error = error {
self.error = error
if let
downloadDelegate = self as? DownloadTaskDelegate,
userInfo = error.userInfo as? [String: AnyObject],
resumeData = userInfo[NSURLSessionDownloadTaskResumeData] as? NSData
{
downloadDelegate.resumeData = resumeData
}
}
queue.suspended = false
}
}
}
// MARK: - DataTaskDelegate
class DataTaskDelegate: TaskDelegate, NSURLSessionDataDelegate {
var dataTask: NSURLSessionDataTask? { return task as? NSURLSessionDataTask }
private var totalBytesReceived: Int64 = 0
private var mutableData: NSMutableData
override var data: NSData? {
if dataStream != nil {
return nil
} else {
return mutableData
}
}
private var expectedContentLength: Int64?
private var dataProgress: ((bytesReceived: Int64, totalBytesReceived: Int64, totalBytesExpectedToReceive: Int64) -> Void)?
private var dataStream: ((data: NSData) -> Void)?
override init(task: NSURLSessionTask) {
mutableData = NSMutableData()
super.init(task: task)
}
// MARK: - NSURLSessionDataDelegate
// MARK: Override Closures
var dataTaskDidReceiveResponse: ((NSURLSession, NSURLSessionDataTask, NSURLResponse) -> NSURLSessionResponseDisposition)?
var dataTaskDidBecomeDownloadTask: ((NSURLSession, NSURLSessionDataTask, NSURLSessionDownloadTask) -> Void)?
var dataTaskDidReceiveData: ((NSURLSession, NSURLSessionDataTask, NSData) -> Void)?
var dataTaskWillCacheResponse: ((NSURLSession, NSURLSessionDataTask, NSCachedURLResponse) -> NSCachedURLResponse?)?
// MARK: Delegate Methods
func URLSession(
session: NSURLSession,
dataTask: NSURLSessionDataTask,
didReceiveResponse response: NSURLResponse,
completionHandler: (NSURLSessionResponseDisposition -> Void))
{
var disposition: NSURLSessionResponseDisposition = .Allow
expectedContentLength = response.expectedContentLength
if let dataTaskDidReceiveResponse = dataTaskDidReceiveResponse {
disposition = dataTaskDidReceiveResponse(session, dataTask, response)
}
completionHandler(disposition)
}
func URLSession(
session: NSURLSession,
dataTask: NSURLSessionDataTask,
didBecomeDownloadTask downloadTask: NSURLSessionDownloadTask)
{
dataTaskDidBecomeDownloadTask?(session, dataTask, downloadTask)
}
func URLSession(session: NSURLSession, dataTask: NSURLSessionDataTask, didReceiveData data: NSData) {
if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
if let dataTaskDidReceiveData = dataTaskDidReceiveData {
dataTaskDidReceiveData(session, dataTask, data)
} else {
if let dataStream = dataStream {
dataStream(data: data)
} else {
mutableData.appendData(data)
}
totalBytesReceived += data.length
let totalBytesExpected = dataTask.response?.expectedContentLength ?? NSURLSessionTransferSizeUnknown
progress.totalUnitCount = totalBytesExpected
progress.completedUnitCount = totalBytesReceived
dataProgress?(
bytesReceived: Int64(data.length),
totalBytesReceived: totalBytesReceived,
totalBytesExpectedToReceive: totalBytesExpected
)
}
}
func URLSession(
session: NSURLSession,
dataTask: NSURLSessionDataTask,
willCacheResponse proposedResponse: NSCachedURLResponse,
completionHandler: ((NSCachedURLResponse?) -> Void))
{
var cachedResponse: NSCachedURLResponse? = proposedResponse
if let dataTaskWillCacheResponse = dataTaskWillCacheResponse {
cachedResponse = dataTaskWillCacheResponse(session, dataTask, proposedResponse)
}
completionHandler(cachedResponse)
}
}
}
// MARK: - CustomStringConvertible
extension Request: CustomStringConvertible {
/**
The textual representation used when written to an output stream, which includes the HTTP method and URL, as
well as the response status code if a response has been received.
*/
public var description: String {
var components: [String] = []
if let HTTPMethod = request?.HTTPMethod {
components.append(HTTPMethod)
}
if let URLString = request?.URL?.absoluteString {
components.append(URLString)
}
if let response = response {
components.append("(\(response.statusCode))")
}
return components.joinWithSeparator(" ")
}
}
// MARK: - CustomDebugStringConvertible
extension Request: CustomDebugStringConvertible {
func cURLRepresentation() -> String {
var components = ["$ curl -i"]
guard let
request = self.request,
URL = request.URL,
host = URL.host
else {
return "$ curl command could not be created"
}
if let HTTPMethod = request.HTTPMethod where HTTPMethod != "GET" {
components.append("-X \(HTTPMethod)")
}
if let credentialStorage = self.session.configuration.URLCredentialStorage {
let protectionSpace = NSURLProtectionSpace(
host: host,
port: URL.port?.integerValue ?? 0,
protocol: URL.scheme,
realm: host,
authenticationMethod: NSURLAuthenticationMethodHTTPBasic
)
if let credentials = credentialStorage.credentialsForProtectionSpace(protectionSpace)?.values {
for credential in credentials {
components.append("-u \(credential.user!):\(credential.password!)")
}
} else {
if let credential = delegate.credential {
components.append("-u \(credential.user!):\(credential.password!)")
}
}
}
if session.configuration.HTTPShouldSetCookies {
if let
cookieStorage = session.configuration.HTTPCookieStorage,
cookies = cookieStorage.cookiesForURL(URL) where !cookies.isEmpty
{
let string = cookies.reduce("") { $0 + "\($1.name)=\($1.value ?? String());" }
components.append("-b \"\(string.substringToIndex(string.endIndex.predecessor()))\"")
}
}
var headers: [NSObject: AnyObject] = [:]
if let additionalHeaders = session.configuration.HTTPAdditionalHeaders {
for (field, value) in additionalHeaders where field != "Cookie" {
headers[field] = value
}
}
if let headerFields = request.allHTTPHeaderFields {
for (field, value) in headerFields where field != "Cookie" {
headers[field] = value
}
}
for (field, value) in headers {
components.append("-H \"\(field): \(value)\"")
}
if let
HTTPBodyData = request.HTTPBody,
HTTPBody = String(data: HTTPBodyData, encoding: NSUTF8StringEncoding)
{
var escapedBody = HTTPBody.stringByReplacingOccurrencesOfString("\\\"", withString: "\\\\\"")
escapedBody = escapedBody.stringByReplacingOccurrencesOfString("\"", withString: "\\\"")
components.append("-d \"\(escapedBody)\"")
}
components.append("\"\(URL.absoluteString)\"")
return components.joinWithSeparator(" \\\n\t")
}
/// The textual representation used when written to an output stream, in the form of a cURL command.
public var debugDescription: String {
return cURLRepresentation()
}
}

97
Pods/Alamofire/Source/Response.swift generated Normal file
View File

@ -0,0 +1,97 @@
//
// Response.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// Used to store all response data returned from a completed `Request`.
public struct Response<Value, Error: ErrorType> {
/// The URL request sent to the server.
public let request: NSURLRequest?
/// The server's response to the URL request.
public let response: NSHTTPURLResponse?
/// The data returned by the server.
public let data: NSData?
/// The result of response serialization.
public let result: Result<Value, Error>
/// The timeline of the complete lifecycle of the `Request`.
public let timeline: Timeline
/**
Initializes the `Response` instance with the specified URL request, URL response, server data and response
serialization result.
- parameter request: The URL request sent to the server.
- parameter response: The server's response to the URL request.
- parameter data: The data returned by the server.
- parameter result: The result of response serialization.
- parameter timeline: The timeline of the complete lifecycle of the `Request`. Defaults to `Timeline()`.
- returns: the new `Response` instance.
*/
public init(
request: NSURLRequest?,
response: NSHTTPURLResponse?,
data: NSData?,
result: Result<Value, Error>,
timeline: Timeline = Timeline())
{
self.request = request
self.response = response
self.data = data
self.result = result
self.timeline = timeline
}
}
// MARK: - CustomStringConvertible
extension Response: CustomStringConvertible {
/// The textual representation used when written to an output stream, which includes whether the result was a
/// success or failure.
public var description: String {
return result.debugDescription
}
}
// MARK: - CustomDebugStringConvertible
extension Response: CustomDebugStringConvertible {
/// The debug textual representation used when written to an output stream, which includes the URL request, the URL
/// response, the server data and the response serialization result.
public var debugDescription: String {
var output: [String] = []
output.append(request != nil ? "[Request]: \(request!)" : "[Request]: nil")
output.append(response != nil ? "[Response]: \(response!)" : "[Response]: nil")
output.append("[Data]: \(data?.length ?? 0) bytes")
output.append("[Result]: \(result.debugDescription)")
output.append("[Timeline]: \(timeline.debugDescription)")
return output.joinWithSeparator("\n")
}
}

View File

@ -0,0 +1,378 @@
//
// ResponseSerialization.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
// MARK: ResponseSerializer
/**
The type in which all response serializers must conform to in order to serialize a response.
*/
public protocol ResponseSerializerType {
/// The type of serialized object to be created by this `ResponseSerializerType`.
associatedtype SerializedObject
/// The type of error to be created by this `ResponseSerializer` if serialization fails.
associatedtype ErrorObject: ErrorType
/**
A closure used by response handlers that takes a request, response, data and error and returns a result.
*/
var serializeResponse: (NSURLRequest?, NSHTTPURLResponse?, NSData?, NSError?) -> Result<SerializedObject, ErrorObject> { get }
}
// MARK: -
/**
A generic `ResponseSerializerType` used to serialize a request, response, and data into a serialized object.
*/
public struct ResponseSerializer<Value, Error: ErrorType>: ResponseSerializerType {
/// The type of serialized object to be created by this `ResponseSerializer`.
public typealias SerializedObject = Value
/// The type of error to be created by this `ResponseSerializer` if serialization fails.
public typealias ErrorObject = Error
/**
A closure used by response handlers that takes a request, response, data and error and returns a result.
*/
public var serializeResponse: (NSURLRequest?, NSHTTPURLResponse?, NSData?, NSError?) -> Result<Value, Error>
/**
Initializes the `ResponseSerializer` instance with the given serialize response closure.
- parameter serializeResponse: The closure used to serialize the response.
- returns: The new generic response serializer instance.
*/
public init(serializeResponse: (NSURLRequest?, NSHTTPURLResponse?, NSData?, NSError?) -> Result<Value, Error>) {
self.serializeResponse = serializeResponse
}
}
// MARK: - Default
extension Request {
/**
Adds a handler to be called once the request has finished.
- parameter queue: The queue on which the completion handler is dispatched.
- parameter completionHandler: The code to be executed once the request has finished.
- returns: The request.
*/
public func response(
queue queue: dispatch_queue_t? = nil,
completionHandler: (NSURLRequest?, NSHTTPURLResponse?, NSData?, NSError?) -> Void)
-> Self
{
delegate.queue.addOperationWithBlock {
dispatch_async(queue ?? dispatch_get_main_queue()) {
completionHandler(self.request, self.response, self.delegate.data, self.delegate.error)
}
}
return self
}
/**
Adds a handler to be called once the request has finished.
- parameter queue: The queue on which the completion handler is dispatched.
- parameter responseSerializer: The response serializer responsible for serializing the request, response,
and data.
- parameter completionHandler: The code to be executed once the request has finished.
- returns: The request.
*/
public func response<T: ResponseSerializerType>(
queue queue: dispatch_queue_t? = nil,
responseSerializer: T,
completionHandler: Response<T.SerializedObject, T.ErrorObject> -> Void)
-> Self
{
delegate.queue.addOperationWithBlock {
let result = responseSerializer.serializeResponse(
self.request,
self.response,
self.delegate.data,
self.delegate.error
)
let requestCompletedTime = self.endTime ?? CFAbsoluteTimeGetCurrent()
let initialResponseTime = self.delegate.initialResponseTime ?? requestCompletedTime
let timeline = Timeline(
requestStartTime: self.startTime ?? CFAbsoluteTimeGetCurrent(),
initialResponseTime: initialResponseTime,
requestCompletedTime: requestCompletedTime,
serializationCompletedTime: CFAbsoluteTimeGetCurrent()
)
let response = Response<T.SerializedObject, T.ErrorObject>(
request: self.request,
response: self.response,
data: self.delegate.data,
result: result,
timeline: timeline
)
dispatch_async(queue ?? dispatch_get_main_queue()) { completionHandler(response) }
}
return self
}
}
// MARK: - Data
extension Request {
/**
Creates a response serializer that returns the associated data as-is.
- returns: A data response serializer.
*/
public static func dataResponseSerializer() -> ResponseSerializer<NSData, NSError> {
return ResponseSerializer { _, response, data, error in
guard error == nil else { return .Failure(error!) }
if let response = response where response.statusCode == 204 { return .Success(NSData()) }
guard let validData = data else {
let failureReason = "Data could not be serialized. Input data was nil."
let error = Error.error(code: .DataSerializationFailed, failureReason: failureReason)
return .Failure(error)
}
return .Success(validData)
}
}
/**
Adds a handler to be called once the request has finished.
- parameter completionHandler: The code to be executed once the request has finished.
- returns: The request.
*/
public func responseData(
queue queue: dispatch_queue_t? = nil,
completionHandler: Response<NSData, NSError> -> Void)
-> Self
{
return response(queue: queue, responseSerializer: Request.dataResponseSerializer(), completionHandler: completionHandler)
}
}
// MARK: - String
extension Request {
/**
Creates a response serializer that returns a string initialized from the response data with the specified
string encoding.
- parameter encoding: The string encoding. If `nil`, the string encoding will be determined from the server
response, falling back to the default HTTP default character set, ISO-8859-1.
- returns: A string response serializer.
*/
public static func stringResponseSerializer(
encoding encoding: NSStringEncoding? = nil)
-> ResponseSerializer<String, NSError>
{
return ResponseSerializer { _, response, data, error in
guard error == nil else { return .Failure(error!) }
if let response = response where response.statusCode == 204 { return .Success("") }
guard let validData = data else {
let failureReason = "String could not be serialized. Input data was nil."
let error = Error.error(code: .StringSerializationFailed, failureReason: failureReason)
return .Failure(error)
}
var convertedEncoding = encoding
if let encodingName = response?.textEncodingName where convertedEncoding == nil {
convertedEncoding = CFStringConvertEncodingToNSStringEncoding(
CFStringConvertIANACharSetNameToEncoding(encodingName)
)
}
let actualEncoding = convertedEncoding ?? NSISOLatin1StringEncoding
if let string = String(data: validData, encoding: actualEncoding) {
return .Success(string)
} else {
let failureReason = "String could not be serialized with encoding: \(actualEncoding)"
let error = Error.error(code: .StringSerializationFailed, failureReason: failureReason)
return .Failure(error)
}
}
}
/**
Adds a handler to be called once the request has finished.
- parameter encoding: The string encoding. If `nil`, the string encoding will be determined from the
server response, falling back to the default HTTP default character set,
ISO-8859-1.
- parameter completionHandler: A closure to be executed once the request has finished.
- returns: The request.
*/
public func responseString(
queue queue: dispatch_queue_t? = nil,
encoding: NSStringEncoding? = nil,
completionHandler: Response<String, NSError> -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: Request.stringResponseSerializer(encoding: encoding),
completionHandler: completionHandler
)
}
}
// MARK: - JSON
extension Request {
/**
Creates a response serializer that returns a JSON object constructed from the response data using
`NSJSONSerialization` with the specified reading options.
- parameter options: The JSON serialization reading options. `.AllowFragments` by default.
- returns: A JSON object response serializer.
*/
public static func JSONResponseSerializer(
options options: NSJSONReadingOptions = .AllowFragments)
-> ResponseSerializer<AnyObject, NSError>
{
return ResponseSerializer { _, response, data, error in
guard error == nil else { return .Failure(error!) }
if let response = response where response.statusCode == 204 { return .Success(NSNull()) }
guard let validData = data where validData.length > 0 else {
let failureReason = "JSON could not be serialized. Input data was nil or zero length."
let error = Error.error(code: .JSONSerializationFailed, failureReason: failureReason)
return .Failure(error)
}
do {
let JSON = try NSJSONSerialization.JSONObjectWithData(validData, options: options)
return .Success(JSON)
} catch {
return .Failure(error as NSError)
}
}
}
/**
Adds a handler to be called once the request has finished.
- parameter options: The JSON serialization reading options. `.AllowFragments` by default.
- parameter completionHandler: A closure to be executed once the request has finished.
- returns: The request.
*/
public func responseJSON(
queue queue: dispatch_queue_t? = nil,
options: NSJSONReadingOptions = .AllowFragments,
completionHandler: Response<AnyObject, NSError> -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: Request.JSONResponseSerializer(options: options),
completionHandler: completionHandler
)
}
}
// MARK: - Property List
extension Request {
/**
Creates a response serializer that returns an object constructed from the response data using
`NSPropertyListSerialization` with the specified reading options.
- parameter options: The property list reading options. `NSPropertyListReadOptions()` by default.
- returns: A property list object response serializer.
*/
public static func propertyListResponseSerializer(
options options: NSPropertyListReadOptions = NSPropertyListReadOptions())
-> ResponseSerializer<AnyObject, NSError>
{
return ResponseSerializer { _, response, data, error in
guard error == nil else { return .Failure(error!) }
if let response = response where response.statusCode == 204 { return .Success(NSNull()) }
guard let validData = data where validData.length > 0 else {
let failureReason = "Property list could not be serialized. Input data was nil or zero length."
let error = Error.error(code: .PropertyListSerializationFailed, failureReason: failureReason)
return .Failure(error)
}
do {
let plist = try NSPropertyListSerialization.propertyListWithData(validData, options: options, format: nil)
return .Success(plist)
} catch {
return .Failure(error as NSError)
}
}
}
/**
Adds a handler to be called once the request has finished.
- parameter options: The property list reading options. `0` by default.
- parameter completionHandler: A closure to be executed once the request has finished. The closure takes 3
arguments: the URL request, the URL response, the server data and the result
produced while creating the property list.
- returns: The request.
*/
public func responsePropertyList(
queue queue: dispatch_queue_t? = nil,
options: NSPropertyListReadOptions = NSPropertyListReadOptions(),
completionHandler: Response<AnyObject, NSError> -> Void)
-> Self
{
return response(
queue: queue,
responseSerializer: Request.propertyListResponseSerializer(options: options),
completionHandler: completionHandler
)
}
}

103
Pods/Alamofire/Source/Result.swift generated Normal file
View File

@ -0,0 +1,103 @@
//
// Result.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/**
Used to represent whether a request was successful or encountered an error.
- Success: The request and all post processing operations were successful resulting in the serialization of the
provided associated value.
- Failure: The request encountered an error resulting in a failure. The associated values are the original data
provided by the server as well as the error that caused the failure.
*/
public enum Result<Value, Error: ErrorType> {
case Success(Value)
case Failure(Error)
/// Returns `true` if the result is a success, `false` otherwise.
public var isSuccess: Bool {
switch self {
case .Success:
return true
case .Failure:
return false
}
}
/// Returns `true` if the result is a failure, `false` otherwise.
public var isFailure: Bool {
return !isSuccess
}
/// Returns the associated value if the result is a success, `nil` otherwise.
public var value: Value? {
switch self {
case .Success(let value):
return value
case .Failure:
return nil
}
}
/// Returns the associated error value if the result is a failure, `nil` otherwise.
public var error: Error? {
switch self {
case .Success:
return nil
case .Failure(let error):
return error
}
}
}
// MARK: - CustomStringConvertible
extension Result: CustomStringConvertible {
/// The textual representation used when written to an output stream, which includes whether the result was a
/// success or failure.
public var description: String {
switch self {
case .Success:
return "SUCCESS"
case .Failure:
return "FAILURE"
}
}
}
// MARK: - CustomDebugStringConvertible
extension Result: CustomDebugStringConvertible {
/// The debug textual representation used when written to an output stream, which includes whether the result was a
/// success or failure in addition to the value or error.
public var debugDescription: String {
switch self {
case .Success(let value):
return "SUCCESS: \(value)"
case .Failure(let error):
return "FAILURE: \(error)"
}
}
}

View File

@ -0,0 +1,304 @@
//
// ServerTrustPolicy.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// Responsible for managing the mapping of `ServerTrustPolicy` objects to a given host.
public class ServerTrustPolicyManager {
/// The dictionary of policies mapped to a particular host.
public let policies: [String: ServerTrustPolicy]
/**
Initializes the `ServerTrustPolicyManager` instance with the given policies.
Since different servers and web services can have different leaf certificates, intermediate and even root
certficates, it is important to have the flexibility to specify evaluation policies on a per host basis. This
allows for scenarios such as using default evaluation for host1, certificate pinning for host2, public key
pinning for host3 and disabling evaluation for host4.
- parameter policies: A dictionary of all policies mapped to a particular host.
- returns: The new `ServerTrustPolicyManager` instance.
*/
public init(policies: [String: ServerTrustPolicy]) {
self.policies = policies
}
/**
Returns the `ServerTrustPolicy` for the given host if applicable.
By default, this method will return the policy that perfectly matches the given host. Subclasses could override
this method and implement more complex mapping implementations such as wildcards.
- parameter host: The host to use when searching for a matching policy.
- returns: The server trust policy for the given host if found.
*/
public func serverTrustPolicyForHost(host: String) -> ServerTrustPolicy? {
return policies[host]
}
}
// MARK: -
extension NSURLSession {
private struct AssociatedKeys {
static var ManagerKey = "NSURLSession.ServerTrustPolicyManager"
}
var serverTrustPolicyManager: ServerTrustPolicyManager? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.ManagerKey) as? ServerTrustPolicyManager
}
set (manager) {
objc_setAssociatedObject(self, &AssociatedKeys.ManagerKey, manager, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}
// MARK: - ServerTrustPolicy
/**
The `ServerTrustPolicy` evaluates the server trust generally provided by an `NSURLAuthenticationChallenge` when
connecting to a server over a secure HTTPS connection. The policy configuration then evaluates the server trust
with a given set of criteria to determine whether the server trust is valid and the connection should be made.
Using pinned certificates or public keys for evaluation helps prevent man-in-the-middle (MITM) attacks and other
vulnerabilities. Applications dealing with sensitive customer data or financial information are strongly encouraged
to route all communication over an HTTPS connection with pinning enabled.
- PerformDefaultEvaluation: Uses the default server trust evaluation while allowing you to control whether to
validate the host provided by the challenge. Applications are encouraged to always
validate the host in production environments to guarantee the validity of the server's
certificate chain.
- PinCertificates: Uses the pinned certificates to validate the server trust. The server trust is
considered valid if one of the pinned certificates match one of the server certificates.
By validating both the certificate chain and host, certificate pinning provides a very
secure form of server trust validation mitigating most, if not all, MITM attacks.
Applications are encouraged to always validate the host and require a valid certificate
chain in production environments.
- PinPublicKeys: Uses the pinned public keys to validate the server trust. The server trust is considered
valid if one of the pinned public keys match one of the server certificate public keys.
By validating both the certificate chain and host, public key pinning provides a very
secure form of server trust validation mitigating most, if not all, MITM attacks.
Applications are encouraged to always validate the host and require a valid certificate
chain in production environments.
- DisableEvaluation: Disables all evaluation which in turn will always consider any server trust as valid.
- CustomEvaluation: Uses the associated closure to evaluate the validity of the server trust.
*/
public enum ServerTrustPolicy {
case PerformDefaultEvaluation(validateHost: Bool)
case PinCertificates(certificates: [SecCertificate], validateCertificateChain: Bool, validateHost: Bool)
case PinPublicKeys(publicKeys: [SecKey], validateCertificateChain: Bool, validateHost: Bool)
case DisableEvaluation
case CustomEvaluation((serverTrust: SecTrust, host: String) -> Bool)
// MARK: - Bundle Location
/**
Returns all certificates within the given bundle with a `.cer` file extension.
- parameter bundle: The bundle to search for all `.cer` files.
- returns: All certificates within the given bundle.
*/
public static func certificatesInBundle(bundle: NSBundle = NSBundle.mainBundle()) -> [SecCertificate] {
var certificates: [SecCertificate] = []
let paths = Set([".cer", ".CER", ".crt", ".CRT", ".der", ".DER"].map { fileExtension in
bundle.pathsForResourcesOfType(fileExtension, inDirectory: nil)
}.flatten())
for path in paths {
if let
certificateData = NSData(contentsOfFile: path),
certificate = SecCertificateCreateWithData(nil, certificateData)
{
certificates.append(certificate)
}
}
return certificates
}
/**
Returns all public keys within the given bundle with a `.cer` file extension.
- parameter bundle: The bundle to search for all `*.cer` files.
- returns: All public keys within the given bundle.
*/
public static func publicKeysInBundle(bundle: NSBundle = NSBundle.mainBundle()) -> [SecKey] {
var publicKeys: [SecKey] = []
for certificate in certificatesInBundle(bundle) {
if let publicKey = publicKeyForCertificate(certificate) {
publicKeys.append(publicKey)
}
}
return publicKeys
}
// MARK: - Evaluation
/**
Evaluates whether the server trust is valid for the given host.
- parameter serverTrust: The server trust to evaluate.
- parameter host: The host of the challenge protection space.
- returns: Whether the server trust is valid.
*/
public func evaluateServerTrust(serverTrust: SecTrust, isValidForHost host: String) -> Bool {
var serverTrustIsValid = false
switch self {
case let .PerformDefaultEvaluation(validateHost):
let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
SecTrustSetPolicies(serverTrust, [policy])
serverTrustIsValid = trustIsValid(serverTrust)
case let .PinCertificates(pinnedCertificates, validateCertificateChain, validateHost):
if validateCertificateChain {
let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
SecTrustSetPolicies(serverTrust, [policy])
SecTrustSetAnchorCertificates(serverTrust, pinnedCertificates)
SecTrustSetAnchorCertificatesOnly(serverTrust, true)
serverTrustIsValid = trustIsValid(serverTrust)
} else {
let serverCertificatesDataArray = certificateDataForTrust(serverTrust)
let pinnedCertificatesDataArray = certificateDataForCertificates(pinnedCertificates)
outerLoop: for serverCertificateData in serverCertificatesDataArray {
for pinnedCertificateData in pinnedCertificatesDataArray {
if serverCertificateData.isEqualToData(pinnedCertificateData) {
serverTrustIsValid = true
break outerLoop
}
}
}
}
case let .PinPublicKeys(pinnedPublicKeys, validateCertificateChain, validateHost):
var certificateChainEvaluationPassed = true
if validateCertificateChain {
let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
SecTrustSetPolicies(serverTrust, [policy])
certificateChainEvaluationPassed = trustIsValid(serverTrust)
}
if certificateChainEvaluationPassed {
outerLoop: for serverPublicKey in ServerTrustPolicy.publicKeysForTrust(serverTrust) as [AnyObject] {
for pinnedPublicKey in pinnedPublicKeys as [AnyObject] {
if serverPublicKey.isEqual(pinnedPublicKey) {
serverTrustIsValid = true
break outerLoop
}
}
}
}
case .DisableEvaluation:
serverTrustIsValid = true
case let .CustomEvaluation(closure):
serverTrustIsValid = closure(serverTrust: serverTrust, host: host)
}
return serverTrustIsValid
}
// MARK: - Private - Trust Validation
private func trustIsValid(trust: SecTrust) -> Bool {
var isValid = false
var result = SecTrustResultType(kSecTrustResultInvalid)
let status = SecTrustEvaluate(trust, &result)
if status == errSecSuccess {
let unspecified = SecTrustResultType(kSecTrustResultUnspecified)
let proceed = SecTrustResultType(kSecTrustResultProceed)
isValid = result == unspecified || result == proceed
}
return isValid
}
// MARK: - Private - Certificate Data
private func certificateDataForTrust(trust: SecTrust) -> [NSData] {
var certificates: [SecCertificate] = []
for index in 0..<SecTrustGetCertificateCount(trust) {
if let certificate = SecTrustGetCertificateAtIndex(trust, index) {
certificates.append(certificate)
}
}
return certificateDataForCertificates(certificates)
}
private func certificateDataForCertificates(certificates: [SecCertificate]) -> [NSData] {
return certificates.map { SecCertificateCopyData($0) as NSData }
}
// MARK: - Private - Public Key Extraction
private static func publicKeysForTrust(trust: SecTrust) -> [SecKey] {
var publicKeys: [SecKey] = []
for index in 0..<SecTrustGetCertificateCount(trust) {
if let
certificate = SecTrustGetCertificateAtIndex(trust, index),
publicKey = publicKeyForCertificate(certificate)
{
publicKeys.append(publicKey)
}
}
return publicKeys
}
private static func publicKeyForCertificate(certificate: SecCertificate) -> SecKey? {
var publicKey: SecKey?
let policy = SecPolicyCreateBasicX509()
var trust: SecTrust?
let trustCreationStatus = SecTrustCreateWithCertificates(certificate, policy, &trust)
if let trust = trust where trustCreationStatus == errSecSuccess {
publicKey = SecTrustCopyPublicKey(trust)
}
return publicKey
}
}

182
Pods/Alamofire/Source/Stream.swift generated Normal file
View File

@ -0,0 +1,182 @@
//
// Stream.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
#if !os(watchOS)
@available(iOS 9.0, OSX 10.11, tvOS 9.0, *)
extension Manager {
private enum Streamable {
case Stream(String, Int)
case NetService(NSNetService)
}
private func stream(streamable: Streamable) -> Request {
var streamTask: NSURLSessionStreamTask!
switch streamable {
case .Stream(let hostName, let port):
dispatch_sync(queue) {
streamTask = self.session.streamTaskWithHostName(hostName, port: port)
}
case .NetService(let netService):
dispatch_sync(queue) {
streamTask = self.session.streamTaskWithNetService(netService)
}
}
let request = Request(session: session, task: streamTask)
delegate[request.delegate.task] = request.delegate
if startRequestsImmediately {
request.resume()
}
return request
}
/**
Creates a request for bidirectional streaming with the given hostname and port.
- parameter hostName: The hostname of the server to connect to.
- parameter port: The port of the server to connect to.
:returns: The created stream request.
*/
public func stream(hostName hostName: String, port: Int) -> Request {
return stream(.Stream(hostName, port))
}
/**
Creates a request for bidirectional streaming with the given `NSNetService`.
- parameter netService: The net service used to identify the endpoint.
- returns: The created stream request.
*/
public func stream(netService netService: NSNetService) -> Request {
return stream(.NetService(netService))
}
}
// MARK: -
@available(iOS 9.0, OSX 10.11, tvOS 9.0, *)
extension Manager.SessionDelegate: NSURLSessionStreamDelegate {
// MARK: Override Closures
/// Overrides default behavior for NSURLSessionStreamDelegate method `URLSession:readClosedForStreamTask:`.
public var streamTaskReadClosed: ((NSURLSession, NSURLSessionStreamTask) -> Void)? {
get {
return _streamTaskReadClosed as? (NSURLSession, NSURLSessionStreamTask) -> Void
}
set {
_streamTaskReadClosed = newValue
}
}
/// Overrides default behavior for NSURLSessionStreamDelegate method `URLSession:writeClosedForStreamTask:`.
public var streamTaskWriteClosed: ((NSURLSession, NSURLSessionStreamTask) -> Void)? {
get {
return _streamTaskWriteClosed as? (NSURLSession, NSURLSessionStreamTask) -> Void
}
set {
_streamTaskWriteClosed = newValue
}
}
/// Overrides default behavior for NSURLSessionStreamDelegate method `URLSession:betterRouteDiscoveredForStreamTask:`.
public var streamTaskBetterRouteDiscovered: ((NSURLSession, NSURLSessionStreamTask) -> Void)? {
get {
return _streamTaskBetterRouteDiscovered as? (NSURLSession, NSURLSessionStreamTask) -> Void
}
set {
_streamTaskBetterRouteDiscovered = newValue
}
}
/// Overrides default behavior for NSURLSessionStreamDelegate method `URLSession:streamTask:didBecomeInputStream:outputStream:`.
public var streamTaskDidBecomeInputStream: ((NSURLSession, NSURLSessionStreamTask, NSInputStream, NSOutputStream) -> Void)? {
get {
return _streamTaskDidBecomeInputStream as? (NSURLSession, NSURLSessionStreamTask, NSInputStream, NSOutputStream) -> Void
}
set {
_streamTaskDidBecomeInputStream = newValue
}
}
// MARK: Delegate Methods
/**
Tells the delegate that the read side of the connection has been closed.
- parameter session: The session.
- parameter streamTask: The stream task.
*/
public func URLSession(session: NSURLSession, readClosedForStreamTask streamTask: NSURLSessionStreamTask) {
streamTaskReadClosed?(session, streamTask)
}
/**
Tells the delegate that the write side of the connection has been closed.
- parameter session: The session.
- parameter streamTask: The stream task.
*/
public func URLSession(session: NSURLSession, writeClosedForStreamTask streamTask: NSURLSessionStreamTask) {
streamTaskWriteClosed?(session, streamTask)
}
/**
Tells the delegate that the system has determined that a better route to the host is available.
- parameter session: The session.
- parameter streamTask: The stream task.
*/
public func URLSession(session: NSURLSession, betterRouteDiscoveredForStreamTask streamTask: NSURLSessionStreamTask) {
streamTaskBetterRouteDiscovered?(session, streamTask)
}
/**
Tells the delegate that the stream task has been completed and provides the unopened stream objects.
- parameter session: The session.
- parameter streamTask: The stream task.
- parameter inputStream: The new input stream.
- parameter outputStream: The new output stream.
*/
public func URLSession(
session: NSURLSession,
streamTask: NSURLSessionStreamTask,
didBecomeInputStream inputStream: NSInputStream,
outputStream: NSOutputStream)
{
streamTaskDidBecomeInputStream?(session, streamTask, inputStream, outputStream)
}
}
#endif

125
Pods/Alamofire/Source/Timeline.swift generated Normal file
View File

@ -0,0 +1,125 @@
//
// Timeline.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
/// Responsible for computing the timing metrics for the complete lifecycle of a `Request`.
public struct Timeline {
/// The time the request was initialized.
public let requestStartTime: CFAbsoluteTime
/// The time the first bytes were received from or sent to the server.
public let initialResponseTime: CFAbsoluteTime
/// The time when the request was completed.
public let requestCompletedTime: CFAbsoluteTime
/// The time when the response serialization was completed.
public let serializationCompletedTime: CFAbsoluteTime
/// The time interval in seconds from the time the request started to the initial response from the server.
public let latency: NSTimeInterval
/// The time interval in seconds from the time the request started to the time the request completed.
public let requestDuration: NSTimeInterval
/// The time interval in seconds from the time the request completed to the time response serialization completed.
public let serializationDuration: NSTimeInterval
/// The time interval in seconds from the time the request started to the time response serialization completed.
public let totalDuration: NSTimeInterval
/**
Creates a new `Timeline` instance with the specified request times.
- parameter requestStartTime: The time the request was initialized. Defaults to `0.0`.
- parameter initialResponseTime: The time the first bytes were received from or sent to the server.
Defaults to `0.0`.
- parameter requestCompletedTime: The time when the request was completed. Defaults to `0.0`.
- parameter serializationCompletedTime: The time when the response serialization was completed. Defaults
to `0.0`.
- returns: The new `Timeline` instance.
*/
public init(
requestStartTime: CFAbsoluteTime = 0.0,
initialResponseTime: CFAbsoluteTime = 0.0,
requestCompletedTime: CFAbsoluteTime = 0.0,
serializationCompletedTime: CFAbsoluteTime = 0.0)
{
self.requestStartTime = requestStartTime
self.initialResponseTime = initialResponseTime
self.requestCompletedTime = requestCompletedTime
self.serializationCompletedTime = serializationCompletedTime
self.latency = initialResponseTime - requestStartTime
self.requestDuration = requestCompletedTime - requestStartTime
self.serializationDuration = serializationCompletedTime - requestCompletedTime
self.totalDuration = serializationCompletedTime - requestStartTime
}
}
// MARK: - CustomStringConvertible
extension Timeline: CustomStringConvertible {
/// The textual representation used when written to an output stream, which includes the latency, the request
/// duration and the total duration.
public var description: String {
let latency = String(format: "%.3f", self.latency)
let requestDuration = String(format: "%.3f", self.requestDuration)
let serializationDuration = String(format: "%.3f", self.serializationDuration)
let totalDuration = String(format: "%.3f", self.totalDuration)
let timings = [
"\"Latency\": \(latency) secs",
"\"Request Duration\": \(requestDuration) secs",
"\"Serialization Duration\": \(serializationDuration) secs",
"\"Total Duration\": \(totalDuration) secs"
]
return "Timeline: { \(timings.joinWithSeparator(", ")) }"
}
}
// MARK: - CustomDebugStringConvertible
extension Timeline: CustomDebugStringConvertible {
/// The textual representation used when written to an output stream, which includes the request start time, the
/// initial response time, the request completed time, the serialization completed time, the latency, the request
/// duration and the total duration.
public var debugDescription: String {
let timings = [
"\"Request Start Time\": \(requestStartTime)",
"\"Initial Response Time\": \(initialResponseTime)",
"\"Request Completed Time\": \(requestCompletedTime)",
"\"Serialization Completed Time\": \(serializationCompletedTime)",
"\"Latency\": \(latency) secs",
"\"Request Duration\": \(requestDuration) secs",
"\"Serialization Duration\": \(serializationDuration) secs",
"\"Total Duration\": \(totalDuration) secs"
]
return "Timeline: { \(timings.joinWithSeparator(", ")) }"
}
}

376
Pods/Alamofire/Source/Upload.swift generated Normal file
View File

@ -0,0 +1,376 @@
//
// Upload.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
extension Manager {
private enum Uploadable {
case Data(NSURLRequest, NSData)
case File(NSURLRequest, NSURL)
case Stream(NSURLRequest, NSInputStream)
}
private func upload(uploadable: Uploadable) -> Request {
var uploadTask: NSURLSessionUploadTask!
var HTTPBodyStream: NSInputStream?
switch uploadable {
case .Data(let request, let data):
dispatch_sync(queue) {
uploadTask = self.session.uploadTaskWithRequest(request, fromData: data)
}
case .File(let request, let fileURL):
dispatch_sync(queue) {
uploadTask = self.session.uploadTaskWithRequest(request, fromFile: fileURL)
}
case .Stream(let request, let stream):
dispatch_sync(queue) {
uploadTask = self.session.uploadTaskWithStreamedRequest(request)
}
HTTPBodyStream = stream
}
let request = Request(session: session, task: uploadTask)
if HTTPBodyStream != nil {
request.delegate.taskNeedNewBodyStream = { _, _ in
return HTTPBodyStream
}
}
delegate[request.delegate.task] = request.delegate
if startRequestsImmediately {
request.resume()
}
return request
}
// MARK: File
/**
Creates a request for uploading a file to the specified URL request.
If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
- parameter URLRequest: The URL request
- parameter file: The file to upload
- returns: The created upload request.
*/
public func upload(URLRequest: URLRequestConvertible, file: NSURL) -> Request {
return upload(.File(URLRequest.URLRequest, file))
}
/**
Creates a request for uploading a file to the specified URL request.
If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
- parameter method: The HTTP method.
- parameter URLString: The URL string.
- parameter headers: The HTTP headers. `nil` by default.
- parameter file: The file to upload
- returns: The created upload request.
*/
public func upload(
method: Method,
_ URLString: URLStringConvertible,
headers: [String: String]? = nil,
file: NSURL)
-> Request
{
let mutableURLRequest = URLRequest(method, URLString, headers: headers)
return upload(mutableURLRequest, file: file)
}
// MARK: Data
/**
Creates a request for uploading data to the specified URL request.
If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
- parameter URLRequest: The URL request.
- parameter data: The data to upload.
- returns: The created upload request.
*/
public func upload(URLRequest: URLRequestConvertible, data: NSData) -> Request {
return upload(.Data(URLRequest.URLRequest, data))
}
/**
Creates a request for uploading data to the specified URL request.
If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
- parameter method: The HTTP method.
- parameter URLString: The URL string.
- parameter headers: The HTTP headers. `nil` by default.
- parameter data: The data to upload
- returns: The created upload request.
*/
public func upload(
method: Method,
_ URLString: URLStringConvertible,
headers: [String: String]? = nil,
data: NSData)
-> Request
{
let mutableURLRequest = URLRequest(method, URLString, headers: headers)
return upload(mutableURLRequest, data: data)
}
// MARK: Stream
/**
Creates a request for uploading a stream to the specified URL request.
If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
- parameter URLRequest: The URL request.
- parameter stream: The stream to upload.
- returns: The created upload request.
*/
public func upload(URLRequest: URLRequestConvertible, stream: NSInputStream) -> Request {
return upload(.Stream(URLRequest.URLRequest, stream))
}
/**
Creates a request for uploading a stream to the specified URL request.
If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
- parameter method: The HTTP method.
- parameter URLString: The URL string.
- parameter headers: The HTTP headers. `nil` by default.
- parameter stream: The stream to upload.
- returns: The created upload request.
*/
public func upload(
method: Method,
_ URLString: URLStringConvertible,
headers: [String: String]? = nil,
stream: NSInputStream)
-> Request
{
let mutableURLRequest = URLRequest(method, URLString, headers: headers)
return upload(mutableURLRequest, stream: stream)
}
// MARK: MultipartFormData
/// Default memory threshold used when encoding `MultipartFormData`.
public static let MultipartFormDataEncodingMemoryThreshold: UInt64 = 10 * 1024 * 1024
/**
Defines whether the `MultipartFormData` encoding was successful and contains result of the encoding as
associated values.
- Success: Represents a successful `MultipartFormData` encoding and contains the new `Request` along with
streaming information.
- Failure: Used to represent a failure in the `MultipartFormData` encoding and also contains the encoding
error.
*/
public enum MultipartFormDataEncodingResult {
case Success(request: Request, streamingFromDisk: Bool, streamFileURL: NSURL?)
case Failure(ErrorType)
}
/**
Encodes the `MultipartFormData` and creates a request to upload the result to the specified URL request.
It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative
payload is small, encoding the data in-memory and directly uploading to a server is the by far the most
efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to
be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory
footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be
used for larger payloads such as video content.
The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory
or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`,
encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk
during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding
technique was used.
If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
- parameter method: The HTTP method.
- parameter URLString: The URL string.
- parameter headers: The HTTP headers. `nil` by default.
- parameter multipartFormData: The closure used to append body parts to the `MultipartFormData`.
- parameter encodingMemoryThreshold: The encoding memory threshold in bytes.
`MultipartFormDataEncodingMemoryThreshold` by default.
- parameter encodingCompletion: The closure called when the `MultipartFormData` encoding is complete.
*/
public func upload(
method: Method,
_ URLString: URLStringConvertible,
headers: [String: String]? = nil,
multipartFormData: MultipartFormData -> Void,
encodingMemoryThreshold: UInt64 = Manager.MultipartFormDataEncodingMemoryThreshold,
encodingCompletion: (MultipartFormDataEncodingResult -> Void)?)
{
let mutableURLRequest = URLRequest(method, URLString, headers: headers)
return upload(
mutableURLRequest,
multipartFormData: multipartFormData,
encodingMemoryThreshold: encodingMemoryThreshold,
encodingCompletion: encodingCompletion
)
}
/**
Encodes the `MultipartFormData` and creates a request to upload the result to the specified URL request.
It is important to understand the memory implications of uploading `MultipartFormData`. If the cummulative
payload is small, encoding the data in-memory and directly uploading to a server is the by far the most
efficient approach. However, if the payload is too large, encoding the data in-memory could cause your app to
be terminated. Larger payloads must first be written to disk using input and output streams to keep the memory
footprint low, then the data can be uploaded as a stream from the resulting file. Streaming from disk MUST be
used for larger payloads such as video content.
The `encodingMemoryThreshold` parameter allows Alamofire to automatically determine whether to encode in-memory
or stream from disk. If the content length of the `MultipartFormData` is below the `encodingMemoryThreshold`,
encoding takes place in-memory. If the content length exceeds the threshold, the data is streamed to disk
during the encoding process. Then the result is uploaded as data or as a stream depending on which encoding
technique was used.
If `startRequestsImmediately` is `true`, the request will have `resume()` called before being returned.
- parameter URLRequest: The URL request.
- parameter multipartFormData: The closure used to append body parts to the `MultipartFormData`.
- parameter encodingMemoryThreshold: The encoding memory threshold in bytes.
`MultipartFormDataEncodingMemoryThreshold` by default.
- parameter encodingCompletion: The closure called when the `MultipartFormData` encoding is complete.
*/
public func upload(
URLRequest: URLRequestConvertible,
multipartFormData: MultipartFormData -> Void,
encodingMemoryThreshold: UInt64 = Manager.MultipartFormDataEncodingMemoryThreshold,
encodingCompletion: (MultipartFormDataEncodingResult -> Void)?)
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
let formData = MultipartFormData()
multipartFormData(formData)
let URLRequestWithContentType = URLRequest.URLRequest
URLRequestWithContentType.setValue(formData.contentType, forHTTPHeaderField: "Content-Type")
let isBackgroundSession = self.session.configuration.identifier != nil
if formData.contentLength < encodingMemoryThreshold && !isBackgroundSession {
do {
let data = try formData.encode()
let encodingResult = MultipartFormDataEncodingResult.Success(
request: self.upload(URLRequestWithContentType, data: data),
streamingFromDisk: false,
streamFileURL: nil
)
dispatch_async(dispatch_get_main_queue()) {
encodingCompletion?(encodingResult)
}
} catch {
dispatch_async(dispatch_get_main_queue()) {
encodingCompletion?(.Failure(error as NSError))
}
}
} else {
let fileManager = NSFileManager.defaultManager()
let tempDirectoryURL = NSURL(fileURLWithPath: NSTemporaryDirectory())
let directoryURL = tempDirectoryURL.URLByAppendingPathComponent("com.alamofire.manager/multipart.form.data")
let fileName = NSUUID().UUIDString
let fileURL = directoryURL.URLByAppendingPathComponent(fileName)
do {
try fileManager.createDirectoryAtURL(directoryURL, withIntermediateDirectories: true, attributes: nil)
try formData.writeEncodedDataToDisk(fileURL)
dispatch_async(dispatch_get_main_queue()) {
let encodingResult = MultipartFormDataEncodingResult.Success(
request: self.upload(URLRequestWithContentType, file: fileURL),
streamingFromDisk: true,
streamFileURL: fileURL
)
encodingCompletion?(encodingResult)
}
} catch {
dispatch_async(dispatch_get_main_queue()) {
encodingCompletion?(.Failure(error as NSError))
}
}
}
}
}
}
// MARK: -
extension Request {
// MARK: - UploadTaskDelegate
class UploadTaskDelegate: DataTaskDelegate {
var uploadTask: NSURLSessionUploadTask? { return task as? NSURLSessionUploadTask }
var uploadProgress: ((Int64, Int64, Int64) -> Void)!
// MARK: - NSURLSessionTaskDelegate
// MARK: Override Closures
var taskDidSendBodyData: ((NSURLSession, NSURLSessionTask, Int64, Int64, Int64) -> Void)?
// MARK: Delegate Methods
func URLSession(
session: NSURLSession,
task: NSURLSessionTask,
didSendBodyData bytesSent: Int64,
totalBytesSent: Int64,
totalBytesExpectedToSend: Int64)
{
if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
if let taskDidSendBodyData = taskDidSendBodyData {
taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalBytesExpectedToSend)
} else {
progress.totalUnitCount = totalBytesExpectedToSend
progress.completedUnitCount = totalBytesSent
uploadProgress?(bytesSent, totalBytesSent, totalBytesExpectedToSend)
}
}
}
}

214
Pods/Alamofire/Source/Validation.swift generated Normal file
View File

@ -0,0 +1,214 @@
//
// Validation.swift
//
// Copyright (c) 2014-2016 Alamofire Software Foundation (http://alamofire.org/)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
import Foundation
extension Request {
/**
Used to represent whether validation was successful or encountered an error resulting in a failure.
- Success: The validation was successful.
- Failure: The validation failed encountering the provided error.
*/
public enum ValidationResult {
case Success
case Failure(NSError)
}
/**
A closure used to validate a request that takes a URL request and URL response, and returns whether the
request was valid.
*/
public typealias Validation = (NSURLRequest?, NSHTTPURLResponse) -> ValidationResult
/**
Validates the request, using the specified closure.
If validation fails, subsequent calls to response handlers will have an associated error.
- parameter validation: A closure to validate the request.
- returns: The request.
*/
public func validate(validation: Validation) -> Self {
delegate.queue.addOperationWithBlock {
if let
response = self.response where self.delegate.error == nil,
case let .Failure(error) = validation(self.request, response)
{
self.delegate.error = error
}
}
return self
}
// MARK: - Status Code
/**
Validates that the response has a status code in the specified range.
If validation fails, subsequent calls to response handlers will have an associated error.
- parameter range: The range of acceptable status codes.
- returns: The request.
*/
public func validate<S: SequenceType where S.Generator.Element == Int>(statusCode acceptableStatusCode: S) -> Self {
return validate { _, response in
if acceptableStatusCode.contains(response.statusCode) {
return .Success
} else {
let failureReason = "Response status code was unacceptable: \(response.statusCode)"
let error = NSError(
domain: Error.Domain,
code: Error.Code.StatusCodeValidationFailed.rawValue,
userInfo: [
NSLocalizedFailureReasonErrorKey: failureReason,
Error.UserInfoKeys.StatusCode: response.statusCode
]
)
return .Failure(error)
}
}
}
// MARK: - Content-Type
private struct MIMEType {
let type: String
let subtype: String
init?(_ string: String) {
let components: [String] = {
let stripped = string.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
let split = stripped.substringToIndex(stripped.rangeOfString(";")?.startIndex ?? stripped.endIndex)
return split.componentsSeparatedByString("/")
}()
if let
type = components.first,
subtype = components.last
{
self.type = type
self.subtype = subtype
} else {
return nil
}
}
func matches(MIME: MIMEType) -> Bool {
switch (type, subtype) {
case (MIME.type, MIME.subtype), (MIME.type, "*"), ("*", MIME.subtype), ("*", "*"):
return true
default:
return false
}
}
}
/**
Validates that the response has a content type in the specified array.
If validation fails, subsequent calls to response handlers will have an associated error.
- parameter contentType: The acceptable content types, which may specify wildcard types and/or subtypes.
- returns: The request.
*/
public func validate<S : SequenceType where S.Generator.Element == String>(contentType acceptableContentTypes: S) -> Self {
return validate { _, response in
guard let validData = self.delegate.data where validData.length > 0 else { return .Success }
if let
responseContentType = response.MIMEType,
responseMIMEType = MIMEType(responseContentType)
{
for contentType in acceptableContentTypes {
if let acceptableMIMEType = MIMEType(contentType) where acceptableMIMEType.matches(responseMIMEType) {
return .Success
}
}
} else {
for contentType in acceptableContentTypes {
if let MIMEType = MIMEType(contentType) where MIMEType.type == "*" && MIMEType.subtype == "*" {
return .Success
}
}
}
let contentType: String
let failureReason: String
if let responseContentType = response.MIMEType {
contentType = responseContentType
failureReason = (
"Response content type \"\(responseContentType)\" does not match any acceptable " +
"content types: \(acceptableContentTypes)"
)
} else {
contentType = ""
failureReason = "Response content type was missing and acceptable content type does not match \"*/*\""
}
let error = NSError(
domain: Error.Domain,
code: Error.Code.ContentTypeValidationFailed.rawValue,
userInfo: [
NSLocalizedFailureReasonErrorKey: failureReason,
Error.UserInfoKeys.ContentType: contentType
]
)
return .Failure(error)
}
}
// MARK: - Automatic
/**
Validates that the response has a status code in the default acceptable range of 200...299, and that the content
type matches any specified in the Accept HTTP header field.
If validation fails, subsequent calls to response handlers will have an associated error.
- returns: The request.
*/
public func validate() -> Self {
let acceptableStatusCodes: Range<Int> = 200..<300
let acceptableContentTypes: [String] = {
if let accept = request?.valueForHTTPHeaderField("Accept") {
return accept.componentsSeparatedByString(",")
}
return ["*/*"]
}()
return validate(statusCode: acceptableStatusCodes).validate(contentType: acceptableContentTypes)
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Foundation/Foundation.h>
#import <Bolts/BFCancellationTokenRegistration.h>
NS_ASSUME_NONNULL_BEGIN
/*!
A block that will be called when a token is cancelled.
*/
typedef void(^BFCancellationBlock)();
/*!
The consumer view of a CancellationToken.
Propagates notification that operations should be canceled.
A BFCancellationToken has methods to inspect whether the token has been cancelled.
*/
@interface BFCancellationToken : NSObject
/*!
Whether cancellation has been requested for this token source.
*/
@property (nonatomic, assign, readonly, getter=isCancellationRequested) BOOL cancellationRequested;
/*!
Register a block to be notified when the token is cancelled.
If the token is already cancelled the delegate will be notified immediately.
*/
- (BFCancellationTokenRegistration *)registerCancellationObserverWithBlock:(BFCancellationBlock)block;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,144 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "BFCancellationToken.h"
#import "BFCancellationTokenRegistration.h"
NS_ASSUME_NONNULL_BEGIN
@interface BFCancellationToken ()
@property (nullable, nonatomic, strong) NSMutableArray *registrations;
@property (nonatomic, strong) NSObject *lock;
@property (nonatomic) BOOL disposed;
@end
@interface BFCancellationTokenRegistration (BFCancellationToken)
+ (instancetype)registrationWithToken:(BFCancellationToken *)token delegate:(BFCancellationBlock)delegate;
- (void)notifyDelegate;
@end
@implementation BFCancellationToken
@synthesize cancellationRequested = _cancellationRequested;
#pragma mark - Initializer
- (instancetype)init {
self = [super init];
if (!self) return self;
_registrations = [NSMutableArray array];
_lock = [NSObject new];
return self;
}
#pragma mark - Custom Setters/Getters
- (BOOL)isCancellationRequested {
@synchronized(self.lock) {
[self throwIfDisposed];
return _cancellationRequested;
}
}
- (void)cancel {
NSArray *registrations;
@synchronized(self.lock) {
[self throwIfDisposed];
if (_cancellationRequested) {
return;
}
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(cancelPrivate) object:nil];
_cancellationRequested = YES;
registrations = [self.registrations copy];
}
[self notifyCancellation:registrations];
}
- (void)notifyCancellation:(NSArray *)registrations {
for (BFCancellationTokenRegistration *registration in registrations) {
[registration notifyDelegate];
}
}
- (BFCancellationTokenRegistration *)registerCancellationObserverWithBlock:(BFCancellationBlock)block {
@synchronized(self.lock) {
BFCancellationTokenRegistration *registration = [BFCancellationTokenRegistration registrationWithToken:self delegate:[block copy]];
[self.registrations addObject:registration];
return registration;
}
}
- (void)unregisterRegistration:(BFCancellationTokenRegistration *)registration {
@synchronized(self.lock) {
[self throwIfDisposed];
[self.registrations removeObject:registration];
}
}
// Delay on a non-public method to prevent interference with a user calling performSelector or
// cancelPreviousPerformRequestsWithTarget on the public method
- (void)cancelPrivate {
[self cancel];
}
- (void)cancelAfterDelay:(int)millis {
[self throwIfDisposed];
if (millis < -1) {
[NSException raise:NSInvalidArgumentException format:@"Delay must be >= -1"];
}
if (millis == 0) {
[self cancel];
return;
}
@synchronized(self.lock) {
[self throwIfDisposed];
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(cancelPrivate) object:nil];
if (self.cancellationRequested) {
return;
}
if (millis != -1) {
double delay = (double)millis / 1000;
[self performSelector:@selector(cancelPrivate) withObject:nil afterDelay:delay];
}
}
}
- (void)dispose {
@synchronized(self.lock) {
if (self.disposed) {
return;
}
[self.registrations makeObjectsPerformSelector:@selector(dispose)];
self.registrations = nil;
self.disposed = YES;
}
}
- (void)throwIfDisposed {
if (self.disposed) {
[NSException raise:NSInternalInconsistencyException format:@"Object already disposed"];
}
}
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,29 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/*!
Represents the registration of a cancellation observer with a cancellation token.
Can be used to unregister the observer at a later time.
*/
@interface BFCancellationTokenRegistration : NSObject
/*!
Removes the cancellation observer registered with the token
and releases all resources associated with this registration.
*/
- (void)dispose;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,79 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "BFCancellationTokenRegistration.h"
#import "BFCancellationToken.h"
NS_ASSUME_NONNULL_BEGIN
@interface BFCancellationTokenRegistration ()
@property (nonatomic, weak) BFCancellationToken *token;
@property (nullable, nonatomic, strong) BFCancellationBlock cancellationObserverBlock;
@property (nonatomic, strong) NSObject *lock;
@property (nonatomic) BOOL disposed;
@end
@interface BFCancellationToken (BFCancellationTokenRegistration)
- (void)unregisterRegistration:(BFCancellationTokenRegistration *)registration;
@end
@implementation BFCancellationTokenRegistration
+ (instancetype)registrationWithToken:(BFCancellationToken *)token delegate:(BFCancellationBlock)delegate {
BFCancellationTokenRegistration *registration = [BFCancellationTokenRegistration new];
registration.token = token;
registration.cancellationObserverBlock = delegate;
return registration;
}
- (instancetype)init {
self = [super init];
if (!self) return self;
_lock = [NSObject new];
return self;
}
- (void)dispose {
@synchronized(self.lock) {
if (self.disposed) {
return;
}
self.disposed = YES;
}
BFCancellationToken *token = self.token;
if (token != nil) {
[token unregisterRegistration:self];
self.token = nil;
}
self.cancellationObserverBlock = nil;
}
- (void)notifyDelegate {
@synchronized(self.lock) {
[self throwIfDisposed];
self.cancellationObserverBlock();
}
}
- (void)throwIfDisposed {
NSAssert(!self.disposed, @"Object already disposed");
}
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,60 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class BFCancellationToken;
/*!
BFCancellationTokenSource represents the producer side of a CancellationToken.
Signals to a CancellationToken that it should be canceled.
It is a cancellation token that also has methods
for changing the state of a token by cancelling it.
*/
@interface BFCancellationTokenSource : NSObject
/*!
Creates a new cancellation token source.
*/
+ (instancetype)cancellationTokenSource;
/*!
The cancellation token associated with this CancellationTokenSource.
*/
@property (nonatomic, strong, readonly) BFCancellationToken *token;
/*!
Whether cancellation has been requested for this token source.
*/
@property (nonatomic, assign, readonly, getter=isCancellationRequested) BOOL cancellationRequested;
/*!
Cancels the token if it has not already been cancelled.
*/
- (void)cancel;
/*!
Schedules a cancel operation on this CancellationTokenSource after the specified number of milliseconds.
@param millis The number of milliseconds to wait before completing the returned task.
If delay is `0` the cancel is executed immediately. If delay is `-1` any scheduled cancellation is stopped.
*/
- (void)cancelAfterDelay:(int)millis;
/*!
Releases all resources associated with this token source,
including disposing of all registrations.
*/
- (void)dispose;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,64 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "BFCancellationTokenSource.h"
#import "BFCancellationToken.h"
NS_ASSUME_NONNULL_BEGIN
@interface BFCancellationToken (BFCancellationTokenSource)
- (void)cancel;
- (void)cancelAfterDelay:(int)millis;
- (void)dispose;
- (void)throwIfDisposed;
@end
@implementation BFCancellationTokenSource
#pragma mark - Initializer
- (instancetype)init {
self = [super init];
if (!self) return self;
_token = [BFCancellationToken new];
return self;
}
+ (instancetype)cancellationTokenSource {
return [BFCancellationTokenSource new];
}
#pragma mark - Custom Setters/Getters
- (BOOL)isCancellationRequested {
return _token.isCancellationRequested;
}
- (void)cancel {
[_token cancel];
}
- (void)cancelAfterDelay:(int)millis {
[_token cancelAfterDelay:millis];
}
- (void)dispose {
[_token dispose];
}
@end
NS_ASSUME_NONNULL_END

62
Pods/Bolts/Bolts/Common/BFExecutor.h generated Normal file
View File

@ -0,0 +1,62 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/*!
An object that can run a given block.
*/
@interface BFExecutor : NSObject
/*!
Returns a default executor, which runs continuations immediately until the call stack gets too
deep, then dispatches to a new GCD queue.
*/
+ (instancetype)defaultExecutor;
/*!
Returns an executor that runs continuations on the thread where the previous task was completed.
*/
+ (instancetype)immediateExecutor;
/*!
Returns an executor that runs continuations on the main thread.
*/
+ (instancetype)mainThreadExecutor;
/*!
Returns a new executor that uses the given block to execute continuations.
@param block The block to use.
*/
+ (instancetype)executorWithBlock:(void(^)(void(^block)()))block;
/*!
Returns a new executor that runs continuations on the given queue.
@param queue The instance of `dispatch_queue_t` to dispatch all continuations onto.
*/
+ (instancetype)executorWithDispatchQueue:(dispatch_queue_t)queue;
/*!
Returns a new executor that runs continuations on the given queue.
@param queue The instance of `NSOperationQueue` to run all continuations on.
*/
+ (instancetype)executorWithOperationQueue:(NSOperationQueue *)queue;
/*!
Runs the given block using this executor's particular strategy.
@param block The block to execute.
*/
- (void)execute:(void(^)())block;
@end
NS_ASSUME_NONNULL_END

136
Pods/Bolts/Bolts/Common/BFExecutor.m generated Normal file
View File

@ -0,0 +1,136 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "BFExecutor.h"
#import <pthread.h>
NS_ASSUME_NONNULL_BEGIN
/*!
Get the remaining stack-size of the current thread.
@param totalSize The total stack size of the current thread.
@return The remaining size, in bytes, available to the current thread.
@note This function cannot be inlined, as otherwise the internal implementation could fail to report the proper
remaining stack space.
*/
__attribute__((noinline)) static size_t remaining_stack_size(size_t *restrict totalSize) {
pthread_t currentThread = pthread_self();
// NOTE: We must store stack pointers as uint8_t so that the pointer math is well-defined
uint8_t *endStack = pthread_get_stackaddr_np(currentThread);
*totalSize = pthread_get_stacksize_np(currentThread);
// NOTE: If the function is inlined, this value could be incorrect
uint8_t *frameAddr = __builtin_frame_address(0);
return (*totalSize) - (endStack - frameAddr);
}
@interface BFExecutor ()
@property (nonatomic, copy) void(^block)(void(^block)());
@end
@implementation BFExecutor
#pragma mark - Executor methods
+ (instancetype)defaultExecutor {
static BFExecutor *defaultExecutor = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
defaultExecutor = [self executorWithBlock:^void(void(^block)()) {
// We prefer to run everything possible immediately, so that there is callstack information
// when debugging. However, we don't want the stack to get too deep, so if the remaining stack space
// is less than 10% of the total space, we dispatch to another GCD queue.
size_t totalStackSize = 0;
size_t remainingStackSize = remaining_stack_size(&totalStackSize);
if (remainingStackSize < (totalStackSize / 10)) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block);
} else {
@autoreleasepool {
block();
}
}
}];
});
return defaultExecutor;
}
+ (instancetype)immediateExecutor {
static BFExecutor *immediateExecutor = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
immediateExecutor = [self executorWithBlock:^void(void(^block)()) {
block();
}];
});
return immediateExecutor;
}
+ (instancetype)mainThreadExecutor {
static BFExecutor *mainThreadExecutor = NULL;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
mainThreadExecutor = [self executorWithBlock:^void(void(^block)()) {
if (![NSThread isMainThread]) {
dispatch_async(dispatch_get_main_queue(), block);
} else {
@autoreleasepool {
block();
}
}
}];
});
return mainThreadExecutor;
}
+ (instancetype)executorWithBlock:(void(^)(void(^block)()))block {
return [[self alloc] initWithBlock:block];
}
+ (instancetype)executorWithDispatchQueue:(dispatch_queue_t)queue {
return [self executorWithBlock:^void(void(^block)()) {
dispatch_async(queue, block);
}];
}
+ (instancetype)executorWithOperationQueue:(NSOperationQueue *)queue {
return [self executorWithBlock:^void(void(^block)()) {
[queue addOperation:[NSBlockOperation blockOperationWithBlock:block]];
}];
}
#pragma mark - Initializer
- (instancetype)initWithBlock:(void(^)(void(^block)()))block {
self = [super init];
if (!self) return self;
_block = block;
return self;
}
#pragma mark - Execution
- (void)execute:(void(^)())block {
self.block(block);
}
@end
NS_ASSUME_NONNULL_END

281
Pods/Bolts/Bolts/Common/BFTask.h generated Normal file
View File

@ -0,0 +1,281 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Foundation/Foundation.h>
#import <Bolts/BFCancellationToken.h>
NS_ASSUME_NONNULL_BEGIN
/*!
Error domain used if there was multiple errors on <BFTask taskForCompletionOfAllTasks:>.
*/
extern NSString *const BFTaskErrorDomain;
/*!
An error code used for <BFTask taskForCompletionOfAllTasks:>, if there were multiple errors.
*/
extern NSInteger const kBFMultipleErrorsError;
/*!
An exception that is thrown if there was multiple exceptions on <BFTask taskForCompletionOfAllTasks:>.
*/
extern NSString *const BFTaskMultipleExceptionsException;
/*!
An error userInfo key used if there were multiple errors on <BFTask taskForCompletionOfAllTasks:>.
Value type is `NSArray<NSError *> *`.
*/
extern NSString *const BFTaskMultipleErrorsUserInfoKey;
/*!
An error userInfo key used if there were multiple exceptions on <BFTask taskForCompletionOfAllTasks:>.
Value type is `NSArray<NSException *> *`.
*/
extern NSString *const BFTaskMultipleExceptionsUserInfoKey;
@class BFExecutor;
@class BFTask;
/*!
The consumer view of a Task. A BFTask has methods to
inspect the state of the task, and to add continuations to
be run once the task is complete.
*/
@interface BFTask<__covariant ResultType> : NSObject
/*!
A block that can act as a continuation for a task.
*/
typedef __nullable id(^BFContinuationBlock)(BFTask<ResultType> *task);
/*!
Creates a task that is already completed with the given result.
@param result The result for the task.
*/
+ (instancetype)taskWithResult:(nullable ResultType)result;
/*!
Creates a task that is already completed with the given error.
@param error The error for the task.
*/
+ (instancetype)taskWithError:(NSError *)error;
/*!
Creates a task that is already completed with the given exception.
@param exception The exception for the task.
*/
+ (instancetype)taskWithException:(NSException *)exception;
/*!
Creates a task that is already cancelled.
*/
+ (instancetype)cancelledTask;
/*!
Returns a task that will be completed (with result == nil) once
all of the input tasks have completed.
@param tasks An `NSArray` of the tasks to use as an input.
*/
+ (instancetype)taskForCompletionOfAllTasks:(nullable NSArray<BFTask *> *)tasks;
/*!
Returns a task that will be completed once all of the input tasks have completed.
If all tasks complete successfully without being faulted or cancelled the result will be
an `NSArray` of all task results in the order they were provided.
@param tasks An `NSArray` of the tasks to use as an input.
*/
+ (instancetype)taskForCompletionOfAllTasksWithResults:(nullable NSArray<BFTask *> *)tasks;
/*!
Returns a task that will be completed once there is at least one successful task.
The first task to successuly complete will set the result, all other tasks results are
ignored.
@param tasks An `NSArray` of the tasks to use as an input.
*/
+ (instancetype)taskForCompletionOfAnyTask:(nullable NSArray<BFTask *> *)tasks;
/*!
Returns a task that will be completed a certain amount of time in the future.
@param millis The approximate number of milliseconds to wait before the
task will be finished (with result == nil).
*/
+ (instancetype)taskWithDelay:(int)millis;
/*!
Returns a task that will be completed a certain amount of time in the future.
@param millis The approximate number of milliseconds to wait before the
task will be finished (with result == nil).
@param token The cancellation token (optional).
*/
+ (instancetype)taskWithDelay:(int)millis cancellationToken:(nullable BFCancellationToken *)token;
/*!
Returns a task that will be completed after the given block completes with
the specified executor.
@param executor A BFExecutor responsible for determining how the
continuation block will be run.
@param block The block to immediately schedule to run with the given executor.
@returns A task that will be completed after block has run.
If block returns a BFTask, then the task returned from
this method will not be completed until that task is completed.
*/
+ (instancetype)taskFromExecutor:(BFExecutor *)executor withBlock:(nullable id (^)())block;
// Properties that will be set on the task once it is completed.
/*!
The result of a successful task.
*/
@property (nullable, nonatomic, strong, readonly) ResultType result;
/*!
The error of a failed task.
*/
@property (nullable, nonatomic, strong, readonly) NSError *error;
/*!
The exception of a failed task.
*/
@property (nullable, nonatomic, strong, readonly) NSException *exception;
/*!
Whether this task has been cancelled.
*/
@property (nonatomic, assign, readonly, getter=isCancelled) BOOL cancelled;
/*!
Whether this task has completed due to an error or exception.
*/
@property (nonatomic, assign, readonly, getter=isFaulted) BOOL faulted;
/*!
Whether this task has completed.
*/
@property (nonatomic, assign, readonly, getter=isCompleted) BOOL completed;
/*!
Enqueues the given block to be run once this task is complete.
This method uses a default execution strategy. The block will be
run on the thread where the previous task completes, unless the
the stack depth is too deep, in which case it will be run on a
dispatch queue with default priority.
@param block The block to be run once this task is complete.
@returns A task that will be completed after block has run.
If block returns a BFTask, then the task returned from
this method will not be completed until that task is completed.
*/
- (BFTask *)continueWithBlock:(BFContinuationBlock)block;
/*!
Enqueues the given block to be run once this task is complete.
This method uses a default execution strategy. The block will be
run on the thread where the previous task completes, unless the
the stack depth is too deep, in which case it will be run on a
dispatch queue with default priority.
@param block The block to be run once this task is complete.
@param cancellationToken The cancellation token (optional).
@returns A task that will be completed after block has run.
If block returns a BFTask, then the task returned from
this method will not be completed until that task is completed.
*/
- (BFTask *)continueWithBlock:(BFContinuationBlock)block cancellationToken:(nullable BFCancellationToken *)cancellationToken;
/*!
Enqueues the given block to be run once this task is complete.
@param executor A BFExecutor responsible for determining how the
continuation block will be run.
@param block The block to be run once this task is complete.
@returns A task that will be completed after block has run.
If block returns a BFTask, then the task returned from
this method will not be completed until that task is completed.
*/
- (BFTask *)continueWithExecutor:(BFExecutor *)executor withBlock:(BFContinuationBlock)block;
/*!
Enqueues the given block to be run once this task is complete.
@param executor A BFExecutor responsible for determining how the
continuation block will be run.
@param block The block to be run once this task is complete.
@param cancellationToken The cancellation token (optional).
@returns A task that will be completed after block has run.
If block returns a BFTask, then the task returned from
his method will not be completed until that task is completed.
*/
- (BFTask *)continueWithExecutor:(BFExecutor *)executor
block:(BFContinuationBlock)block
cancellationToken:(nullable BFCancellationToken *)cancellationToken;
/*!
Identical to continueWithBlock:, except that the block is only run
if this task did not produce a cancellation, error, or exception.
If it did, then the failure will be propagated to the returned
task.
@param block The block to be run once this task is complete.
@returns A task that will be completed after block has run.
If block returns a BFTask, then the task returned from
this method will not be completed until that task is completed.
*/
- (BFTask *)continueWithSuccessBlock:(BFContinuationBlock)block;
/*!
Identical to continueWithBlock:, except that the block is only run
if this task did not produce a cancellation, error, or exception.
If it did, then the failure will be propagated to the returned
task.
@param block The block to be run once this task is complete.
@param cancellationToken The cancellation token (optional).
@returns A task that will be completed after block has run.
If block returns a BFTask, then the task returned from
this method will not be completed until that task is completed.
*/
- (BFTask *)continueWithSuccessBlock:(BFContinuationBlock)block cancellationToken:(nullable BFCancellationToken *)cancellationToken;
/*!
Identical to continueWithExecutor:withBlock:, except that the block
is only run if this task did not produce a cancellation, error, or
exception. If it did, then the failure will be propagated to the
returned task.
@param executor A BFExecutor responsible for determining how the
continuation block will be run.
@param block The block to be run once this task is complete.
@returns A task that will be completed after block has run.
If block returns a BFTask, then the task returned from
this method will not be completed until that task is completed.
*/
- (BFTask *)continueWithExecutor:(BFExecutor *)executor withSuccessBlock:(BFContinuationBlock)block;
/*!
Identical to continueWithExecutor:withBlock:, except that the block
is only run if this task did not produce a cancellation, error, or
exception. If it did, then the failure will be propagated to the
returned task.
@param executor A BFExecutor responsible for determining how the
continuation block will be run.
@param block The block to be run once this task is complete.
@param cancellationToken The cancellation token (optional).
@returns A task that will be completed after block has run.
If block returns a BFTask, then the task returned from
this method will not be completed until that task is completed.
*/
- (BFTask *)continueWithExecutor:(BFExecutor *)executor
successBlock:(BFContinuationBlock)block
cancellationToken:(nullable BFCancellationToken *)cancellationToken;
/*!
Waits until this operation is completed.
This method is inefficient and consumes a thread resource while
it's running. It should be avoided. This method logs a warning
message if it is used on the main thread.
*/
- (void)waitUntilFinished;
@end
NS_ASSUME_NONNULL_END

535
Pods/Bolts/Bolts/Common/BFTask.m generated Normal file
View File

@ -0,0 +1,535 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "BFTask.h"
#import <libkern/OSAtomic.h>
#import "Bolts.h"
NS_ASSUME_NONNULL_BEGIN
__attribute__ ((noinline)) void warnBlockingOperationOnMainThread() {
NSLog(@"Warning: A long-running operation is being executed on the main thread. \n"
" Break on warnBlockingOperationOnMainThread() to debug.");
}
NSString *const BFTaskErrorDomain = @"bolts";
NSInteger const kBFMultipleErrorsError = 80175001;
NSString *const BFTaskMultipleExceptionsException = @"BFMultipleExceptionsException";
NSString *const BFTaskMultipleErrorsUserInfoKey = @"errors";
NSString *const BFTaskMultipleExceptionsUserInfoKey = @"exceptions";
@interface BFTask () {
id _result;
NSError *_error;
NSException *_exception;
}
@property (nonatomic, assign, readwrite, getter=isCancelled) BOOL cancelled;
@property (nonatomic, assign, readwrite, getter=isFaulted) BOOL faulted;
@property (nonatomic, assign, readwrite, getter=isCompleted) BOOL completed;
@property (nonatomic, strong) NSObject *lock;
@property (nonatomic, strong) NSCondition *condition;
@property (nonatomic, strong) NSMutableArray *callbacks;
@end
@implementation BFTask
#pragma mark - Initializer
- (instancetype)init {
self = [super init];
if (!self) return self;
_lock = [[NSObject alloc] init];
_condition = [[NSCondition alloc] init];
_callbacks = [NSMutableArray array];
return self;
}
- (instancetype)initWithResult:(id)result {
self = [super init];
if (!self) return self;
[self trySetResult:result];
return self;
}
- (instancetype)initWithError:(NSError *)error {
self = [super init];
if (!self) return self;
[self trySetError:error];
return self;
}
- (instancetype)initWithException:(NSException *)exception {
self = [super init];
if (!self) return self;
[self trySetException:exception];
return self;
}
- (instancetype)initCancelled {
self = [super init];
if (!self) return self;
[self trySetCancelled];
return self;
}
#pragma mark - Task Class methods
+ (instancetype)taskWithResult:(nullable id)result {
return [[self alloc] initWithResult:result];
}
+ (instancetype)taskWithError:(NSError *)error {
return [[self alloc] initWithError:error];
}
+ (instancetype)taskWithException:(NSException *)exception {
return [[self alloc] initWithException:exception];
}
+ (instancetype)cancelledTask {
return [[self alloc] initCancelled];
}
+ (instancetype)taskForCompletionOfAllTasks:(nullable NSArray<BFTask *> *)tasks {
__block int32_t total = (int32_t)tasks.count;
if (total == 0) {
return [self taskWithResult:nil];
}
__block int32_t cancelled = 0;
NSObject *lock = [[NSObject alloc] init];
NSMutableArray *errors = [NSMutableArray array];
NSMutableArray *exceptions = [NSMutableArray array];
BFTaskCompletionSource *tcs = [BFTaskCompletionSource taskCompletionSource];
for (BFTask *task in tasks) {
[task continueWithBlock:^id(BFTask *task) {
if (task.exception) {
@synchronized (lock) {
[exceptions addObject:task.exception];
}
} else if (task.error) {
@synchronized (lock) {
[errors addObject:task.error];
}
} else if (task.cancelled) {
OSAtomicIncrement32Barrier(&cancelled);
}
if (OSAtomicDecrement32Barrier(&total) == 0) {
if (exceptions.count > 0) {
if (exceptions.count == 1) {
tcs.exception = [exceptions firstObject];
} else {
NSException *exception =
[NSException exceptionWithName:BFTaskMultipleExceptionsException
reason:@"There were multiple exceptions."
userInfo:@{ BFTaskMultipleExceptionsUserInfoKey: exceptions }];
tcs.exception = exception;
}
} else if (errors.count > 0) {
if (errors.count == 1) {
tcs.error = [errors firstObject];
} else {
NSError *error = [NSError errorWithDomain:BFTaskErrorDomain
code:kBFMultipleErrorsError
userInfo:@{ BFTaskMultipleErrorsUserInfoKey: errors }];
tcs.error = error;
}
} else if (cancelled > 0) {
[tcs cancel];
} else {
tcs.result = nil;
}
}
return nil;
}];
}
return tcs.task;
}
+ (instancetype)taskForCompletionOfAllTasksWithResults:(nullable NSArray<BFTask *> *)tasks {
return [[self taskForCompletionOfAllTasks:tasks] continueWithSuccessBlock:^id(BFTask *task) {
return [tasks valueForKey:@"result"];
}];
}
+ (instancetype)taskForCompletionOfAnyTask:(nullable NSArray<BFTask *> *)tasks
{
__block int32_t total = (int32_t)tasks.count;
if (total == 0) {
return [self taskWithResult:nil];
}
__block int completed = 0;
__block int32_t cancelled = 0;
NSObject *lock = [NSObject new];
NSMutableArray<NSError *> *errors = [NSMutableArray new];
NSMutableArray<NSException *> *exceptions = [NSMutableArray new];
BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource];
for (BFTask *task in tasks) {
[task continueWithBlock:^id(BFTask *task) {
if (task.exception != nil) {
@synchronized(lock) {
[exceptions addObject:task.exception];
}
} else if (task.error != nil) {
@synchronized(lock) {
[errors addObject:task.error];
}
} else if (task.cancelled) {
OSAtomicIncrement32Barrier(&cancelled);
} else {
if(OSAtomicCompareAndSwap32Barrier(0, 1, &completed)) {
[source setResult:task.result];
}
}
if (OSAtomicDecrement32Barrier(&total) == 0 &&
OSAtomicCompareAndSwap32Barrier(0, 1, &completed)) {
if (cancelled > 0) {
[source cancel];
} else if (exceptions.count > 0) {
if (exceptions.count == 1) {
source.exception = exceptions.firstObject;
} else {
NSException *exception =
[NSException exceptionWithName:BFTaskMultipleExceptionsException
reason:@"There were multiple exceptions."
userInfo:@{ @"exceptions": exceptions }];
source.exception = exception;
}
} else if (errors.count > 0) {
if (errors.count == 1) {
source.error = errors.firstObject;
} else {
NSError *error = [NSError errorWithDomain:BFTaskErrorDomain
code:kBFMultipleErrorsError
userInfo:@{ @"errors": errors }];
source.error = error;
}
}
}
// Abort execution of per tasks continuations
return nil;
}];
}
return source.task;
}
+ (instancetype)taskWithDelay:(int)millis {
BFTaskCompletionSource *tcs = [BFTaskCompletionSource taskCompletionSource];
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, millis * NSEC_PER_MSEC);
dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
tcs.result = nil;
});
return tcs.task;
}
+ (instancetype)taskWithDelay:(int)millis cancellationToken:(nullable BFCancellationToken *)token {
if (token.cancellationRequested) {
return [BFTask cancelledTask];
}
BFTaskCompletionSource *tcs = [BFTaskCompletionSource taskCompletionSource];
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, millis * NSEC_PER_MSEC);
dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
if (token.cancellationRequested) {
[tcs cancel];
return;
}
tcs.result = nil;
});
return tcs.task;
}
+ (instancetype)taskFromExecutor:(BFExecutor *)executor withBlock:(nullable id (^)())block {
return [[self taskWithResult:nil] continueWithExecutor:executor withBlock:^id(BFTask *task) {
return block();
}];
}
#pragma mark - Custom Setters/Getters
- (nullable id)result {
@synchronized(self.lock) {
return _result;
}
}
- (BOOL)trySetResult:(nullable id)result {
@synchronized(self.lock) {
if (self.completed) {
return NO;
}
self.completed = YES;
_result = result;
[self runContinuations];
return YES;
}
}
- (nullable NSError *)error {
@synchronized(self.lock) {
return _error;
}
}
- (BOOL)trySetError:(NSError *)error {
@synchronized(self.lock) {
if (self.completed) {
return NO;
}
self.completed = YES;
self.faulted = YES;
_error = error;
[self runContinuations];
return YES;
}
}
- (nullable NSException *)exception {
@synchronized(self.lock) {
return _exception;
}
}
- (BOOL)trySetException:(NSException *)exception {
@synchronized(self.lock) {
if (self.completed) {
return NO;
}
self.completed = YES;
self.faulted = YES;
_exception = exception;
[self runContinuations];
return YES;
}
}
- (BOOL)isCancelled {
@synchronized(self.lock) {
return _cancelled;
}
}
- (BOOL)isFaulted {
@synchronized(self.lock) {
return _faulted;
}
}
- (BOOL)trySetCancelled {
@synchronized(self.lock) {
if (self.completed) {
return NO;
}
self.completed = YES;
self.cancelled = YES;
[self runContinuations];
return YES;
}
}
- (BOOL)isCompleted {
@synchronized(self.lock) {
return _completed;
}
}
- (void)runContinuations {
@synchronized(self.lock) {
[self.condition lock];
[self.condition broadcast];
[self.condition unlock];
for (void (^callback)() in self.callbacks) {
callback();
}
[self.callbacks removeAllObjects];
}
}
#pragma mark - Chaining methods
- (BFTask *)continueWithExecutor:(BFExecutor *)executor withBlock:(BFContinuationBlock)block {
return [self continueWithExecutor:executor block:block cancellationToken:nil];
}
- (BFTask *)continueWithExecutor:(BFExecutor *)executor
block:(BFContinuationBlock)block
cancellationToken:(nullable BFCancellationToken *)cancellationToken {
BFTaskCompletionSource *tcs = [BFTaskCompletionSource taskCompletionSource];
// Capture all of the state that needs to used when the continuation is complete.
dispatch_block_t executionBlock = ^{
if (cancellationToken.cancellationRequested) {
[tcs cancel];
return;
}
id result = nil;
@try {
result = block(self);
} @catch (NSException *exception) {
tcs.exception = exception;
return;
}
if ([result isKindOfClass:[BFTask class]]) {
id (^setupWithTask) (BFTask *) = ^id(BFTask *task) {
if (cancellationToken.cancellationRequested || task.cancelled) {
[tcs cancel];
} else if (task.exception) {
tcs.exception = task.exception;
} else if (task.error) {
tcs.error = task.error;
} else {
tcs.result = task.result;
}
return nil;
};
BFTask *resultTask = (BFTask *)result;
if (resultTask.completed) {
setupWithTask(resultTask);
} else {
[resultTask continueWithBlock:setupWithTask];
}
} else {
tcs.result = result;
}
};
BOOL completed;
@synchronized(self.lock) {
completed = self.completed;
if (!completed) {
[self.callbacks addObject:[^{
[executor execute:executionBlock];
} copy]];
}
}
if (completed) {
[executor execute:executionBlock];
}
return tcs.task;
}
- (BFTask *)continueWithBlock:(BFContinuationBlock)block {
return [self continueWithExecutor:[BFExecutor defaultExecutor] block:block cancellationToken:nil];
}
- (BFTask *)continueWithBlock:(BFContinuationBlock)block cancellationToken:(nullable BFCancellationToken *)cancellationToken {
return [self continueWithExecutor:[BFExecutor defaultExecutor] block:block cancellationToken:cancellationToken];
}
- (BFTask *)continueWithExecutor:(BFExecutor *)executor
withSuccessBlock:(BFContinuationBlock)block {
return [self continueWithExecutor:executor successBlock:block cancellationToken:nil];
}
- (BFTask *)continueWithExecutor:(BFExecutor *)executor
successBlock:(BFContinuationBlock)block
cancellationToken:(nullable BFCancellationToken *)cancellationToken {
if (cancellationToken.cancellationRequested) {
return [BFTask cancelledTask];
}
return [self continueWithExecutor:executor block:^id(BFTask *task) {
if (task.faulted || task.cancelled) {
return task;
} else {
return block(task);
}
} cancellationToken:cancellationToken];
}
- (BFTask *)continueWithSuccessBlock:(BFContinuationBlock)block {
return [self continueWithExecutor:[BFExecutor defaultExecutor] successBlock:block cancellationToken:nil];
}
- (BFTask *)continueWithSuccessBlock:(BFContinuationBlock)block cancellationToken:(nullable BFCancellationToken *)cancellationToken {
return [self continueWithExecutor:[BFExecutor defaultExecutor] successBlock:block cancellationToken:cancellationToken];
}
#pragma mark - Syncing Task (Avoid it)
- (void)warnOperationOnMainThread {
warnBlockingOperationOnMainThread();
}
- (void)waitUntilFinished {
if ([NSThread isMainThread]) {
[self warnOperationOnMainThread];
}
@synchronized(self.lock) {
if (self.completed) {
return;
}
[self.condition lock];
}
[self.condition wait];
[self.condition unlock];
}
#pragma mark - NSObject
- (NSString *)description {
// Acquire the data from the locked properties
BOOL completed;
BOOL cancelled;
BOOL faulted;
NSString *resultDescription = nil;
@synchronized(self.lock) {
completed = self.completed;
cancelled = self.cancelled;
faulted = self.faulted;
resultDescription = completed ? [NSString stringWithFormat:@" result = %@", self.result] : @"";
}
// Description string includes status information and, if available, the
// result since in some ways this is what a promise actually "is".
return [NSString stringWithFormat:@"<%@: %p; completed = %@; cancelled = %@; faulted = %@;%@>",
NSStringFromClass([self class]),
self,
completed ? @"YES" : @"NO",
cancelled ? @"YES" : @"NO",
faulted ? @"YES" : @"NO",
resultDescription];
}
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,89 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class BFTask<ResultType>;
/*!
A BFTaskCompletionSource represents the producer side of tasks.
It is a task that also has methods for changing the state of the
task by settings its completion values.
*/
@interface BFTaskCompletionSource<__covariant ResultType> : NSObject
/*!
Creates a new unfinished task.
*/
+ (instancetype)taskCompletionSource;
/*!
The task associated with this TaskCompletionSource.
*/
@property (nonatomic, strong, readonly) BFTask<ResultType> *task;
/*!
Completes the task by setting the result.
Attempting to set this for a completed task will raise an exception.
@param result The result of the task.
*/
- (void)setResult:(nullable ResultType)result;
/*!
Completes the task by setting the error.
Attempting to set this for a completed task will raise an exception.
@param error The error for the task.
*/
- (void)setError:(NSError *)error;
/*!
Completes the task by setting an exception.
Attempting to set this for a completed task will raise an exception.
@param exception The exception for the task.
*/
- (void)setException:(NSException *)exception;
/*!
Completes the task by marking it as cancelled.
Attempting to set this for a completed task will raise an exception.
*/
- (void)cancel;
/*!
Sets the result of the task if it wasn't already completed.
@returns whether the new value was set.
*/
- (BOOL)trySetResult:(nullable ResultType)result;
/*!
Sets the error of the task if it wasn't already completed.
@param error The error for the task.
@returns whether the new value was set.
*/
- (BOOL)trySetError:(NSError *)error;
/*!
Sets the exception of the task if it wasn't already completed.
@param exception The exception for the task.
@returns whether the new value was set.
*/
- (BOOL)trySetException:(NSException *)exception;
/*!
Sets the cancellation state of the task if it wasn't already completed.
@returns whether the new value was set.
*/
- (BOOL)trySetCancelled;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,91 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "BFTaskCompletionSource.h"
#import "BFTask.h"
NS_ASSUME_NONNULL_BEGIN
@interface BFTask (BFTaskCompletionSource)
- (BOOL)trySetResult:(nullable id)result;
- (BOOL)trySetError:(NSError *)error;
- (BOOL)trySetException:(NSException *)exception;
- (BOOL)trySetCancelled;
@end
@implementation BFTaskCompletionSource
#pragma mark - Initializer
+ (instancetype)taskCompletionSource {
return [[self alloc] init];
}
- (instancetype)init {
self = [super init];
if (!self) return self;
_task = [[BFTask alloc] init];
return self;
}
#pragma mark - Custom Setters/Getters
- (void)setResult:(nullable id)result {
if (![self.task trySetResult:result]) {
[NSException raise:NSInternalInconsistencyException
format:@"Cannot set the result on a completed task."];
}
}
- (void)setError:(NSError *)error {
if (![self.task trySetError:error]) {
[NSException raise:NSInternalInconsistencyException
format:@"Cannot set the error on a completed task."];
}
}
- (void)setException:(NSException *)exception {
if (![self.task trySetException:exception]) {
[NSException raise:NSInternalInconsistencyException
format:@"Cannot set the exception on a completed task."];
}
}
- (void)cancel {
if (![self.task trySetCancelled]) {
[NSException raise:NSInternalInconsistencyException
format:@"Cannot cancel a completed task."];
}
}
- (BOOL)trySetResult:(nullable id)result {
return [self.task trySetResult:result];
}
- (BOOL)trySetError:(NSError *)error {
return [self.task trySetError:error];
}
- (BOOL)trySetException:(NSException *)exception {
return [self.task trySetException:exception];
}
- (BOOL)trySetCancelled {
return [self.task trySetCancelled];
}
@end
NS_ASSUME_NONNULL_END

38
Pods/Bolts/Bolts/Common/Bolts.h generated Normal file
View File

@ -0,0 +1,38 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Bolts/BFCancellationToken.h>
#import <Bolts/BFCancellationTokenRegistration.h>
#import <Bolts/BFCancellationTokenSource.h>
#import <Bolts/BFExecutor.h>
#import <Bolts/BFTask.h>
#import <Bolts/BFTaskCompletionSource.h>
#if __has_include(<Bolts/BFAppLink.h>) && TARGET_OS_IPHONE && !TARGET_OS_WATCH && !TARGET_OS_TV
#import <Bolts/BFAppLink.h>
#import <Bolts/BFAppLinkNavigation.h>
#import <Bolts/BFAppLinkResolving.h>
#import <Bolts/BFAppLinkReturnToRefererController.h>
#import <Bolts/BFAppLinkReturnToRefererView.h>
#import <Bolts/BFAppLinkTarget.h>
#import <Bolts/BFMeasurementEvent.h>
#import <Bolts/BFURL.h>
#import <Bolts/BFWebViewAppLinkResolver.h>
#endif
NS_ASSUME_NONNULL_BEGIN
/**
A string containing the version of the Bolts Framework used by the current application.
*/
extern NSString *const BoltsFrameworkVersionString;
NS_ASSUME_NONNULL_END

17
Pods/Bolts/Bolts/Common/Bolts.m generated Normal file
View File

@ -0,0 +1,17 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "Bolts.h"
NS_ASSUME_NONNULL_BEGIN
NSString *const BoltsFrameworkVersionString = @"1.7.0";
NS_ASSUME_NONNULL_END

49
Pods/Bolts/Bolts/iOS/BFAppLink.h generated Normal file
View File

@ -0,0 +1,49 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Foundation/Foundation.h>
/*! The version of the App Link protocol that this library supports */
FOUNDATION_EXPORT NSString *const BFAppLinkVersion;
/*!
Contains App Link metadata relevant for navigation on this device
derived from the HTML at a given URL.
*/
@interface BFAppLink : NSObject
/*!
Creates a BFAppLink with the given list of BFAppLinkTargets and target URL.
Generally, this will only be used by implementers of the BFAppLinkResolving protocol,
as these implementers will produce App Link metadata for a given URL.
@param sourceURL the URL from which this App Link is derived
@param targets an ordered list of BFAppLinkTargets for this platform derived
from App Link metadata.
@param webURL the fallback web URL, if any, for the app link.
*/
+ (instancetype)appLinkWithSourceURL:(NSURL *)sourceURL
targets:(NSArray *)targets
webURL:(NSURL *)webURL;
/*! The URL from which this BFAppLink was derived */
@property (nonatomic, strong, readonly) NSURL *sourceURL;
/*!
The ordered list of targets applicable to this platform that will be used
for navigation.
*/
@property (nonatomic, copy, readonly) NSArray *targets;
/*! The fallback web URL to use if no targets are installed on this device. */
@property (nonatomic, strong, readonly) NSURL *webURL;
@end

62
Pods/Bolts/Bolts/iOS/BFAppLink.m generated Normal file
View File

@ -0,0 +1,62 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "BFAppLink_Internal.h"
NSString *const BFAppLinkDataParameterName = @"al_applink_data";
NSString *const BFAppLinkTargetKeyName = @"target_url";
NSString *const BFAppLinkUserAgentKeyName = @"user_agent";
NSString *const BFAppLinkExtrasKeyName = @"extras";
NSString *const BFAppLinkRefererAppLink = @"referer_app_link";
NSString *const BFAppLinkRefererAppName = @"app_name";
NSString *const BFAppLinkRefererUrl = @"url";
NSString *const BFAppLinkVersionKeyName = @"version";
NSString *const BFAppLinkVersion = @"1.0";
@interface BFAppLink ()
@property (nonatomic, strong, readwrite) NSURL *sourceURL;
@property (nonatomic, copy, readwrite) NSArray *targets;
@property (nonatomic, strong, readwrite) NSURL *webURL;
@property (nonatomic, assign, readwrite, getter=isBackToReferrer) BOOL backToReferrer;
@end
@implementation BFAppLink
+ (instancetype)appLinkWithSourceURL:(NSURL *)sourceURL
targets:(NSArray *)targets
webURL:(NSURL *)webURL
isBackToReferrer:(BOOL)isBackToReferrer {
BFAppLink *link = [[self alloc] initWithIsBackToReferrer:isBackToReferrer];
link.sourceURL = sourceURL;
link.targets = [targets copy];
link.webURL = webURL;
return link;
}
+ (instancetype)appLinkWithSourceURL:(NSURL *)sourceURL
targets:(NSArray *)targets
webURL:(NSURL *)webURL {
return [self appLinkWithSourceURL:sourceURL
targets:targets
webURL:webURL
isBackToReferrer:NO];
}
- (BFAppLink *)initWithIsBackToReferrer:(BOOL)backToReferrer {
if ((self = [super init])) {
_backToReferrer = backToReferrer;
}
return self;
}
@end

View File

@ -0,0 +1,93 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Foundation/Foundation.h>
#import <Bolts/BFAppLink.h>
/*!
The result of calling navigate on a BFAppLinkNavigation
*/
typedef NS_ENUM(NSInteger, BFAppLinkNavigationType) {
/*! Indicates that the navigation failed and no app was opened */
BFAppLinkNavigationTypeFailure,
/*! Indicates that the navigation succeeded by opening the URL in the browser */
BFAppLinkNavigationTypeBrowser,
/*! Indicates that the navigation succeeded by opening the URL in an app on the device */
BFAppLinkNavigationTypeApp
};
@protocol BFAppLinkResolving;
@class BFTask;
/*!
Represents a pending request to navigate to an App Link. Most developers will
simply use navigateToURLInBackground: to open a URL, but developers can build
custom requests with additional navigation and app data attached to them by
creating BFAppLinkNavigations themselves.
*/
@interface BFAppLinkNavigation : NSObject
/*!
The extras for the AppLinkNavigation. This will generally contain application-specific
data that should be passed along with the request, such as advertiser or affiliate IDs or
other such metadata relevant on this device.
*/
@property (nonatomic, copy, readonly) NSDictionary *extras;
/*!
The al_applink_data for the AppLinkNavigation. This will generally contain data common to
navigation attempts such as back-links, user agents, and other information that may be used
in routing and handling an App Link request.
*/
@property (nonatomic, copy, readonly) NSDictionary *appLinkData;
/*! The AppLink to navigate to */
@property (nonatomic, strong, readonly) BFAppLink *appLink;
/*! Creates an AppLinkNavigation with the given link, extras, and App Link data */
+ (instancetype)navigationWithAppLink:(BFAppLink *)appLink
extras:(NSDictionary *)extras
appLinkData:(NSDictionary *)appLinkData;
/*! Performs the navigation */
- (BFAppLinkNavigationType)navigate:(NSError **)error;
/*! Returns a BFAppLink for the given URL */
+ (BFTask *)resolveAppLinkInBackground:(NSURL *)destination;
/*! Returns a BFAppLink for the given URL using the given App Link resolution strategy */
+ (BFTask *)resolveAppLinkInBackground:(NSURL *)destination resolver:(id<BFAppLinkResolving>)resolver;
/*! Navigates to a BFAppLink and returns whether it opened in-app or in-browser */
+ (BFAppLinkNavigationType)navigateToAppLink:(BFAppLink *)link error:(NSError **)error;
/*! Navigates to a URL (an asynchronous action) and returns a BFNavigationType */
+ (BFTask *)navigateToURLInBackground:(NSURL *)destination;
/*!
Navigates to a URL (an asynchronous action) using the given App Link resolution
strategy and returns a BFNavigationType
*/
+ (BFTask *)navigateToURLInBackground:(NSURL *)destination resolver:(id<BFAppLinkResolving>)resolver;
/*!
Gets the default resolver to be used for App Link resolution. If the developer has not set one explicitly,
a basic, built-in resolver will be used.
*/
+ (id<BFAppLinkResolving>)defaultResolver;
/*!
Sets the default resolver to be used for App Link resolution. Setting this to nil will revert the
default resolver to the basic, built-in resolver provided by Bolts.
*/
+ (void)setDefaultResolver:(id<BFAppLinkResolving>)resolver;
@end

243
Pods/Bolts/Bolts/iOS/BFAppLinkNavigation.m generated Normal file
View File

@ -0,0 +1,243 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <UIKit/UIKit.h>
#import <Bolts/Bolts.h>
#import "BFMeasurementEvent_Internal.h"
#import "BFAppLink_Internal.h"
FOUNDATION_EXPORT NSString *const BFAppLinkDataParameterName;
FOUNDATION_EXPORT NSString *const BFAppLinkTargetKeyName;
FOUNDATION_EXPORT NSString *const BFAppLinkUserAgentKeyName;
FOUNDATION_EXPORT NSString *const BFAppLinkExtrasKeyName;
FOUNDATION_EXPORT NSString *const BFAppLinkVersionKeyName;
static id<BFAppLinkResolving> defaultResolver;
@interface BFAppLinkNavigation ()
@property (nonatomic, copy, readwrite) NSDictionary *extras;
@property (nonatomic, copy, readwrite) NSDictionary *appLinkData;
@property (nonatomic, strong, readwrite) BFAppLink *appLink;
@end
@implementation BFAppLinkNavigation
+ (instancetype)navigationWithAppLink:(BFAppLink *)appLink
extras:(NSDictionary *)extras
appLinkData:(NSDictionary *)appLinkData {
BFAppLinkNavigation *navigation = [[self alloc] init];
navigation.appLink = appLink;
navigation.extras = extras;
navigation.appLinkData = appLinkData;
return navigation;
}
- (NSString *)stringByEscapingQueryString:(NSString *)string {
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0 || __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_9
return [string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
#else
return (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL,
(CFStringRef)string,
NULL,
(CFStringRef) @":/?#[]@!$&'()*+,;=",
kCFStringEncodingUTF8));
#endif
}
- (NSURL *)appLinkURLWithTargetURL:(NSURL *)targetUrl error:(NSError **)error {
NSMutableDictionary *appLinkData = [NSMutableDictionary dictionaryWithDictionary:self.appLinkData ?: @{}];
// Add applink protocol data
if (!appLinkData[BFAppLinkUserAgentKeyName]) {
appLinkData[BFAppLinkUserAgentKeyName] = [NSString stringWithFormat:@"Bolts iOS %@", BoltsFrameworkVersionString];
}
if (!appLinkData[BFAppLinkVersionKeyName]) {
appLinkData[BFAppLinkVersionKeyName] = BFAppLinkVersion;
}
appLinkData[BFAppLinkTargetKeyName] = [self.appLink.sourceURL absoluteString];
appLinkData[BFAppLinkExtrasKeyName] = self.extras ?: @{};
// JSON-ify the applink data
NSError *jsonError = nil;
NSData *jsonBlob = [NSJSONSerialization dataWithJSONObject:appLinkData options:0 error:&jsonError];
if (!jsonError) {
NSString *jsonString = [[NSString alloc] initWithData:jsonBlob encoding:NSUTF8StringEncoding];
NSString *encoded = [self stringByEscapingQueryString:jsonString];
NSString *endUrlString = [NSString stringWithFormat:@"%@%@%@=%@",
[targetUrl absoluteString],
targetUrl.query ? @"&" : @"?",
BFAppLinkDataParameterName,
encoded];
return [NSURL URLWithString:endUrlString];
} else {
if (error) {
*error = jsonError;
}
// If there was an error encoding the app link data, fail hard.
return nil;
}
}
- (BFAppLinkNavigationType)navigate:(NSError **)error {
NSURL *openedURL = nil;
NSError *encodingError = nil;
BFAppLinkNavigationType retType = BFAppLinkNavigationTypeFailure;
// Find the first eligible/launchable target in the BFAppLink.
for (BFAppLinkTarget *target in self.appLink.targets) {
NSURL *appLinkAppURL = [self appLinkURLWithTargetURL:target.URL error:&encodingError];
if (encodingError || !appLinkAppURL) {
if (error) {
*error = encodingError;
}
} else if ([[UIApplication sharedApplication] openURL:appLinkAppURL]) {
retType = BFAppLinkNavigationTypeApp;
openedURL = appLinkAppURL;
break;
}
}
if (!openedURL && self.appLink.webURL) {
// Fall back to opening the url in the browser if available.
NSURL *appLinkBrowserURL = [self appLinkURLWithTargetURL:self.appLink.webURL error:&encodingError];
if (encodingError || !appLinkBrowserURL) {
// If there was an error encoding the app link data, fail hard.
if (error) {
*error = encodingError;
}
} else if ([[UIApplication sharedApplication] openURL:appLinkBrowserURL]) {
// This was a browser navigation.
retType = BFAppLinkNavigationTypeBrowser;
openedURL = appLinkBrowserURL;
}
}
[self postAppLinkNavigateEventNotificationWithTargetURL:openedURL
error:error ? *error : nil
type:retType];
return retType;
}
- (void)postAppLinkNavigateEventNotificationWithTargetURL:(NSURL *)outputURL error:(NSError *)error type:(BFAppLinkNavigationType)type {
NSString *const EVENT_YES_VAL = @"1";
NSString *const EVENT_NO_VAL = @"0";
NSMutableDictionary *logData = [[NSMutableDictionary alloc] init];
NSString *outputURLScheme = [outputURL scheme];
NSString *outputURLString = [outputURL absoluteString];
if (outputURLScheme) {
logData[@"outputURLScheme"] = outputURLScheme;
}
if (outputURLString) {
logData[@"outputURL"] = outputURLString;
}
NSString *sourceURLString = [self.appLink.sourceURL absoluteString];
NSString *sourceURLHost = [self.appLink.sourceURL host];
NSString *sourceURLScheme = [self.appLink.sourceURL scheme];
if (sourceURLString) {
logData[@"sourceURL"] = sourceURLString;
}
if (sourceURLHost) {
logData[@"sourceHost"] = sourceURLHost;
}
if (sourceURLScheme) {
logData[@"sourceScheme"] = sourceURLScheme;
}
if ([error localizedDescription]) {
logData[@"error"] = [error localizedDescription];
}
NSString *success = nil; //no
NSString *linkType = nil; // unknown;
switch (type) {
case BFAppLinkNavigationTypeFailure:
success = EVENT_NO_VAL;
linkType = @"fail";
break;
case BFAppLinkNavigationTypeBrowser:
success = EVENT_YES_VAL;
linkType = @"web";
break;
case BFAppLinkNavigationTypeApp:
success = EVENT_YES_VAL;
linkType = @"app";
break;
default:
break;
}
if (success) {
logData[@"success"] = success;
}
if (linkType) {
logData[@"type"] = linkType;
}
if ([self.appLink isBackToReferrer]) {
[BFMeasurementEvent postNotificationForEventName:BFAppLinkNavigateBackToReferrerEventName args:logData];
} else {
[BFMeasurementEvent postNotificationForEventName:BFAppLinkNavigateOutEventName args:logData];
}
}
+ (BFTask *)resolveAppLinkInBackground:(NSURL *)destination resolver:(id<BFAppLinkResolving>)resolver {
return [resolver appLinkFromURLInBackground:destination];
}
+ (BFTask *)resolveAppLinkInBackground:(NSURL *)destination {
return [self resolveAppLinkInBackground:destination resolver:[self defaultResolver]];
}
+ (BFTask *)navigateToURLInBackground:(NSURL *)destination {
return [self navigateToURLInBackground:destination
resolver:[self defaultResolver]];
}
+ (BFTask *)navigateToURLInBackground:(NSURL *)destination
resolver:(id<BFAppLinkResolving>)resolver {
BFTask *resolutionTask = [self resolveAppLinkInBackground:destination
resolver:resolver];
return [resolutionTask continueWithExecutor:[BFExecutor mainThreadExecutor]
withSuccessBlock:^id(BFTask *task) {
NSError *error = nil;
BFAppLinkNavigationType result = [self navigateToAppLink:task.result
error:&error];
if (error) {
return [BFTask taskWithError:error];
} else {
return @(result);
}
}];
}
+ (BFAppLinkNavigationType)navigateToAppLink:(BFAppLink *)link error:(NSError **)error {
return [[BFAppLinkNavigation navigationWithAppLink:link
extras:nil
appLinkData:nil] navigate:error];
}
+ (id<BFAppLinkResolving>)defaultResolver {
if (defaultResolver) {
return defaultResolver;
}
return [BFWebViewAppLinkResolver sharedInstance];
}
+ (void)setDefaultResolver:(id<BFAppLinkResolving>)resolver {
defaultResolver = resolver;
}
@end

View File

@ -0,0 +1,30 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Foundation/Foundation.h>
@class BFTask;
/*!
Implement this protocol to provide an alternate strategy for resolving
App Links that may include pre-fetching, caching, or querying for App Link
data from an index provided by a service provider.
*/
@protocol BFAppLinkResolving <NSObject>
/*!
Asynchronously resolves App Link data for a given URL.
@param url The URL to resolve into an App Link.
@returns A BFTask that will return a BFAppLink for the given URL.
*/
- (BFTask *)appLinkFromURLInBackground:(NSURL *)url;
@end

View File

@ -0,0 +1,87 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <Bolts/BFAppLinkReturnToRefererView.h>
@class BFAppLink;
@class BFAppLinkReturnToRefererController;
/*!
Protocol that a class can implement in order to be notified when the user has navigated back
to the referer of an App Link.
*/
@protocol BFAppLinkReturnToRefererControllerDelegate <NSObject>
@optional
/*! Called when the user has tapped to navigate, but before the navigation has been performed. */
- (void)returnToRefererController:(BFAppLinkReturnToRefererController *)controller
willNavigateToAppLink:(BFAppLink *)appLink;
/*! Called after the navigation has been attempted, with an indication of whether the referer
app link was successfully opened. */
- (void)returnToRefererController:(BFAppLinkReturnToRefererController *)controller
didNavigateToAppLink:(BFAppLink *)url
type:(BFAppLinkNavigationType)type;
@end
/*!
A controller class that implements default behavior for a BFAppLinkReturnToRefererView, including
the ability to display the view above the navigation bar for navigation-based apps.
*/
@interface BFAppLinkReturnToRefererController : NSObject <BFAppLinkReturnToRefererViewDelegate>
/*!
The delegate that will be notified when the user navigates back to the referer.
*/
@property (nonatomic, weak) id<BFAppLinkReturnToRefererControllerDelegate> delegate;
/*!
The BFAppLinkReturnToRefererView this controller is controlling.
*/
@property (nonatomic, strong) BFAppLinkReturnToRefererView *view;
/*!
Initializes a controller suitable for controlling a BFAppLinkReturnToRefererView that is to be displayed
contained within another UIView (i.e., not displayed above the navigation bar).
*/
- (instancetype)init;
/*!
Initializes a controller suitable for controlling a BFAppLinkReturnToRefererView that is to be displayed
displayed above the navigation bar.
*/
- (instancetype)initForDisplayAboveNavController:(UINavigationController *)navController;
/*!
Removes the view entirely from the navigation controller it is currently displayed in.
*/
- (void)removeFromNavController;
/*!
Shows the BFAppLinkReturnToRefererView with the specified referer information. If nil or missing data,
the view will not be displayed. */
- (void)showViewForRefererAppLink:(BFAppLink *)refererAppLink;
/*!
Shows the BFAppLinkReturnToRefererView with referer information extracted from the specified URL.
If nil or missing referer App Link data, the view will not be displayed. */
- (void)showViewForRefererURL:(NSURL *)url;
/*!
Closes the view, possibly animating it.
*/
- (void)closeViewAnimated:(BOOL)animated;
@end

View File

@ -0,0 +1,230 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "BFAppLinkReturnToRefererController.h"
#import "BFAppLink.h"
#import "BFAppLinkReturnToRefererView_Internal.h"
#import "BFURL_Internal.h"
static const CFTimeInterval kBFViewAnimationDuration = 0.25f;
@implementation BFAppLinkReturnToRefererController {
UINavigationController *_navigationController;
BFAppLinkReturnToRefererView *_view;
}
#pragma mark - Object lifecycle
- (instancetype)init {
return [self initForDisplayAboveNavController:nil];
}
- (instancetype)initForDisplayAboveNavController:(UINavigationController *)navController {
self = [super init];
if (self) {
_navigationController = navController;
if (_navigationController != nil) {
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:@selector(statusBarFrameWillChange:)
name:UIApplicationWillChangeStatusBarFrameNotification
object:nil];
[nc addObserver:self
selector:@selector(statusBarFrameDidChange:)
name:UIApplicationDidChangeStatusBarFrameNotification
object:nil];
[nc addObserver:self
selector:@selector(orientationDidChange:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
}
}
return self;
}
- (void)dealloc {
_view.delegate = nil;
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - Public API
- (BFAppLinkReturnToRefererView *)view {
if (!_view) {
self.view = [[BFAppLinkReturnToRefererView alloc] initWithFrame:CGRectZero];
if (_navigationController) {
[_navigationController.view addSubview:_view];
}
}
return _view;
}
- (void)setView:(BFAppLinkReturnToRefererView *)view {
if (_view != view) {
_view.delegate = nil;
}
_view = view;
_view.delegate = self;
if (_navigationController) {
_view.includeStatusBarInSize = BFIncludeStatusBarInSizeAlways;
}
}
- (void)showViewForRefererAppLink:(BFAppLink *)refererAppLink {
self.view.refererAppLink = refererAppLink;
[_view sizeToFit];
if (_navigationController) {
if (!_view.closed) {
dispatch_async(dispatch_get_main_queue(), ^{
[self moveNavigationBar];
});
}
}
}
- (void)showViewForRefererURL:(NSURL *)url {
BFAppLink *appLink = [BFURL URLForRenderBackToReferrerBarURL:url].appLinkReferer;
[self showViewForRefererAppLink:appLink];
}
- (void)removeFromNavController {
if (_navigationController) {
[_view removeFromSuperview];
_navigationController = nil;
}
}
#pragma mark - BFAppLinkReturnToRefererViewDelegate
- (void)returnToRefererViewDidTapInsideCloseButton:(BFAppLinkReturnToRefererView *)view {
[self closeViewAnimated:YES explicitlyClosed:YES];
}
- (void)returnToRefererViewDidTapInsideLink:(BFAppLinkReturnToRefererView *)view
link:(BFAppLink *)link {
[self openRefererAppLink:link];
[self closeViewAnimated:NO explicitlyClosed:NO];
}
#pragma mark - Private
- (void)statusBarFrameWillChange:(NSNotification *)notification {
NSValue *rectValue = [[notification userInfo] valueForKey:UIApplicationStatusBarFrameUserInfoKey];
CGRect newFrame;
[rectValue getValue:&newFrame];
if (_navigationController && !_view.closed) {
if (CGRectGetHeight(newFrame) == 40) {
UIViewAnimationOptions options = UIViewAnimationOptionBeginFromCurrentState;
[UIView animateWithDuration:kBFViewAnimationDuration delay:0.0 options:options animations:^{
_view.frame = CGRectMake(0.0, 0.0, CGRectGetWidth(_view.bounds), 0.0);
} completion:nil];
}
}
}
- (void)statusBarFrameDidChange:(NSNotification *)notification {
NSValue *rectValue = [[notification userInfo] valueForKey:UIApplicationStatusBarFrameUserInfoKey];
CGRect newFrame;
[rectValue getValue:&newFrame];
if (_navigationController && !_view.closed) {
if (CGRectGetHeight(newFrame) == 40) {
UIViewAnimationOptions options = UIViewAnimationOptionBeginFromCurrentState;
[UIView animateWithDuration:kBFViewAnimationDuration delay:0.0 options:options animations:^{
[_view sizeToFit];
[self moveNavigationBar];
} completion:nil];
}
}
}
- (void)orientationDidChange:(NSNotificationCenter *)notification {
if (_navigationController && !_view.closed && CGRectGetHeight(_view.bounds) > 0) {
dispatch_async(dispatch_get_main_queue(), ^{
[self moveNavigationBar];
});
}
}
- (void)moveNavigationBar {
if (_view.closed || !_view.refererAppLink) {
return;
}
[self updateNavigationBarY:CGRectGetHeight(_view.bounds)];
}
- (void)updateNavigationBarY:(CGFloat)y {
UINavigationBar *navigationBar = _navigationController.navigationBar;
CGRect navigationBarFrame = navigationBar.frame;
CGFloat oldContainerViewY = CGRectGetMaxY(navigationBarFrame);
navigationBarFrame.origin.y = y;
navigationBar.frame = navigationBarFrame;
CGFloat dy = CGRectGetMaxY(navigationBarFrame) - oldContainerViewY;
UIView *containerView = _navigationController.visibleViewController.view.superview;
containerView.frame = UIEdgeInsetsInsetRect(containerView.frame, UIEdgeInsetsMake(dy, 0.0, 0.0, 0.0));
}
- (void)closeViewAnimated:(BOOL)animated {
[self closeViewAnimated:animated explicitlyClosed:YES];
}
- (void)closeViewAnimated:(BOOL)animated explicitlyClosed:(BOOL)explicitlyClosed {
void (^closer)(void) = ^{
if (_navigationController) {
[self updateNavigationBarY:_view.statusBarHeight];
}
CGRect frame = _view.frame;
frame.size.height = 0.0;
_view.frame = frame;
};
if (animated) {
[UIView animateWithDuration:kBFViewAnimationDuration animations:^{
closer();
} completion:^(BOOL finished) {
if (explicitlyClosed) {
_view.closed = YES;
}
}];
} else {
closer();
if (explicitlyClosed) {
_view.closed = YES;
}
}
}
- (void)openRefererAppLink:(BFAppLink *)refererAppLink {
if (refererAppLink) {
id<BFAppLinkReturnToRefererControllerDelegate> delegate = _delegate;
if ([delegate respondsToSelector:@selector(returnToRefererController:willNavigateToAppLink:)]) {
[delegate returnToRefererController:self willNavigateToAppLink:refererAppLink];
}
NSError *error = nil;
BFAppLinkNavigationType type = [BFAppLinkNavigation navigateToAppLink:refererAppLink error:&error];
if ([delegate respondsToSelector:@selector(returnToRefererController:didNavigateToAppLink:type:)]) {
[delegate returnToRefererController:self didNavigateToAppLink:refererAppLink type:type];
}
}
}
@end

View File

@ -0,0 +1,77 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <Bolts/BFAppLinkNavigation.h>
@class BFAppLinkReturnToRefererView;
@class BFURL;
typedef NS_ENUM(NSUInteger, BFIncludeStatusBarInSize) {
BFIncludeStatusBarInSizeNever,
BFIncludeStatusBarInSizeIOS7AndLater,
BFIncludeStatusBarInSizeAlways,
};
/*!
Protocol that a class can implement in order to be notified when the user has navigated back
to the referer of an App Link.
*/
@protocol BFAppLinkReturnToRefererViewDelegate <NSObject>
/*!
Called when the user has tapped inside the close button.
*/
- (void)returnToRefererViewDidTapInsideCloseButton:(BFAppLinkReturnToRefererView *)view;
/*!
Called when the user has tapped inside the App Link portion of the view.
*/
- (void)returnToRefererViewDidTapInsideLink:(BFAppLinkReturnToRefererView *)view
link:(BFAppLink *)link;
@end
/*!
Provides a UIView that displays a button allowing users to navigate back to the
application that launched the App Link currently being handled, if the App Link
contained referer data. The user can also close the view by clicking a close button
rather than navigating away. If the view is provided an App Link that does not contain
referer data, it will have zero size and no UI will be displayed.
*/
@interface BFAppLinkReturnToRefererView : UIView
/*!
The delegate that will be notified when the user navigates back to the referer.
*/
@property (nonatomic, weak) id<BFAppLinkReturnToRefererViewDelegate> delegate;
/*!
The color of the text label and close button.
*/
@property (nonatomic, strong) UIColor *textColor;
@property (nonatomic, strong) BFAppLink *refererAppLink;
/*!
Indicates whether to extend the size of the view to include the current status bar
size, for use in scenarios where the view might extend under the status bar on iOS 7 and
above; this property has no effect on earlier versions of iOS.
*/
@property (nonatomic, assign) BFIncludeStatusBarInSize includeStatusBarInSize;
/*!
Indicates whether the user has closed the view by clicking the close button.
*/
@property (nonatomic, assign) BOOL closed;
@end

View File

@ -0,0 +1,268 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "BFAppLinkReturnToRefererView.h"
#import "BFAppLink.h"
#import "BFAppLinkTarget.h"
static const CGFloat BFMarginX = 8.5f;
static const CGFloat BFMarginY = 8.5f;
static NSString *const BFRefererAppLink = @"referer_app_link";
static NSString *const BFRefererAppName = @"app_name";
static NSString *const BFRefererUrl = @"url";
static const CGFloat BFCloseButtonWidth = 12.0;
static const CGFloat BFCloseButtonHeight = 12.0;
@interface BFAppLinkReturnToRefererView ()
@property (nonatomic, strong) UILabel *labelView;
@property (nonatomic, strong) UIButton *closeButton;
@property (nonatomic, strong) UITapGestureRecognizer *insideTapGestureRecognizer;
@end
@implementation BFAppLinkReturnToRefererView {
BOOL _explicitlyHidden;
}
#pragma mark - Initialization
- (instancetype)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[self commonInit];
[self sizeToFit];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self commonInit];
}
return self;
}
- (void)commonInit {
// Initialization code
_includeStatusBarInSize = BFIncludeStatusBarInSizeIOS7AndLater;
// iOS 7 system blue color
self.backgroundColor = [UIColor colorWithRed:0.0f green:122.0f / 255.0f blue:1.0f alpha:1.0f];
self.textColor = [UIColor whiteColor];
self.clipsToBounds = YES;
[self initViews];
}
- (void)initViews {
if (!_labelView && !_closeButton) {
_closeButton = [UIButton buttonWithType:UIButtonTypeCustom];
_closeButton.backgroundColor = [UIColor clearColor];
_closeButton.userInteractionEnabled = YES;
_closeButton.clipsToBounds = YES;
_closeButton.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleTopMargin;
_closeButton.contentMode = UIViewContentModeCenter;
[_closeButton addTarget:self action:@selector(closeButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_closeButton];
_labelView = [[UILabel alloc] initWithFrame:CGRectZero];
_labelView.font = [UIFont systemFontOfSize:[UIFont smallSystemFontSize]];
_labelView.textColor = [UIColor whiteColor];
_labelView.backgroundColor = [UIColor clearColor];
#ifdef __IPHONE_6_0
_labelView.textAlignment = NSTextAlignmentCenter;
#else
_labelView.textAlignment = UITextAlignmentCenter;
#endif
_labelView.clipsToBounds = YES;
[self updateLabelText];
[self addSubview:_labelView];
_insideTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTapInside:)];
_labelView.userInteractionEnabled = YES;
[_labelView addGestureRecognizer:_insideTapGestureRecognizer];
[self updateColors];
}
}
#pragma mark - Layout
- (CGSize)intrinsicContentSize {
CGSize size = self.bounds.size;
if (_closed || !self.hasRefererData) {
size.height = 0.0;
} else {
CGSize labelSize = [_labelView sizeThatFits:size];
size = CGSizeMake(size.width, labelSize.height + 2 * BFMarginY + self.statusBarHeight);
}
return size;
}
- (void)layoutSubviews {
[super layoutSubviews];
CGRect bounds = self.bounds;
_labelView.preferredMaxLayoutWidth = _labelView.bounds.size.width;
CGSize labelSize = [_labelView sizeThatFits:bounds.size];
_labelView.frame = CGRectMake(BFMarginX,
CGRectGetMaxY(bounds) - labelSize.height - 1.5f * BFMarginY,
CGRectGetMaxX(bounds) - BFCloseButtonWidth - 3 * BFMarginX,
labelSize.height + BFMarginY);
_closeButton.frame = CGRectMake(CGRectGetMaxX(bounds) - BFCloseButtonWidth - 2 * BFMarginX,
_labelView.center.y - BFCloseButtonHeight / 2.0f - BFMarginY,
BFCloseButtonWidth + 2 * BFMarginX,
BFCloseButtonHeight + 2 * BFMarginY);
}
- (CGSize)sizeThatFits:(CGSize)size {
if (_closed || !self.hasRefererData) {
size = CGSizeMake(size.width, 0.0);
} else {
CGSize labelSize = [_labelView sizeThatFits:size];
size = CGSizeMake(size.width, labelSize.height + 2 * BFMarginY + self.statusBarHeight);
}
return size;
}
- (CGFloat)statusBarHeight {
UIApplication *application = [UIApplication sharedApplication];
BOOL include;
switch (_includeStatusBarInSize) {
case BFIncludeStatusBarInSizeAlways:
include = YES;
break;
case BFIncludeStatusBarInSizeIOS7AndLater: {
float systemVersion = [[[UIDevice currentDevice] systemVersion] floatValue];
include = (systemVersion >= 7.0);
break;
}
case BFIncludeStatusBarInSizeNever:
include = NO;
break;
}
if (include && !application.statusBarHidden) {
BOOL landscape = UIInterfaceOrientationIsLandscape(application.statusBarOrientation);
CGRect statusBarFrame = application.statusBarFrame;
return landscape ? CGRectGetWidth(statusBarFrame) : CGRectGetHeight(statusBarFrame);
}
return 0;
}
#pragma mark - Public API
- (void)setIncludeStatusBarInSize:(BFIncludeStatusBarInSize)includeStatusBarInSize {
_includeStatusBarInSize = includeStatusBarInSize;
[self setNeedsLayout];
[self invalidateIntrinsicContentSize];
}
- (void)setTextColor:(UIColor *)textColor {
_textColor = textColor;
[self updateColors];
}
- (void)setRefererAppLink:(BFAppLink *)refererAppLink {
_refererAppLink = refererAppLink;
[self updateLabelText];
[self updateHidden];
[self invalidateIntrinsicContentSize];
}
- (void)setClosed:(BOOL)closed {
if (_closed != closed) {
_closed = closed;
[self updateHidden];
[self invalidateIntrinsicContentSize];
}
}
- (void)setHidden:(BOOL)hidden {
_explicitlyHidden = hidden;
[self updateHidden];
}
#pragma mark - Private
- (void)updateLabelText {
NSString *appName = (_refererAppLink && _refererAppLink.targets[0]) ? [_refererAppLink.targets[0] appName] : nil;
_labelView.text = [self localizedLabelForReferer:appName];
}
- (void)updateColors {
UIImage *closeButtonImage = [self drawCloseButtonImageWithColor:_textColor];
_labelView.textColor = _textColor;
[_closeButton setImage:closeButtonImage forState:UIControlStateNormal];
}
- (UIImage *)drawCloseButtonImageWithColor:(UIColor *)color {
UIGraphicsBeginImageContextWithOptions(CGSizeMake(BFCloseButtonWidth, BFCloseButtonHeight), NO, 0.0f);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [color CGColor]);
CGContextSetFillColorWithColor(context, [color CGColor]);
CGContextSetLineWidth(context, 1.25f);
CGFloat inset = 0.5f;
CGContextMoveToPoint(context, inset, inset);
CGContextAddLineToPoint(context, BFCloseButtonWidth - inset, BFCloseButtonHeight - inset);
CGContextStrokePath(context);
CGContextMoveToPoint(context, BFCloseButtonWidth - inset, inset);
CGContextAddLineToPoint(context, inset, BFCloseButtonHeight - inset);
CGContextStrokePath(context);
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return result;
}
- (NSString *)localizedLabelForReferer:(NSString *)refererName {
if (!refererName) {
return nil;
}
NSString *format = NSLocalizedString(@"Touch to return to %1$@", @"Format for the string to return to a calling app.");
return [NSString stringWithFormat:format, refererName];
}
- (BOOL)hasRefererData {
return _refererAppLink && _refererAppLink.targets[0];
}
- (void)closeButtonTapped:(id)sender {
[_delegate returnToRefererViewDidTapInsideCloseButton:self];
}
- (void)onTapInside:(UIGestureRecognizer *)sender {
[_delegate returnToRefererViewDidTapInsideLink:self link:_refererAppLink];
}
- (void)updateHidden {
[super setHidden:_explicitlyHidden || _closed || !self.hasRefererData];
}
@end

View File

@ -0,0 +1,17 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Bolts/BFAppLinkReturnToRefererView.h>
@interface BFAppLinkReturnToRefererView (Internal)
- (CGFloat)statusBarHeight;
@end

33
Pods/Bolts/Bolts/iOS/BFAppLinkTarget.h generated Normal file
View File

@ -0,0 +1,33 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Foundation/Foundation.h>
/*!
Represents a target defined in App Link metadata, consisting of at least
a URL, and optionally an App Store ID and name.
*/
@interface BFAppLinkTarget : NSObject
/*! Creates a BFAppLinkTarget with the given app site and target URL. */
+ (instancetype)appLinkTargetWithURL:(NSURL *)url
appStoreId:(NSString *)appStoreId
appName:(NSString *)appName;
/*! The URL prefix for this app link target */
@property (nonatomic, strong, readonly) NSURL *URL;
/*! The app ID for the app store */
@property (nonatomic, copy, readonly) NSString *appStoreId;
/*! The name of the app */
@property (nonatomic, copy, readonly) NSString *appName;
@end

33
Pods/Bolts/Bolts/iOS/BFAppLinkTarget.m generated Normal file
View File

@ -0,0 +1,33 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "BFAppLinkTarget.h"
@interface BFAppLinkTarget ()
@property (nonatomic, strong, readwrite) NSURL *URL;
@property (nonatomic, copy, readwrite) NSString *appStoreId;
@property (nonatomic, copy, readwrite) NSString *appName;
@end
@implementation BFAppLinkTarget
+ (instancetype)appLinkTargetWithURL:(NSURL *)url
appStoreId:(NSString *)appStoreId
appName:(NSString *)appName {
BFAppLinkTarget *target = [[self alloc] init];
target.URL = url;
target.appStoreId = appStoreId;
target.appName = appName;
return target;
}
@end

View File

@ -0,0 +1,32 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Bolts/BFAppLink.h>
FOUNDATION_EXPORT NSString *const BFAppLinkDataParameterName;
FOUNDATION_EXPORT NSString *const BFAppLinkTargetKeyName;
FOUNDATION_EXPORT NSString *const BFAppLinkUserAgentKeyName;
FOUNDATION_EXPORT NSString *const BFAppLinkExtrasKeyName;
FOUNDATION_EXPORT NSString *const BFAppLinkVersionKeyName;
FOUNDATION_EXPORT NSString *const BFAppLinkRefererAppLink;
FOUNDATION_EXPORT NSString *const BFAppLinkRefererAppName;
FOUNDATION_EXPORT NSString *const BFAppLinkRefererUrl;
@interface BFAppLink (Internal)
+ (instancetype)appLinkWithSourceURL:(NSURL *)sourceURL
targets:(NSArray *)targets
webURL:(NSURL *)webURL
isBackToReferrer:(BOOL)isBackToReferrer;
/*! return if this AppLink is to go back to referrer. */
@property (nonatomic, assign, readonly, getter=isBackToReferrer) BOOL backToReferrer;
@end

View File

@ -0,0 +1,45 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Foundation/Foundation.h>
/*! The name of the notification posted by BFMeasurementEvent */
FOUNDATION_EXPORT NSString *const BFMeasurementEventNotificationName;
/*! Defines keys in the userInfo object for the notification named BFMeasurementEventNotificationName */
/*! The string field for the name of the event */
FOUNDATION_EXPORT NSString *const BFMeasurementEventNameKey;
/*! The dictionary field for the arguments of the event */
FOUNDATION_EXPORT NSString *const BFMeasurementEventArgsKey;
/*! Bolts Events raised by BFMeasurementEvent for Applink */
/*!
The name of the event posted when [BFURL URLWithURL:] is called successfully. This represents the successful parsing of an app link URL.
*/
FOUNDATION_EXPORT NSString *const BFAppLinkParseEventName;
/*!
The name of the event posted when [BFURL URLWithInboundURL:] is called successfully.
This represents parsing an inbound app link URL from a different application
*/
FOUNDATION_EXPORT NSString *const BFAppLinkNavigateInEventName;
/*! The event raised when the user navigates from your app to other apps */
FOUNDATION_EXPORT NSString *const BFAppLinkNavigateOutEventName;
/*!
The event raised when the user navigates out from your app and back to the referrer app.
e.g when the user leaves your app after tapping the back-to-referrer navigation bar
*/
FOUNDATION_EXPORT NSString *const BFAppLinkNavigateBackToReferrerEventName;
@interface BFMeasurementEvent : NSObject
@end

View File

@ -0,0 +1,62 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "BFMeasurementEvent_Internal.h"
NSString *const BFMeasurementEventNotificationName = @"com.parse.bolts.measurement_event";
NSString *const BFMeasurementEventNameKey = @"event_name";
NSString *const BFMeasurementEventArgsKey = @"event_args";
/* app Link Event raised by this BFURL */
NSString *const BFAppLinkParseEventName = @"al_link_parse";
NSString *const BFAppLinkNavigateInEventName = @"al_nav_in";
/*! AppLink events raised in this class */
NSString *const BFAppLinkNavigateOutEventName = @"al_nav_out";
NSString *const BFAppLinkNavigateBackToReferrerEventName = @"al_ref_back_out";
__attribute__((noinline)) void warnOnMissingEventName() {
NSLog(@"Warning: Missing event name when logging bolts measurement event. \n"
" Ignoring this event in logging.");
}
@implementation BFMeasurementEvent {
NSString *_name;
NSDictionary *_args;
}
- (void)postNotification {
if (!_name) {
warnOnMissingEventName();
return;
}
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
NSDictionary *userInfo = @{BFMeasurementEventNameKey : _name,
BFMeasurementEventArgsKey : _args};
[center postNotificationName:BFMeasurementEventNotificationName
object:self
userInfo:userInfo];
}
- (instancetype)initEventWithName:(NSString *)name args:(NSDictionary *)args {
if ((self = [super init])) {
_name = name;
_args = args ? args : @{};
}
return self;
}
+ (void)postNotificationForEventName:(NSString *)name args:(NSDictionary *)args {
[[[self alloc] initEventWithName:name args:args] postNotification];
}
@end

View File

@ -0,0 +1,19 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Bolts/BFMeasurementEvent.h>
/*!
Provides methods for posting notifications from the Bolts framework
*/
@interface BFMeasurementEvent (Internal)
+ (void)postNotificationForEventName:(NSString *)name args:(NSDictionary *)args;
@end

75
Pods/Bolts/Bolts/iOS/BFURL.h generated Normal file
View File

@ -0,0 +1,75 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Foundation/Foundation.h>
@class BFAppLink;
/*!
Provides a set of utilities for working with NSURLs, such as parsing of query parameters
and handling for App Link requests.
*/
@interface BFURL : NSObject
/*!
Creates a link target from a raw URL.
On success, this posts the BFAppLinkParseEventName measurement event. If you are constructing the BFURL within your application delegate's
application:openURL:sourceApplication:annotation:, you should instead use URLWithInboundURL:sourceApplication:
to support better BFMeasurementEvent notifications
@param url The instance of `NSURL` to create BFURL from.
*/
+ (BFURL *)URLWithURL:(NSURL *)url;
/*!
Creates a link target from a raw URL received from an external application. This is typically called from the app delegate's
application:openURL:sourceApplication:annotation: and will post the BFAppLinkNavigateInEventName measurement event.
@param url The instance of `NSURL` to create BFURL from.
@param sourceApplication the bundle ID of the app that is requesting your app to open the URL. The same sourceApplication in application:openURL:sourceApplication:annotation:
*/
+ (BFURL *)URLWithInboundURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication;
/*!
Gets the target URL. If the link is an App Link, this is the target of the App Link.
Otherwise, it is the url that created the target.
*/
@property (nonatomic, strong, readonly) NSURL *targetURL;
/*!
Gets the query parameters for the target, parsed into an NSDictionary.
*/
@property (nonatomic, strong, readonly) NSDictionary *targetQueryParameters;
/*!
If this link target is an App Link, this is the data found in al_applink_data.
Otherwise, it is nil.
*/
@property (nonatomic, strong, readonly) NSDictionary *appLinkData;
/*!
If this link target is an App Link, this is the data found in extras.
*/
@property (nonatomic, strong, readonly) NSDictionary *appLinkExtras;
/*!
The App Link indicating how to navigate back to the referer app, if any.
*/
@property (nonatomic, strong, readonly) BFAppLink *appLinkReferer;
/*!
The URL that was used to create this BFURL.
*/
@property (nonatomic, strong, readonly) NSURL *inputURL;
/*!
The query parameters of the inputURL, parsed into an NSDictionary.
*/
@property (nonatomic, strong, readonly) NSDictionary *inputQueryParameters;
@end

142
Pods/Bolts/Bolts/iOS/BFURL.m generated Normal file
View File

@ -0,0 +1,142 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "BFURL_Internal.h"
#import "BFAppLink_Internal.h"
#import "BFAppLinkTarget.h"
#import "BFMeasurementEvent_Internal.h"
@implementation BFURL
- (instancetype)initWithURL:(NSURL *)url forOpenInboundURL:(BOOL)forOpenURLEvent sourceApplication:(NSString *)sourceApplication forRenderBackToReferrerBar:(BOOL)forRenderBackToReferrerBar {
self = [super init];
if (!self) return nil;
_inputURL = url;
_targetURL = url;
// Parse the query string parameters for the base URL
NSDictionary *baseQuery = [BFURL queryParametersForURL:url];
_inputQueryParameters = baseQuery;
_targetQueryParameters = baseQuery;
// Check for applink_data
NSString *appLinkDataString = baseQuery[BFAppLinkDataParameterName];
if (appLinkDataString) {
// Try to parse the JSON
NSError *error = nil;
NSDictionary *applinkData = [NSJSONSerialization JSONObjectWithData:[appLinkDataString dataUsingEncoding:NSUTF8StringEncoding]
options:0
error:&error];
if (!error && [applinkData isKindOfClass:[NSDictionary class]]) {
// If the version is not specified, assume it is 1.
NSString *version = applinkData[BFAppLinkVersionKeyName] ?: @"1.0";
NSString *target = applinkData[BFAppLinkTargetKeyName];
if ([version isKindOfClass:[NSString class]] &&
[version isEqual:BFAppLinkVersion]) {
// There's applink data! The target should actually be the applink target.
_appLinkData = applinkData;
id applinkExtras = applinkData[BFAppLinkExtrasKeyName];
if (applinkExtras && [applinkExtras isKindOfClass:[NSDictionary class]]) {
_appLinkExtras = applinkExtras;
}
_targetURL = ([target isKindOfClass:[NSString class]] ? [NSURL URLWithString:target] : url);
_targetQueryParameters = [BFURL queryParametersForURL:_targetURL];
NSDictionary *refererAppLink = _appLinkData[BFAppLinkRefererAppLink];
NSString *refererURLString = refererAppLink[BFAppLinkRefererUrl];
NSString *refererAppName = refererAppLink[BFAppLinkRefererAppName];
if (refererURLString && refererAppName) {
BFAppLinkTarget *target = [BFAppLinkTarget appLinkTargetWithURL:[NSURL URLWithString:refererURLString]
appStoreId:nil
appName:refererAppName];
_appLinkReferer = [BFAppLink appLinkWithSourceURL:[NSURL URLWithString:refererURLString]
targets:@[ target ]
webURL:nil
isBackToReferrer:YES];
}
// Raise Measurement Event
NSString *const EVENT_YES_VAL = @"1";
NSString *const EVENT_NO_VAL = @"0";
NSMutableDictionary *logData = [[NSMutableDictionary alloc] init];
logData[@"version"] = version;
if (refererURLString) {
logData[@"refererURL"] = refererURLString;
}
if (refererAppName) {
logData[@"refererAppName"] = refererAppName;
}
if (sourceApplication) {
logData[@"sourceApplication"] = sourceApplication;
}
if ([_targetURL absoluteString]) {
logData[@"targetURL"] = [_targetURL absoluteString];
}
if ([_inputURL absoluteString]) {
logData[@"inputURL"] = [_inputURL absoluteString];
}
if ([_inputURL scheme]) {
logData[@"inputURLScheme"] = [_inputURL scheme];
}
logData[@"forRenderBackToReferrerBar"] = forRenderBackToReferrerBar ? EVENT_YES_VAL : EVENT_NO_VAL;
logData[@"forOpenUrl"] = forOpenURLEvent ? EVENT_YES_VAL : EVENT_NO_VAL;
[BFMeasurementEvent postNotificationForEventName:BFAppLinkParseEventName args:logData];
if (forOpenURLEvent) {
[BFMeasurementEvent postNotificationForEventName:BFAppLinkNavigateInEventName args:logData];
}
}
}
}
return self;
}
+ (BFURL *)URLWithURL:(NSURL *)url {
return [[BFURL alloc] initWithURL:url forOpenInboundURL:NO sourceApplication:nil forRenderBackToReferrerBar:NO];
}
+ (BFURL *)URLWithInboundURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication {
return [[BFURL alloc] initWithURL:url forOpenInboundURL:YES sourceApplication:sourceApplication forRenderBackToReferrerBar:NO];
}
+ (BFURL *)URLForRenderBackToReferrerBarURL:(NSURL *)url {
return [[BFURL alloc] initWithURL:url forOpenInboundURL:NO sourceApplication:nil forRenderBackToReferrerBar:YES];
}
+ (NSString *)decodeURLString:(NSString *)string {
return (NSString *)CFBridgingRelease(CFURLCreateStringByReplacingPercentEscapes(NULL,
(CFStringRef)string,
CFSTR("")));
}
+ (NSDictionary *)queryParametersForURL:(NSURL *)url {
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
NSString *query = url.query;
if ([query isEqualToString:@""]) {
return @{};
}
NSArray *queryComponents = [query componentsSeparatedByString:@"&"];
for (NSString *component in queryComponents) {
NSRange equalsLocation = [component rangeOfString:@"="];
if (equalsLocation.location == NSNotFound) {
// There's no equals, so associate the key with NSNull
parameters[[self decodeURLString:component]] = [NSNull null];
} else {
NSString *key = [self decodeURLString:[component substringToIndex:equalsLocation.location]];
NSString *value = [self decodeURLString:[component substringFromIndex:equalsLocation.location + 1]];
parameters[key] = value;
}
}
return [NSDictionary dictionaryWithDictionary:parameters];
}
@end

15
Pods/Bolts/Bolts/iOS/BFURL_Internal.h generated Normal file
View File

@ -0,0 +1,15 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Bolts/BFURL.h>
@interface BFURL (Internal)
+ (BFURL *)URLForRenderBackToReferrerBarURL:(NSURL *)url;
@end

View File

@ -0,0 +1,26 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <Foundation/Foundation.h>
#import <Bolts/BFAppLinkResolving.h>
/*!
A reference implementation for an App Link resolver that uses a hidden UIWebView
to parse the HTML containing App Link metadata.
*/
@interface BFWebViewAppLinkResolver : NSObject <BFAppLinkResolving>
/*!
Gets the instance of a BFWebViewAppLinkResolver.
*/
+ (instancetype)sharedInstance;
@end

View File

@ -0,0 +1,305 @@
/*
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import <UIKit/UIKit.h>
#import "BFWebViewAppLinkResolver.h"
#import "BFAppLink.h"
#import "BFAppLinkTarget.h"
#import "BFTask.h"
#import "BFTaskCompletionSource.h"
#import "BFExecutor.h"
// Defines JavaScript to extract app link tags from HTML content
static NSString *const BFWebViewAppLinkResolverTagExtractionJavaScript = @""
"(function() {"
" var metaTags = document.getElementsByTagName('meta');"
" var results = [];"
" for (var i = 0; i < metaTags.length; i++) {"
" var property = metaTags[i].getAttribute('property');"
" if (property && property.substring(0, 'al:'.length) === 'al:') {"
" var tag = { \"property\": metaTags[i].getAttribute('property') };"
" if (metaTags[i].hasAttribute('content')) {"
" tag['content'] = metaTags[i].getAttribute('content');"
" }"
" results.push(tag);"
" }"
" }"
" return JSON.stringify(results);"
"})()";
static NSString *const BFWebViewAppLinkResolverIOSURLKey = @"url";
static NSString *const BFWebViewAppLinkResolverIOSAppStoreIdKey = @"app_store_id";
static NSString *const BFWebViewAppLinkResolverIOSAppNameKey = @"app_name";
static NSString *const BFWebViewAppLinkResolverDictionaryValueKey = @"_value";
static NSString *const BFWebViewAppLinkResolverPreferHeader = @"Prefer-Html-Meta-Tags";
static NSString *const BFWebViewAppLinkResolverMetaTagPrefix = @"al";
static NSString *const BFWebViewAppLinkResolverWebKey = @"web";
static NSString *const BFWebViewAppLinkResolverIOSKey = @"ios";
static NSString *const BFWebViewAppLinkResolverIPhoneKey = @"iphone";
static NSString *const BFWebViewAppLinkResolverIPadKey = @"ipad";
static NSString *const BFWebViewAppLinkResolverWebURLKey = @"url";
static NSString *const BFWebViewAppLinkResolverShouldFallbackKey = @"should_fallback";
@interface BFWebViewAppLinkResolverWebViewDelegate : NSObject <UIWebViewDelegate>
@property (nonatomic, copy) void (^didFinishLoad)(UIWebView *webView);
@property (nonatomic, copy) void (^didFailLoadWithError)(UIWebView *webView, NSError *error);
@property (nonatomic, assign) BOOL hasLoaded;
@end
@implementation BFWebViewAppLinkResolverWebViewDelegate
- (void)webViewDidFinishLoad:(UIWebView *)webView {
if (self.didFinishLoad) {
self.didFinishLoad(webView);
}
}
- (void)webViewDidStartLoad:(UIWebView *)webView {
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {
if (self.didFailLoadWithError) {
self.didFailLoadWithError(webView, error);
}
}
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
if (self.hasLoaded) {
// Consider loading a second resource to be "success", since it indicates an inner frame
// or redirect is happening. We can run the tag extraction script at this point.
self.didFinishLoad(webView);
return NO;
}
self.hasLoaded = YES;
return YES;
}
@end
@implementation BFWebViewAppLinkResolver
+ (instancetype)sharedInstance {
static id instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[self alloc] init];
});
return instance;
}
- (BFTask *)followRedirects:(NSURL *)url {
// This task will be resolved with either the redirect NSURL
// or a dictionary with the response data to be returned.
BFTaskCompletionSource *tcs = [BFTaskCompletionSource taskCompletionSource];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setValue:BFWebViewAppLinkResolverMetaTagPrefix forHTTPHeaderField:BFWebViewAppLinkResolverPreferHeader];
void (^completion)(NSURLResponse *response, NSData *data, NSError *error) = ^(NSURLResponse *response, NSData *data, NSError *error) {
if (error) {
[tcs setError:error];
return;
}
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
// NSURLConnection usually follows redirects automatically, but the
// documentation is unclear what the default is. This helps it along.
if (httpResponse.statusCode >= 300 && httpResponse.statusCode < 400) {
NSString *redirectString = httpResponse.allHeaderFields[@"Location"];
NSURL *redirectURL = [NSURL URLWithString:redirectString];
[tcs setResult:redirectURL];
return;
}
}
[tcs setResult:@{ @"response" : response, @"data" : data }];
};
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_7_0 || __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_9
NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
completion(response, data, error);
}] resume];
#else
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:completion];
#endif
return [tcs.task continueWithSuccessBlock:^id(BFTask *task) {
// If we redirected, just keep recursing.
if ([task.result isKindOfClass:[NSURL class]]) {
return [self followRedirects:task.result];
}
return task;
}];
}
- (BFTask *)appLinkFromURLInBackground:(NSURL *)url {
return [[self followRedirects:url] continueWithExecutor:[BFExecutor mainThreadExecutor]
withSuccessBlock:^id(BFTask *task) {
NSData *responseData = task.result[@"data"];
NSHTTPURLResponse *response = task.result[@"response"];
BFTaskCompletionSource *tcs = [BFTaskCompletionSource taskCompletionSource];
UIWebView *webView = [[UIWebView alloc] init];
BFWebViewAppLinkResolverWebViewDelegate *listener = [[BFWebViewAppLinkResolverWebViewDelegate alloc] init];
__block BFWebViewAppLinkResolverWebViewDelegate *retainedListener = listener;
listener.didFinishLoad = ^(UIWebView *view) {
if (retainedListener) {
NSDictionary *ogData = [self getALDataFromLoadedPage:view];
[view removeFromSuperview];
view.delegate = nil;
retainedListener = nil;
[tcs setResult:[self appLinkFromALData:ogData destination:url]];
}
};
listener.didFailLoadWithError = ^(UIWebView* view, NSError *error) {
if (retainedListener) {
[view removeFromSuperview];
view.delegate = nil;
retainedListener = nil;
[tcs setError:error];
}
};
webView.delegate = listener;
webView.hidden = YES;
[webView loadData:responseData
MIMEType:response.MIMEType
textEncodingName:response.textEncodingName
baseURL:response.URL];
UIWindow *window = [UIApplication sharedApplication].windows.firstObject;
[window addSubview:webView];
return tcs.task;
}];
}
/*
Builds up a data structure filled with the app link data from the meta tags on a page.
The structure of this object is a dictionary where each key holds an array of app link
data dictionaries. Values are stored in a key called "_value".
*/
- (NSDictionary *)parseALData:(NSArray *)dataArray {
NSMutableDictionary *al = [NSMutableDictionary dictionary];
for (NSDictionary *tag in dataArray) {
NSString *name = tag[@"property"];
if (![name isKindOfClass:[NSString class]]) {
continue;
}
NSArray *nameComponents = [name componentsSeparatedByString:@":"];
if (![nameComponents[0] isEqualToString:BFWebViewAppLinkResolverMetaTagPrefix]) {
continue;
}
NSMutableDictionary *root = al;
for (int i = 1; i < nameComponents.count; i++) {
NSMutableArray *children = root[nameComponents[i]];
if (!children) {
children = [NSMutableArray array];
root[nameComponents[i]] = children;
}
NSMutableDictionary *child = children.lastObject;
if (!child || i == nameComponents.count - 1) {
child = [NSMutableDictionary dictionary];
[children addObject:child];
}
root = child;
}
if (tag[@"content"]) {
root[BFWebViewAppLinkResolverDictionaryValueKey] = tag[@"content"];
}
}
return al;
}
- (NSDictionary *)getALDataFromLoadedPage:(UIWebView *)webView {
// Run some JavaScript in the webview to fetch the meta tags.
NSString *jsonString = [webView stringByEvaluatingJavaScriptFromString:BFWebViewAppLinkResolverTagExtractionJavaScript];
NSError *error = nil;
NSArray *arr = [NSJSONSerialization JSONObjectWithData:[jsonString dataUsingEncoding:NSUTF8StringEncoding]
options:0
error:&error];
return [self parseALData:arr];
}
/*
Converts app link data into a BFAppLink containing the targets relevant for this platform.
*/
- (BFAppLink *)appLinkFromALData:(NSDictionary *)appLinkDict destination:(NSURL *)destination {
NSMutableArray *linkTargets = [NSMutableArray array];
NSArray *platformData = nil;
switch (UI_USER_INTERFACE_IDIOM()) {
case UIUserInterfaceIdiomPad:
platformData = @[ appLinkDict[BFWebViewAppLinkResolverIPadKey] ?: @{},
appLinkDict[BFWebViewAppLinkResolverIOSKey] ?: @{} ];
break;
case UIUserInterfaceIdiomPhone:
platformData = @[ appLinkDict[BFWebViewAppLinkResolverIPhoneKey] ?: @{},
appLinkDict[BFWebViewAppLinkResolverIOSKey] ?: @{} ];
break;
#ifdef __TVOS_9_0
case UIUserInterfaceIdiomTV:
#endif
#ifdef __IPHONE_9_3
case UIUserInterfaceIdiomCarPlay:
#endif
case UIUserInterfaceIdiomUnspecified:
default:
// Future-proofing. Other User Interface idioms should only hit ios.
platformData = @[ appLinkDict[BFWebViewAppLinkResolverIOSKey] ?: @{} ];
break;
}
for (NSArray *platformObjects in platformData) {
for (NSDictionary *platformDict in platformObjects) {
// The schema requires a single url/app store id/app name,
// but we could find multiple of them. We'll make a best effort
// to interpret this data.
NSArray *urls = platformDict[BFWebViewAppLinkResolverIOSURLKey];
NSArray *appStoreIds = platformDict[BFWebViewAppLinkResolverIOSAppStoreIdKey];
NSArray *appNames = platformDict[BFWebViewAppLinkResolverIOSAppNameKey];
NSUInteger maxCount = MAX(urls.count, MAX(appStoreIds.count, appNames.count));
for (NSUInteger i = 0; i < maxCount; i++) {
NSString *urlString = urls[i][BFWebViewAppLinkResolverDictionaryValueKey];
NSURL *url = urlString ? [NSURL URLWithString:urlString] : nil;
NSString *appStoreId = appStoreIds[i][BFWebViewAppLinkResolverDictionaryValueKey];
NSString *appName = appNames[i][BFWebViewAppLinkResolverDictionaryValueKey];
BFAppLinkTarget *target = [BFAppLinkTarget appLinkTargetWithURL:url
appStoreId:appStoreId
appName:appName];
[linkTargets addObject:target];
}
}
}
NSDictionary *webDict = appLinkDict[BFWebViewAppLinkResolverWebKey][0];
NSString *webUrlString = webDict[BFWebViewAppLinkResolverWebURLKey][0][BFWebViewAppLinkResolverDictionaryValueKey];
NSString *shouldFallbackString = webDict[BFWebViewAppLinkResolverShouldFallbackKey][0][BFWebViewAppLinkResolverDictionaryValueKey];
NSURL *webUrl = destination;
if (shouldFallbackString &&
[@[ @"no", @"false", @"0" ] containsObject:[shouldFallbackString lowercaseString]]) {
webUrl = nil;
}
if (webUrl && webUrlString) {
webUrl = [NSURL URLWithString:webUrlString];
}
return [BFAppLink appLinkWithSourceURL:destination
targets:linkTargets
webURL:webUrl];
}
@end

Some files were not shown because too many files have changed in this diff Show More