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