Alamofire 是基于 NSURLSession
和 Foundation URL Loading System 构建的
出现频率最高的方法 Alamofire.request
使用了一个共享的实例 Alamofire.Manager
,它由默认的 NSURLSessionConfiguration
构造,下面两个方法是等价的
// 方法1 Alamofire.request(.GET, "http://httpbin.org/get") // 方法2 let manager = Alamofire.Manager.sharedInstance manager.request(NSURLRequest(URL: NSURL(string: "http://httpbin.org/get")))
其实无论是一个 request
, upload
或者 download
的结果,都是 Alamofire.Request 的实例。而一个 request 总是通过他的构造器创建,而非直接创建。
类似于 authenticate
, validate
, response
这些方法也总是返回一个 Self,即调用者 request,方便将他们链接起来。
Requests 可以暂停,恢复,和取消
除了 Alamofire 给我们提供的 strings, JSON, property lists 这三种响应序列化方法外,我们还可以通过为 Alamofire.Request
添加 extension,自定义序列化方法,
看似复杂,其实很简单,两个步骤:
response(queue:responseSerializer:completionHandler:) -> Self
方法 return Self
我们可以使用泛型来提供自动化、类型安全的 response 对象序列化
@objc public protocol ResponseObjectSerializable { init?(response: NSHTTPURLResponse, representation: AnyObject) } extension Request { public func responseObject<T: ResponseObjectSerializable>(completionHandler: (NSURLRequest, NSHTTPURLResponse?, T?, NSError?) -> Void) -> Self { let responseSerializer = GenericResponseSerializer<T> { request, response, data in let JSONResponseSerializer = Request.JSONResponseSerializer(options: .AllowFragments) let (JSON: AnyObject?, serializationError) = JSONResponseSerializer.serializeResponse(request, response, data) if let response = response, JSON: AnyObject = JSON { // 因为 T 遵守了 ResponseObjectSerializable ,所以可以直接用这两个参数初始化 return (T(response: response, representation: JSON), nil) } else { return (nil, serializationError) } } return response(responseSerializer: responseSerializer, completionHandler: completionHandler) } }
只要一个对象满足 ResponseObjectSerializable 协议,他就能使用 responseObject 进行序列化
final class User: ResponseObjectSerializable { let username: String let name: String @objc required init?(response: NSHTTPURLResponse, representation: AnyObject) { self.username = response.URL!.lastPathComponent! self.name = representation.valueForKeyPath("name") as! String } } // 序列化得到 User 类型 Alamofire.request(.GET, "http://example.com/users/mattt") .responseObject { (_, _, user: User?, _) in println(user) }
同样的道理,如果我们对响应序列化之后得到一个数组对象,就可以这么改一下
@objc public protocol ResponseCollectionSerializable { static func collection(#response: NSHTTPURLResponse, representation: AnyObject) -> [Self] } extension Alamofire.Request { public func responseCollection<T: ResponseCollectionSerializable>(completionHandler: (NSURLRequest, NSHTTPURLResponse?, [T]?, NSError?) -> Void) -> Self { let responseSerializer = GenericResponseSerializer<[T]> { request, response, data in let JSONSerializer = Request.JSONResponseSerializer(options: .AllowFragments) let (JSON: AnyObject?, serializationError) = JSONSerializer.serializeResponse(request, response, data) if let response = response, JSON: AnyObject = JSON { return (T.collection(response: response, representation: JSON), nil) } else { return (nil, serializationError) } } return response(responseSerializer: responseSerializer, completionHandler: completionHandler) } }
User 对象要实现 ResponseCollectionSerializable
协议的方法:
@objc final class User: ResponseObjectSerializable, ResponseCollectionSerializable { let username: String let name: String required init?(response: NSHTTPURLResponse, representation: AnyObject) { self.username = response.URL!.lastPathComponent! self.name = representation.valueForKeyPath("name") as! String } static func collection(#response: NSHTTPURLResponse, representation: AnyObject) -> [User] { var users: [User] = [] if let representation = representation as? [[String: AnyObject]] { for userRepresentation in representation { if let user = User(response: response, representation: userRepresentation) { users.append(user) } } } return users } } // 序列化之后得到数组对象 Alamofire.request(.GET, "http://example.com/users") .responseCollection { (_, _, users: [User]?, _) in println(users) }
凡是遵循 URLStringConvertible
协议的对象都能构造 URL strings ,该协议只有一个 Property
var URLString: String { get }
比如有一个 User 类遵循 URLStringConvertible
extension User: URLStringConvertible { static let baseURLString = "http://example.com" var URLString: String { return User.baseURLString + "/users//(username)/" } } // 直接使用 let user = User(username: "mattt") Alamofire.request(.GET, user) // http://example.com/users/mattt
凡是遵循 URLRequestConvertible
协议的对象都能构造 URL requests ,该协议只有一个 Property
var URLRequest: NSURLRequest { get }
NSURLRequest 默认是遵守该协议的,那么实现了这个协议的对象有什么用呢?可以直接当做 Alamofire 中 request
, upload
, download
方法的参数来使用,也就是说你不用写 reauest(...) 方法中的这些繁杂的参数
func request(method: Alamofire.Method, URLString: URLStringConvertible, parameters: [String : AnyObject]? = default, encoding: Alamofire.ParameterEncoding = default, headers: [String : String]? = default) -> Alamofire.Request
你直接可以传一个遵循 URLRequestConvertible
协议的对象就行了,当然要提前设置一下:
let URL = NSURL(string: "http://httpbin.org/post")! let mutableURLRequest = NSMutableURLRequest(URL: URL) mutableURLRequest.HTTPMethod = "POST" let parameters = ["foo": "bar"] var JSONSerializationError: NSError? = nil mutableURLRequest.HTTPBody = NSJSONSerialization.dataWithJSONObject(parameters, options: nil, error: &JSONSerializationError) mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type") Alamofire.request(mutableURLRequest)
有了上述基础,我们就可以将服务端的各种逻辑细节抽象出来,统一进行封装
// 遵守 URLRequestConvertible 协议 enum Router: URLRequestConvertible { static let baseURLString = "http://example.com" static let perPage = 50 case Search(query: String, page: Int) // MARK: URLRequestConvertible var URLRequest: NSURLRequest { // 用闭包函数得到一个二元组(path, parameters) let (path: String, parameters: [String: AnyObject]?) = { switch self { case .Search(let query, let page) where page > 1: return ("/search", ["q": query, "offset": Router.perPage * page]) case .Search(let query, _): return ("/search", ["q": query]) } }() // 根据上面的 元组 进行如下操作 let URL = NSURL(string: Router.baseURLString)! let URLRequest = NSURLRequest(URL: URL.URLByAppendingPathComponent(path)) let encoding = Alamofire.ParameterEncoding.URL return encoding.encode(URLRequest, parameters: parameters).0 } }
这样调用就很干净了
Alamofire.request(Router.Search(query: "foo bar", page: 1)) // http://example.com/search?q=foo%20bar&offset=50
enum Router: URLRequestConvertible { static let baseURLString = "http://example.com" static var OAuthToken: String? case CreateUser([String: AnyObject]) case ReadUser(String) case UpdateUser(String, [String: AnyObject]) case DestroyUser(String) var method: Alamofire.Method { switch self { case .CreateUser: return .POST case .ReadUser: return .GET case .UpdateUser: return .PUT case .DestroyUser: return .DELETE } } var path: String { switch self { case .CreateUser: return "/users" case .ReadUser(let username): return "/users//(username)" case .UpdateUser(let username, _): return "/users//(username)" case .DestroyUser(let username): return "/users//(username)" } } // MARK: URLRequestConvertible var URLRequest: NSURLRequest { let URL = NSURL(string: Router.baseURLString)! let mutableURLRequest = NSMutableURLRequest(URL: URL.URLByAppendingPathComponent(path)) mutableURLRequest.HTTPMethod = method.rawValue if let token = Router.OAuthToken { mutableURLRequest.setValue("Bearer /(token)", forHTTPHeaderField: "Authorization") } switch self { case .CreateUser(let parameters): return Alamofire.ParameterEncoding.JSON.encode(mutableURLRequest, parameters: parameters).0 case .UpdateUser(_, let parameters): return Alamofire.ParameterEncoding.URL.encode(mutableURLRequest, parameters: parameters).0 default: return mutableURLRequest } } }
执行查找操作
Alamofire.request(Router.ReadUser("mattt")) // GET /users/mattt
Alamofire 默认使用 Apple 的 Security framework 来验证证书链的有效性,这并不保证抵御中间人供给,为了减轻中间人攻击,应使用由 ServerTrustPolicy
提供的证书和 public key pinning
Public Key Pinning 允许网站详细说明网站的有效证书是哪一家 CA 发行的,不再随便接受证书管理器内的数百家 Root CA 之一发行的证书
ServerTrustPolicy 可以看做是一种 policy,列举了一些使用 HTTPS 连接服务器时,对 server trust 的验证规则,比如使用 PinCertificates
let serverTrustPolicy = ServerTrustPolicy.PinCertificates( certificates: ServerTrustPolicy.certificatesInBundle(), validateCertificateChain: true, validateHost: true )
一共有以下五种验证规则
ServerTrustPolicyManager 负责将存储『 服务器验证规则到特定 host 之间的映射关系 』,这样 Alamofire 就可以针对每一个 host 找出对应的 server trust policy.
let serverTrustPolicies: [String: ServerTrustPolicy] = [ "test.example.com": .PinCertificates( // Certificate chain 必须包含一个 pinned certificate certificates: ServerTrustPolicy.certificatesInBundle(), // Certificate chain 必须有效 validateCertificateChain: true, // Challenge host 必须匹配上面证书链的叶证书中的主机 validateHost: true ), // 不验证证书链,总是让 TLS 握手成功 "insecure.expired-apis.com": .DisableEvaluation ] let manager = Manager( configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies) )