RWPickFlavor 0.2.0

RWPickFlavor 0.2.0

测试已测试
语言语言 SwiftSwift
许可 MIT
发布上次发布2016年3月
SPM支持SPM

pick flavor 维护。



 
依赖
BetterBaseClasses~> 1.0
Alamofire~> 2.0
MBProgressHUD~> 0.9.0
 

  • 作者:
  • 杜淑春

Alamofire: Elegant Networking in Swift

Alamofire是用Swift编写的HTTP网络库。

功能

  • [x] 可连续调用的请求/响应方法
  • [x] URL/JSON/plist参数编码
  • [x] 上传文件/数据/流/表单数据
  • [x] 使用请求或恢复数据下载
  • [x] 使用NSURLCredential进行身份验证
  • [x] HTTP响应验证
  • [x] TLS证书和公钥固定
  • [x] 进度闭包与NSProgress
  • [x] cURL调试输出
  • [x] 完整的单元测试覆盖范围
  • [x] 完整文档

系统要求

  • iOS 8.0+ / Mac OS X 10.9+ / watchOS 2
  • Xcode 7.0+

迁移指南

交流

  • 如果您

    需要帮助

    ,请使用Stack Overflow。 (标签‘alamofire’)
  • 如果您

    想问一般性的问题

    ,请使用Stack Overflow
  • 如果您

    找到了一个错误

    ,请打开一个问题。
  • 如果您

    有功能请求

    ,请打开一个问题。
  • 如果您

    想贡献

    ,请提交一个pull请求。

安装

内嵌框架需要最低部署目标为iOS 8或OS X Mavericks (10.9)。

由于不支持框架,Alamofire不再支持iOS 7。没有框架,运行Travis-CI对iOS 7进行测试将需要第二个复制的目标。单独的测试套件需要导入所有Swift文件,并且测试需要复制和重写。这种分割维护起来太难,不能保证Alamofire生态系统可能达到的最高质量。

手动

如果您不想使用上述任何一个依赖管理器,您可以手动将Alamofire集成到项目中。

嵌入式框架

  • 打开终端,使用cd命令进入你的顶级项目目录,然后执行以下命令“if”你的项目尚未初始化为git仓库;
$ git init
  • 使用以下命令添加Alamofire作为git 子模块
$ git submodule add https://github.com/Alamofire/Alamofire.git
  • 打开新创建的Alamofire文件夹,将Alamofire.xcodeproj拖入你的应用程序的Xcode项目的项目导航器中。

    它应该嵌套在你的应用程序的蓝色项目图标下面。它是在所有其他Xcode小组件之上还是之下并不重要。

  • 在项目导航器中选择Alamofire.xcodeproj并验证部署目标是否与你的应用程序目标相匹配。

  • 接下来,在项目导航器中选择你的应用程序项目(蓝色项目图标)以导航到目标配置窗口,并在侧边栏的“目标”标题下选择应用程序目标。
  • 在该窗口顶部的标签栏中,打开“通用”面板。
  • 在“嵌入式二进制”部分下点击+按钮。
  • 你会看到两个不同的Alamofire.xcodeproj文件夹,每个文件夹内部都有一个Products文件夹,包含两个不同版本的Alamofire.framework

    选择哪个Products文件夹并不重要,但是选择顶部的或底部的Alamofire.framework很重要。

  • 对于iOS选择顶部的Alamofire.framework,对于OS X选择底部的。

    你可以通过检查项目的构建日志来验证你选择了哪个版本。构建目标为Alamofire将被列为Alamofire iOSAlamofire OSX

  • 就这样!

Alamofire.framework将自动作为一个目标依赖、链接框架和嵌入式框架添加到一个复制文件构建阶段,这就是所有在模拟器和设备上构建所需要做的。


使用方法

发送请求

import Alamofire

Alamofire.request(.GET, "http://httpbin.org/get")

处理响应

Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"])
         .response { request, response, data, error in
              print(request)
              print(response)
              print(error)
          }

在Alamofire中,网络操作是以异步方式完成的。对于不熟悉这个概念的开发者来说,异步编程可能是一个挫折的来源,但这样做有很好的原因

而不是阻塞执行等待服务器响应,指定了一个callback来处理一旦接收到响应。请求的结果仅在响应处理程序的范围内可用。在服务器接收到的响应或数据上执行的任何执行都必须在处理程序内部完成。

响应序列化

内置响应方法

  • response()
  • responseString(encoding: NSStringEncoding)
  • responseJSON(options: NSJSONReadingOptions)
  • responsePropertyList(options: NSPropertyListReadOptions)

响应字符串处理程序

Alamofire.request(.GET, "http://httpbin.org/get")
         .responseString { _, _, result in
             print("Success: \(result.isSuccess)")
             print("Response String: \(result.value)")
         }

响应JSON处理程序

Alamofire.request(.GET, "http://httpbin.org/get")
         .responseJSON { _, _, result in
             print(result)
             debugPrint(result)
         }

链式响应处理程序

响应处理程序甚至可以被链式调用

Alamofire.request(.GET, "http://httpbin.org/get")
         .responseString { _, _, result in
             print("Response String: \(result.value)")
         }
         .responseJSON { _, _, result in
             print("Response JSON: \(result.value)")
         }

HTTP方法

Alamofire.Method列出了RFC 7231 §4.3中定义的HTTP方法。

public enum Method: String {
    case OPTIONS, GET, HEAD, POST, PUT, PATCH, DELETE, TRACE, CONNECT
}

这些值可以作为Alamofire.request方法的第一个参数传递。

Alamofire.request(.POST, "http://httpbin.org/post")

Alamofire.request(.PUT, "http://httpbin.org/put")

Alamofire.request(.DELETE, "http://httpbin.org/delete")

参数

带 URL 编码参数的 GET 请求

Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"])
// http://httpbin.org/get?foo=bar

带 URL 编码参数的 POST 请求

let parameters = [
    "foo": "bar",
    "baz": ["a", 1],
    "qux": [
        "x": 1,
        "y": 2,
        "z": 3
    ]
]

Alamofire.request(.POST, "http://httpbin.org/post", parameters: parameters)
// HTTP body: foo=bar&baz[]=a&baz[]=1&qux[x]=1&qux[y]=2&qux[z]=3

参数编码

参数还可以编码为 JSON、属性列表或任何自定义格式,使用 ParameterEncoding 枚举

enum ParameterEncoding {
    case URL
    case URLEncodedInURL
    case JSON
    case PropertyList(format: NSPropertyListFormat, options: NSPropertyListWriteOptions)
    case Custom((URLRequestConvertible, [String: AnyObject]?) -> (NSMutableURLRequest, NSError?))

    func encode(request: NSURLRequest, parameters: [String: AnyObject]?) -> (NSURLRequest, NSError?)
    { ... }
}
  • URL:一个查询字符串,用于设置或追加到任何现有 URL 查询(用于 GETHEADDELETE 请求),或设置为请求任何其他 HTTP 方法的主体。带有 HTTP 体的编码请求的 Content-Type HTTP 头字段设置为 application/x-www-form-urlencoded由于没有发布关于如何编码集合类型的规范,Alamofire 遵循了在数组值键后附加 [] 的惯例(foo[]=1&foo[]=2),并在嵌套字典值键周围附加方括号(foo[bar]=baz)。
  • URLEncodedInURL:创建要设置或追加到任何现有 URL 查询的查询字符串。使用与 .URL 情况相同的实现,但始终将编码结果应用于 URL。
  • JSON:使用 NSJSONSerialization 将参数对象的 JSON 表示形式创建为一个 JSON,将其用作请求的主体。编码请求的 Content-Type HTTP 头字段设置为 application/json
  • PropertyList:使用 NSPropertyListSerialization 根据关联的格式和写入选项值创建参数对象的 plist 表示形式,将其用作请求的主体。编码请求的 Content-Type HTTP 头字段设置为 application/x-plist
  • Custom:使用关联的闭包值根据现有请求和参数构造新的请求。

对 NSURLRequest 的手动参数编码

let URL = NSURL(string: "http://httpbin.org/get")!
var request = NSURLRequest(URL: URL)

let parameters = ["foo": "bar"]
let encoding = Alamofire.ParameterEncoding.URL
(request, _) = encoding.encode(request, parameters: parameters)

带有 JSON 编码参数的 POST 请求

let parameters = [
    "foo": [1,2,3],
    "bar": [
        "baz": "qux"
    ]
]

Alamofire.request(.POST, "http://httpbin.org/post", parameters: parameters, encoding: .JSON)
// HTTP body: {"foo": [1, 2, 3], "bar": {"baz": "qux"}}

HTTP 头部

全局 request 方法直接支持向 Request 添加自定义 HTTP 头部。这使得轻松将 HTTP 头部附加到可能不断变化的 Request

对于不改变的 HTTP 头部,建议在 NSURLSessionConfiguration 上设置它们,以便自动应用于由底层 NSURLSession 创建的任何 NSURLSessionTask

let headers = [
    "Authorization": "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
    "Content-Type": "application/x-www-form-urlencoded"
]

Alamofire.request(.GET, "http://httpbin.org/get", headers: headers)
         .responseJSON { _, _, result in
             debugPrint(result)
         }

缓存

缓存由系统框架级 NSURLCache 处理。

上传

支持的上传类型

  • 文件
  • 数据
  • MultipartFormData

上传文件

let fileURL = NSBundle.mainBundle().URLForResource("Default", withExtension: "png")
Alamofire.upload(.POST, "http://httpbin.org/post", file: fileURL)

上传进度

Alamofire.upload(.POST, "http://httpbin.org/post", file: fileURL)
         .progress { bytesWritten, totalBytesWritten, totalBytesExpectedToWrite in
             print(totalBytesWritten)

             // This closure is NOT called on the main queue for performance
             // reasons. To update your ui, dispatch to the main queue.
             dispatch_async(dispatch_get_main_queue) {
                 print("Total bytes written on main queue: \(totalBytesWritten)")
             }
         }
         .responseJSON { request, response, result in
             debugPrint(result)
         }

上传 MultipartFormData

Alamofire.upload(
    .POST,
    URLString: "http://httpbin.org/post",
    multipartFormData: { multipartFormData in
        multipartFormData.appendBodyPart(fileURL: unicornImageURL, name: "unicorn")
        multipartFormData.appendBodyPart(fileURL: rainbowImageURL, name: "rainbow")
    },
    encodingCompletion: { encodingResult in
        switch encodingResult {
        case .Success(let upload, _, _):
            upload.responseJSON { request, response, result in
                debugPrint(result)
            }
        case .Failure(let encodingError):
            print(encodingError)
        }
    }
)

下载

支持的下载类型

  • 请求
  • 续传数据

下载一个文件

Alamofire.download(.GET, "http://httpbin.org/stream/100") { temporaryURL, response in
    let fileManager = NSFileManager.defaultManager()
    if let directoryURL = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0] as? NSURL {
        let pathComponent = response.suggestedFilename
        return directoryURL.URLByAppendingPathComponent(pathComponent!)
    }

    return temporaryURL
}

使用默认下载目的地

let destination = Alamofire.Request.suggestedDownloadDestination(directory: .DocumentDirectory, domain: .UserDomainMask)
Alamofire.download(.GET, "http://httpbin.org/stream/100", destination: destination)

带有进度下载一个文件

Alamofire.download(.GET, "http://httpbin.org/stream/100", destination: destination)
         .progress { bytesRead, totalBytesRead, totalBytesExpectedToRead in
             print(totalBytesRead)

             // This closure is NOT called on the main queue for performance
             // reasons. To update your ui, dispatch to the main queue.
             dispatch_async(dispatch_get_main_queue) {
                 print("Total bytes read on main queue: \(totalBytesRead)")
             }
         }
         .response { request, response, _, error in
             print(response)
         }

访问失败的下载的续传数据

Alamofire.download(.GET, "http://httpbin.org/stream/100", destination: destination)
         .response { request, response, data, error in
             if let
                 data = data,
                 resumeDataString = NSString(data: data, encoding: NSUTF8StringEncoding)
             {
                 print("Resume Data: \(resumeDataString)")
             } else {
                 print("Resume Data was empty")
             }
         }

如果存在,data 参数将自动填充 resumeData

let download = Alamofire.download(.GET, "http://httpbin.org/stream/100", destination: destination)
download.response { request, response, data, error in
    if let
        resumeData = download.resumeData,
        resumeDataString = NSString(data: data, encoding: NSUTF8StringEncoding)
    {
        print("Resume Data: \(resumeDataString)")
    } else {
        print("Resume Data was empty")
    }
}

认证

认证在系统框架级别通过 NSURLCredentialNSURLAuthenticationChallenge 来处理。

支持的认证方案

HTTP Basic 认证

Request 上的 authenticate 方法将自动提供 NSURLCredential 给合适的 NSURLAuthenticationChallenge

let user = "user"
let password = "password"

Alamofire.request(.GET, "https://httpbin.org/basic-auth/\(user)/\(password)")
         .authenticate(user: user, password: password)
         .response { request, response, _, error in
             print(response)
         }

根据您的服务器实现,适当的也可能会使用 Authorization

let user = "user"
let password = "password"

let credentialData = "\(user):\(password)".dataUsingEncoding(NSUTF8StringEncoding)!
let base64Credentials = credentialData.base64EncodedStringWithOptions(nil)

let headers = ["Authorization": "Basic \(base64Credentials)"]

Alamofire.request(.GET, "http://httpbin.org/basic-auth/user/password", headers: headers)
         .responseJSON { _, _, result in
             print(result)
         }

使用 NSURLCredential 认证

let user = "user"
let password = "password"

let credential = NSURLCredential(user: user, password: password, persistence: .ForSession)

Alamofire.request(.GET, "https://httpbin.org/basic-auth/\(user)/\(password)")
         .authenticate(usingCredential: credential)
         .response { request, response, _, error in
             print(response)
         }

验证

默认情况下,Alamofire 将任何完成的请求视为成功,无论响应内容如何。在响应处理程序之前调用 validate将会在响应有不可接受的 HTTP 状态码或 MIME 类型时引发错误。

手动验证

Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"])
         .validate(statusCode: 200..<300)
         .validate(contentType: ["application/json"])
         .response { _, _, _, error in
             print(error)
         }

自动验证

自动验证状态码是否位于 200...299 范围内,以及响应的 Content-Type 头是否与请求的 Accept 头匹配(如果有提供)。

Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"])
         .validate()
         .responseJSON { _, _, result in
             switch result {
             case .Success:
                 print("Validation Successful")
             case .Failure(_, let error):
                 print(error)
             }
         }

可打印

let request = Alamofire.request(.GET, "http://httpbin.org/ip")

print(request)
// GET http://httpbin.org/ip (200)

调试打印

let request = Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"])

debugPrint(request)

输出(cURL)

$ curl -i \
    -H "User-Agent: Alamofire" \
    -H "Accept-Encoding: Accept-Encoding: gzip;q=1.0,compress;q=0.5" \
    -H "Accept-Language: en;q=1.0,fr;q=0.9,de;q=0.8,zh-Hans;q=0.7,zh-Hant;q=0.6,ja;q=0.5" \
    "http://httpbin.org/get?foo=bar"


高级使用

Alamofire 是建立在 NSURLSession 和 Foundation URL 加载系统之上的。为了充分利用此框架,建议您熟悉底层网络堆栈的概念和能力。

推荐阅读

管理器

顶级便捷方法如Alamofire.request使用Alamofire.Manager的共享实例,该实例配置了默认的NSURLSessionConfiguration

因此,以下两个语句是等价的

Alamofire.request(.GET, "http://httpbin.org/get")
let manager = Alamofire.Manager.sharedInstance
manager.request(NSURLRequest(URL: NSURL(string: "http://httpbin.org/get")))

应用程序可以创建适用于后台和短暂会话的管理器,以及针对默认会话配置的新管理者,例如用于默认头信息(HTTPAdditionalHeaders)或超时间隔(timeoutIntervalForRequest)。

使用默认配置创建管理者

let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let manager = Alamofire.Manager(configuration: configuration)

使用后台配置创建管理者

let configuration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier("com.example.app.background")
let manager = Alamofire.Manager(configuration: configuration)

使用短暂配置创建管理者

let configuration = NSURLSessionConfiguration.ephemeralSessionConfiguration()
let manager = Alamofire.Manager(configuration: configuration)

修改会话配置

var defaultHeaders = Alamofire.Manager.sharedInstance.session.configuration.HTTPAdditionalHeaders ?? [:]
defaultHeaders["DNT"] = "1 (Do Not Track Enabled)"

let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.HTTPAdditionalHeaders = defaultHeaders

let manager = Alamofire.Manager(configuration: configuration)

不推荐用于AuthorizationContent-Type头信息。相反,应使用URLRequestConvertibleParameterEncoding

请求

requestuploaddownload方法的结果是一个Alamofire.Request实例。请求总是使用所有者管理器的构造方法创建,而不是直接初始化。

authenticatevalidateresponse等方法返回调用者以简化链式调用。

请求可以被挂起、恢复和取消

  • suspend():挂起底层任务和dispatch队列
  • resume():恢复底层任务和dispatch队列。如果所有者管理器未将startRequestsImmediately设置为true,则请求必须调用resume()才能启动。
  • cancel():取消底层任务,产生一个传递给任何已注册响应处理器的错误。

响应序列化

创建自定义响应序列化器

Alamofire提供了字符串、JSON和属性列表的内置响应序列化,但可以在Alamofire.Request的扩展中添加其他序列化器。

例如,以下是使用Ono的响应处理器可能实现的示例:

extension Request {
    public static func XMLResponseSerializer() -> GenericResponseSerializer<ONOXMLDocument> {
        return GenericResponseSerializer { request, response, data in
            guard let validData = data else {
                let failureReason = "Data could not be serialized. Input data was nil."
                let error = Error.errorWithCode(.DataSerializationFailed, failureReason: failureReason)
                return .Failure(data, error)
            }

            do {
                let XML = try ONOXMLDocument(data: validData)
                return .Success(XML)
            } catch {
                return .Failure(data, error as NSError)
            }
        }
    }

    public func responseXMLDocument(completionHandler: (NSURLRequest?, NSHTTPURLResponse?, Result<ONOXMLDocument>) -> Void) -> Self {
        return response(responseSerializer: Request.XMLResponseSerializer(), completionHandler: completionHandler)
    }
}

泛型响应对象序列化

可以使用泛型来提供自动、类型安全的响应对象序列化。

public protocol ResponseObjectSerializable {
    init?(response: NSHTTPURLResponse, representation: AnyObject)
}

extension Request {
    public func responseObject<T: ResponseObjectSerializable>(completionHandler: (NSURLRequest?, NSHTTPURLResponse?, Result<T>) -> Void) -> Self {
        let responseSerializer = GenericResponseSerializer<T> { request, response, data in
            let JSONResponseSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
            let result = JSONResponseSerializer.serializeResponse(request, response, data)

            switch result {
            case .Success(let value):
                if let
                    response = response,
                    responseObject = T(response: response, representation: value)
                {
                    return .Success(responseObject)
                } else {
                    let failureReason = "JSON could not be serialized into response object: \(value)"
                    let error = Error.errorWithCode(.JSONSerializationFailed, failureReason: failureReason)
                    return .Failure(data, error)
                }
            case .Failure(let data, let error):
                return .Failure(data, error)
            }
        }

        return response(responseSerializer: responseSerializer, completionHandler: completionHandler)
    }
}
final class User: ResponseObjectSerializable {
    let username: String
    let name: String

    init?(response: NSHTTPURLResponse, representation: AnyObject) {
        self.username = response.URL!.lastPathComponent!
        self.name = representation.valueForKeyPath("name") as! String
    }
}
Alamofire.request(.GET, "http://example.com/users/mattt")
         .responseObject { (_, _, result: Result<User>) in
             debugPrint(result)
         }

相同的做法也可以用来处理返回对象集合表示的后端接口

public protocol ResponseCollectionSerializable {
    static func collection(response response: NSHTTPURLResponse, representation: AnyObject) -> [Self]
}

extension Alamofire.Request {
    public func responseCollection<T: ResponseCollectionSerializable>(completionHandler: (NSURLRequest?, NSHTTPURLResponse?, Result<[T]>) -> Void) -> Self {
        let responseSerializer = GenericResponseSerializer<[T]> { request, response, data in
            let JSONSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
            let result = JSONSerializer.serializeResponse(request, response, data)

            switch result {
            case .Success(let value):
                if let response = response {
                    return .Success(T.collection(response: response, representation: value))
                } else {
                    let failureReason = "Response collection could not be serialized due to nil response"
                    let error = Error.errorWithCode(.JSONSerializationFailed, failureReason: failureReason)
                    return .Failure(data, error)
                }
            case .Failure(let data, let error):
                return .Failure(data, error)
            }
        }

        return response(responseSerializer: responseSerializer, completionHandler: completionHandler)
    }
}
final class User: ResponseObjectSerializable, ResponseCollectionSerializable {
    let username: String
    let name: String

    init?(response: NSHTTPURLResponse, representation: AnyObject) {
        self.username = response.URL!.lastPathComponent!
        self.name = representation.valueForKeyPath("name") as! String
    }

    static func collection(response 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 { (_, _, result: Result<[User]>) in
             debugPrint(result)
         }

URLStringConvertible

采用URLStringConvertible协议的类型可用来构建URL字符串,然后将这些字符串用于构建URL请求。NSStringNSURLNSURLComponentsNSURLRequest默认符合URLStringConvertible,使得它们都可以作为URLString参数传递给requestuploaddownload方法。

let string = NSString(string: "http://httpbin.org/post")
Alamofire.request(.POST, string)

let URL = NSURL(string: string)!
Alamofire.request(.POST, URL)

let URLRequest = NSURLRequest(URL: URL)
Alamofire.request(.POST, URLRequest) // overrides `HTTPMethod` of `URLRequest`

let URLComponents = NSURLComponents(URL: URL, resolvingAgainstBaseURL: true)
Alamofire.request(.POST, URLComponents)

鼓励与Web应用程序进行大量交互的应用程序让自定义类型符合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请求。NSURLRequest默认符合URLRequestConvertible,允许它直接传递给requestuploaddownload方法(这是指定单个请求自定义HTTP体的推荐方式)

let URL = NSURL(string: "http://httpbin.org/post")!
let mutableURLRequest = NSMutableURLRequest(URL: URL)
mutableURLRequest.HTTPMethod = "POST"

let parameters = ["foo": "bar"]

do {
    mutableURLRequest.HTTPBody = try NSJSONSerialization.dataWithJSONObject(parameters, options: NSJSONWritingOptions())
} catch {
    // No-op
}

mutableURLRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")

Alamofire.request(mutableURLRequest)

鼓励与网络应用有重大交互的应用程序,将自定义类型符合URLRequestConvertible,以确保请求端点的连续性。这种方法可以用于抽象服务器端的不一致性,并实现类型安全的路由,同时管理认证凭据和其他状态。

API参数抽象

enum Router: URLRequestConvertible {
    static let baseURLString = "http://example.com"
    static let perPage = 50

    case Search(query: String, page: Int)

    // MARK: URLRequestConvertible

    var URLRequest: NSMutableURLRequest {
        let result: (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(result.path))
        let encoding = Alamofire.ParameterEncoding.URL

        return encoding.encode(URLRequest, parameters: result.parameters).0
    }
}
Alamofire.request(Router.Search(query: "foo bar", page: 1)) // ?q=foo%20bar&offset=50

CRUD & 授权

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: NSMutableURLRequest {
        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

安全

在与服务器和Web服务通信时使用安全的HTTPS连接是保护敏感数据的重要步骤。默认情况下,Alamofire将使用Security框架提供的内置验证来评估服务器提供的证书链。虽然这保证了证书链是有效的,但它并不能防止中间人(MITM)攻击或其他潜在漏洞。为了减轻MITM攻击,处理敏感客户数据或财务信息的应用程序应使用ServerTrustPolicy提供的证书或公钥固定。

服务器信任策略

ServerTrustPolicy枚举评估在通过安全的HTTPS连接连接到服务器时,由NSURLAuthenticationChallenge提供的通常的服务器信任。

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

有各种服务器信任评估情况,让您完全控制验证过程。

  • PerformDefaultEvaluation:使用默认的服务器信任评估同时允许您控制是否验证挑战提供的宿主。
  • PinCertificates:使用固定的证书来验证服务器信任。如果固定的证书之一与服务器证书之一匹配,则认为服务器信任是有效的。
  • PinPublicKeys:使用固定的公钥来验证服务器信任。如果固定的公钥之一与服务器证书的公钥之一匹配,则认为服务器信任是有效的。
  • DisableEvaluation:禁用所有评估,因此始终认为任何服务器信任都是有效的。
  • CustomEvaluation:使用关联的闭包来评估服务器信任的有效性,从而让您完全控制验证过程。请谨慎使用。

服务器信任策略管理器

ServerTrustPolicyManager负责将服务器信任策略的内部分配映射到特定的宿主。这允许Alamofire针对不同的服务器信任策略评估每个宿主。

let serverTrustPolicies: [String: ServerTrustPolicy] = [
    "test.example.com": .PinCertificates(
        certificates: ServerTrustPolicy.certificatesInBundle(),
        validateCertificateChain: true,
        validateHost: true
    ),
    "insecure.expired-apis.com": .DisableEvaluation
]

let manager = Manager(
    configuration: NSURLSessionConfiguration.defaultSessionConfiguration(),
    serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)

请务必保留对新Manager实例的引用,否则当您的manager被销毁时,所有请求都将被取消。

这些服务器信任策略将导致以下行为:

  • test.example.com将始终使用带有证书链和宿主验证的固定证书,因此需要满足以下标准,即允许TLS握手成功:
    • 证书链必须有效。
    • 证书链必须包含固定证书之一。
    • 挑战主机必须与证书链中叶证书中的主机匹配。

  • insecure.expired-apis.com永远不会评估证书链,并始终允许TLS握手成功。
  • 所有其他主机将使用苹果提供的默认评估。
子类化服务器信任策略管理器

如果您发现自己需要更灵活的服务器信任策略匹配行为(例如通配符域名),则可以子类化ServerTrustPolicyManager并重写serverTrustPolicyForHost方法,以使用您自己的自定义实现。

class CustomServerTrustPolicyManager: ServerTrustPolicyManager {
    override func serverTrustPolicyForHost(host: String) -> ServerTrustPolicy? {
        var policy: ServerTrustPolicy?

        // Implement your custom domain matching behavior...

        return policy
    }
}

验证主机

.PerformDefaultEvaluation.PinCertificates.PinPublicKeys服务器信任策略都接受一个validateHost参数。将值设置为true将导致服务器信任评估验证证书中匹配的主机名与挑战的主机名。如果它们不匹配,则评估失败。将validateHost的值设置为false将仍然评估整个证书链,但不会验证叶证书的域名。

建议在生产环境中将validateHost始终设置为true

验证证书链

使用validateCertificateChain参数验证证书和公钥都有选项使用证书链。通过将此值设置为true,除了执行对已固定证书或公钥的位比较外,还会评估整个证书链。将值设置为false将跳过证书链验证,但仍然执行位比较。

存在一些情况,其中禁用证书链验证可能是合理的。禁用验证最常见的用例是自签名和过期的证书。在这两种情况下,评估都将失败,但位比较仍然可以确保您从服务器收到的证书正是您期望的。

建议在生产环境中将validateCertificateChain始终设置为true


组件库

为了使Alamofire专注于核心网络实现,由Alamofire软件基金会创建了额外的组件库,为Alamofire生态系统带来了更多功能。

  • AlamofireImage - 一个图像库,包括图像响应序列化器、UIImageUIImageView扩展、自定义图像过滤器、自动清理内存缓存和基于优先级的图像下载系统。

开放Rdars

以下rdars对Alamofire当前实施有影响。

  • rdar://22024442 - Swift 2.0编译器在优化构建中因[SecCertificate]数组而崩溃
  • rdar://21349340 - 编译器由于测试用例中的toll-free bridging问题而抛出警告
  • rdar://22307360 - Swift #available检查与最小部署目标不正确

常见问题解答

Alamofire这个名字的由来是什么?

Alamofire以德克萨斯州的州花蓝色千日的混合变种——阿拉莫火花的名字命名。[链接](https://aggie-horticulture.tamu.edu/wildseed/alamofire.html)


致谢

Alamofire由Alamofire软件基金会拥有和维护。[链接](http://alamofire.org) 您可以在Twitter上关注它们@[AlamofireSF](https://twitter.com/AlamofireSF),获取项目更新和版本发布。

安全披露

如果您认为自己已发现Alamofire的安全漏洞,请尽快通过电子邮件报告:[安全邮箱]([email protected])。请勿将其发布到公共问题跟踪器。

许可

Alamofire遵循MIT许可协议。有关详细信息,请参阅LICENSE文件。