转载

Alamofire 学习笔记(二)

高级用法

Alamofire 是基于 NSURLSession 和 Foundation URL Loading System 构建的

Manager

出现频率最高的方法 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

其实无论是一个 requestupload 或者 download 的结果,都是 Alamofire.Request 的实例。而一个 request 总是通过他的构造器创建,而非直接创建。

类似于 authenticate , validate , response 这些方法也总是返回一个 Self,即调用者 request,方便将他们链接起来。

Requests 可以暂停,恢复,和取消

Response Serialization(响应序列化)

除了 Alamofire 给我们提供的 strings, JSON, property lists 这三种响应序列化方法外,我们还可以通过为 Alamofire.Request 添加 extension,自定义序列化方法,

看似复杂,其实很简单,两个步骤:

  1. 定义一个实现 ResponseSerializer 协议的方法
  2. 然后用 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 协议

凡是遵循 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 协议

凡是遵循 URLRequestConvertible 协议的对象都能构造 URL requests ,该协议只有一个 Property

var URLRequest: NSURLRequest { get }

NSURLRequest 默认是遵守该协议的,那么实现了这个协议的对象有什么用呢?可以直接当做 Alamofirerequest , 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)

有了上述基础,我们就可以将服务端的各种逻辑细节抽象出来,统一进行封装

API Parameter Abstraction

// 遵守 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

增删查改和鉴权(CRUD & Authorization)

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

ServerTrustPolicy 可以看做是一种 policy,列举了一些使用 HTTPS 连接服务器时,对 server trust 的验证规则,比如使用 PinCertificates

let serverTrustPolicy = ServerTrustPolicy.PinCertificates(     certificates: ServerTrustPolicy.certificatesInBundle(),     validateCertificateChain: true,     validateHost: true )

一共有以下五种验证规则

  • PerformDefaultEvaluation
  • PinCertificates
  • PinPublicKeys
  • DisableEvaluation
  • CustomEvaluation

Server Trust Policy Manager

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) ) 
-EOF-
正文到此结束
Loading...