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 协议的对象就行了,当然该对象要提前设置一下,比如下面代码,我们最后直接使用了 Alamofire.request(mutableURLRequest),request 方法只有一个参数

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-

如果感觉此文对你有帮助,请随意打赏支持作者 😘

chengway

认清生活真相之后依然热爱它!

Subscribe to Talk is cheap, Show me the world!

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!