The steps of a simple http request
Alamofire . request ( . GET , "https://httpbin.org/get" ) . responseJSON { response in debugPrint ( reponse ) }
NSURLSession
in Manager
create a NSURLSessionDataTask
inside a Request
, then resume the task.
Once the server responsed, it will call the session’s delegate, which is the SessionDelegate
in manager, the global delegate will try find the subDelegate for the task by identifier. The subDelegate(TaskDelegate
) in Request
will handle the response, and disable queue.suspended
, after some of the response serializer, the completionBlock will be called.
Use protocol and extension to improve API
public protocol URLStringConvertible { var URLString : String { get } } extension String : URLStringConvertible { public var URLString : String { return self } } extension NSURLRequest : URLStringConvertible { public var URLString : String { return URL !. URLString } } public func request ( method : Method , _ URLString : URLStringConvertible , // you could pass NSURL/String/NSURLRequest parameters : [ String : AnyObject ]? = nil , encoding : ParameterEncoding = . URL , headers : [ String : String ]? = nil ) -> Request { return Manager . sharedInstance . request ( method , URLString , parameters : parameters , encoding : encoding , headers : headers ) }
Use subscript and dispatch_barrier to implement subdelegates
public final class SessionDelegate : NSObject , NSURLSessionDelegate , NSURLSessionTaskDelegate , NSURLSessionDataDelegate , NSURLSessionDownloadDelegate { private var subdelegates : [ Int : Request . TaskDelegate ] = [:] private let subdelegateQueue = dispatch_queue_create ( nil , DISPATCH_QUEUE_CONCURRENT ) 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 } } } // blablabla }
Block style API instead of delegate
weakify and strongify
self . delegate . sessionDidFinishEventsForBackgroundURLSession = { [ weak self ] session in if let strongSelf = self { strongSelf . backgroundCompletionHandler ?() } }
Response
There is a NSOperationQueue
in Request
, this queue won’t excute untill the request completed.
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 } }
Use Enum
to simplify API
public enum ParameterEncoding { case URL case URLEncodedInURL case JSON case PropertyList ( NSPropertyListFormat , NSPropertyListWriteOptions ) case Custom (( URLRequestConvertible , [ String : AnyObject ]?) -> ( NSMutableURLRequest , NSError ?)) public func encode ( URLRequest : URLRequestConvertible , parameters : [ String : AnyObject ]?) -> ( NSMutableURLRequest , NSError ?) ... } 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 ) }
Result<T, E> for Error Handling
public enum Result < Value , Error : ErrorType > { case Success ( Value ) case Failure ( Error ) ... public var value : Value ? { get } public var error : Error ? { get } } Alamofire . request ( . GET , "https://httpbin.org/get" ) . responseJSON { response in switch response . result { case . Success ( let value ): print ( value ) case . Failure ( let error ): print ( error ) } }
ResponseSerializer with Generic to support custom serializer
public struct ResponseSerializer < Value , Error : ErrorType > : ResponseSerializerType { public typealias SerializedObject = Value public typealias ErrorObject = Error public var serializeResponse : ( NSURLRequest ?, NSHTTPURLResponse ?, NSData ?, NSError ?) -> Result < Value , Error > public init ( serializeResponse : ( NSURLRequest ?, NSHTTPURLResponse ?, NSData ?, NSError ?) -> Result < Value , Error > ) } extension 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 ) dispatch_async ( queue ?? dispatch_get_main_queue ()) { let response = Response < T . SerializedObject , T . ErrorObject > ( request : self . request , response : self . response , data : self . delegate . data , result : result ) completionHandler ( response ) } } return self } } // JSONResponseSerializer as example extension Request { public static func JSONResponseSerializer ( options options : NSJSONReadingOptions = . AllowFragments ) -> ResponseSerializer < AnyObject , NSError > { return ResponseSerializer { _ , _ , data , error in guard error == nil else { return . Failure ( error ! ) } 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 . errorWithCode ( . JSONSerializationFailed , failureReason : failureReason ) return . Failure ( error ) } do { let JSON = try NSJSONSerialization . JSONObjectWithData ( validData , options : options ) return . Success ( JSON ) } catch { return . Failure ( error as NSError ) } } } public func responseJSON ( options options : NSJSONReadingOptions = . AllowFragments , completionHandler : Response < AnyObject , NSError > -> Void ) -> Self { return response ( responseSerializer : Request . JSONResponseSerializer ( options : options ), completionHandler : completionHandler ) } }
ServerTrustPolicy, Stream didn’t looked much.. -_-
Validation Chain
Alamofire . request ( . GET , "http://httpbin.org/get" , parameters : [ "foo" : "bar" ]) . validate ( statusCode : 200 ..< 300 ) . validate ( contentType : [ "application/json" ]) . response { response in print ( response ) }
extension Request { public enum ValidationResult { case Success case Failure ( NSError ) } public typealias Validation = ( NSURLRequest ?, NSHTTPURLResponse ) -> ValidationResult 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 } ... }
One more thing
The UnitTest cases in Alamofire is very clean and tidy, what’s importand is that it didn’t use any third party code. Very easy to learn how XCTest
works.
At last, here is the mind map for Alamofire, made by iThoughtX