CGT_Pod_Swift 1.1.8

CGT_Pod_Swift 1.1.8

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

Rahul Bansal 维护。



 
依赖关系
Alamofire~> 3.4
MBProgressHUD>= 0
ObjectMapper~> 1.3
Kingfisher~> 2.5.0
 

  • Rahul Bansal

Alamofire: Elegant Networking in Swift

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

功能

  • [x] 链式请求/响应方法
  • [x] URL/JSON/plist 参数编码
  • [x] 上传文件/数据/流/MultipartFormData
  • [x] 使用请求或暂停数据下载
  • [x] 使用 NSURLCredential 进行身份验证
  • [x] HTTP 响应验证
  • [x] TLS 证书和公钥固定
  • [x] 进度闭包 & NSProgress
  • [x] cURL 调试输出
  • [x] 完整的单元测试覆盖率
  • [x] 完整文档

组件库

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

  • AlamofireImage - 一个图像库,包括图像响应序列化器、UIImageUIImageView 扩展、自定义图像过滤器、自动清除内存缓存以及在基于优先级的图像下载系统。
  • AlamofireNetworkActivityIndicator - 使用 Alamofire 控制 iOS 上网络活动指示器的可见性。它包含可配置的延迟计时器以帮助减轻闪光,并可以支持不由 Alamofire 管理的 NSURLSession 实例。

需求

  • iOS 8.0+ / Mac OS X 10.9+ / tvOS 9.0+ / watchOS 2.0+
  • Xcode 8.0+
  • Swift 2.2 或 2.3

迁移指南

交流

  • 如果您 需要帮助,请使用 Stack Overflow。 (Tag ‘alamofire’)
  • 如果您想 提出一个问题,请使用 Stack Overflow
  • 如果您 发现了错误,请创建一个 Issue。
  • 如果您有功能请求,请开启一个问题。
  • 如果您想做出贡献,请提交一个合并请求。

安装

嵌入式框架需要iOS 8或OS X Mavericks (10.9)或更高版本的最低部署目标。

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

手动安装

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

嵌入式框架

  • 打开终端,使用cd进入您顶级项目目录,然后运行以下命令(如果您还没有将项目初始化为git仓库)
$ git init
  • 通过运行以下命令将Alamofire作为一个git 子模块添加
$ git submodule add https://github.com/Alamofire/Alamofire.git
  • 打开新的Alamofire文件夹,并将Alamofire.xcodeproj拖入您应用程序Xcode项目的“项目导航器。

    它应该位于您的应用程序蓝色项目图标之下。它在所有其他Xcode组之上或之下并不重要。

  • 在项目导航器中选择Alamofire.xcodeproj,并验证其部署目标是否与您的应用程序目标的部署目标一致。

  • 接下来,在项目导航器中选择您的应用程序项目(蓝色项目图标),前往目标配置窗口,并在侧边栏的“ targets”标题下选择应用程序目标。
  • 在该窗口顶部的标签栏中,打开“ general”面板。
  • 在“ Embedded Binaries”部分下单击+按钮。
  • 您将看到两个不同的Alamofire.xcodeproj文件夹,每个文件夹中都有两个不同的版本,嵌套在“ Products”文件夹中的Alamofire.framework中。

    您可以选择哪个“ Products”文件夹无关紧要,但您选择的顶层或底层的Alamofire.framework则很重要。

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

    您可以通过检查您的项目构建日志来验证您选择的是哪一个。您的构建目标将被列為“ Alamofire iOS”或“ Alamofire OSX”。

  • 就是这样!

Alamofire.framework会自动添加为目标依赖、链接框架和副本文件构建阶段的嵌入式框架添加,这就是您在模拟器和设备上构建所需要的一切。


用法

发送请求

import Alamofire

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

响应处理

Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"])
         .responseJSON { response in
             print(response.request)  // original URL request
             print(response.response) // URL response
             print(response.data)     // server data
             print(response.result)   // result of response serialization

             if let JSON = response.result.value {
                 print("JSON: \(JSON)")
             }
         }

在Alamofire中的网络操作是异步进行的。对不熟悉异步编程概念的程序员来说,异步编程可能是一个让人头痛的问题,但是有非常好的理由这么做。

与其阻塞执行以等待服务器的响应,不如指定一个回调来处理响应。请求的结果仅在响应处理程序的作用域内可用。任何依赖于响应或从服务器接收到的数据的执行必须在处理程序内完成。

验证

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

手动验证

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

自动验证

自动验证状态码在200...299范围内,并且响应的Content-Type头与请求的Accept头匹配(如果提供的话)。

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

响应序列化

内置响应方法

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

响应处理程序

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

response序列化器不会评估任何响应数据。它只将所有信息直接从URL会话代理中转发。我们强烈建议您利用其他响应序列化器,利用ResponseResult类型。

响应数据处理程序

Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"])
         .validate()
         .responseData { response in
             print(response.request)
             print(response.response)
             print(response.result)
          }

响应字符串处理程序

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

响应JSON处理程序

Alamofire.request(.GET, "https://httpbin.org/get")
         .validate()
         .responseJSON { response in
             debugPrint(response)
         }

链式响应处理程序

响应处理程序甚至可以链式操作。

Alamofire.request(.GET, "https://httpbin.org/get")
         .validate()
         .responseString { response in
             print("Response String: \(response.result.value)")
         }
         .responseJSON { response in
             print("Response JSON: \(response.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, "https://httpbin.org/post")

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

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

参数

带有URL编码参数的GET请求

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

带有URL编码参数的POST请求

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

Alamofire.request(.POST, "https://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:一个查询字符串,将为 GETHEADDELETE 请求设置或追加到现有 URL 查询,或设置为任何其他 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 表示形式,并将其设置为请求体。编码请求的 Content-Type HTTP 头字段设置为 application/json
  • PropertyList:使用 NSPropertyListSerialization 创建参数对象的 plist 表示形式,根据相关格式和写入选项值,并将其设置为请求体。编码请求的 Content-Type HTTP 头字段设置为 application/x-plist
  • Custom:使用关联的闭包值根据现有请求和参数构造新的请求。

手动对 NSURLRequest 进行参数编码

let URL = NSURL(string: "https://httpbin.org/get")!
var request = NSMutableURLRequest(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, "https://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==",
    "Accept": "application/json"
]

Alamofire.request(.GET, "https://httpbin.org/get", headers: headers)
         .responseJSON { response in
             debugPrint(response)
         }

默认 Alamofire Manager 为每个请求提供一组常用头信息。这包括

  • Accept-Encoding,默认值为 gzip;q=1.0, compress;q=0.5,根据 RFC7230
  • Accept-Language,默认值为系统上前六个首选语言,格式为 en;q=1.0,根据 RFC7231
  • User-Agent,包含有关当前应用的版本信息。例如:iOS Example/1.0 (com.alamofire.iOS-Example; build:1; iOS 9.3.0) Alamofire/3.4.2,根据 RFC7231

由于它们需要添加到每个请求中,因此应该通过创建自定义的 Manager 实例并修改 defaultHTTPHeaders 字典来完成这些头部的自定义,如 修改会话配置 部分所示。

缓存

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

上传

支持的上传类型

  • 文件
  • 数据
  • multipartFormData

上传文件

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

带有进度上传

Alamofire.upload(.POST, "https://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)")
             }
         }
         .validate()
         .responseJSON { response in
             debugPrint(response)
         }

上传 MultipartFormData

Alamofire.upload(
    .POST,
    "https://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 { response in
                debugPrint(response)
            }
        case .Failure(let encodingError):
            print(encodingError)
        }
    }
)

下载

支持的下载类型

  • 请求
  • 恢复数据

下载文件

Alamofire.download(.GET, "https://httpbin.org/stream/100") { temporaryURL, response in
    let fileManager = NSFileManager.defaultManager()
    let directoryURL = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
    let pathComponent = response.suggestedFilename

    return directoryURL.URLByAppendingPathComponent(pathComponent!)
}

使用默认下载目标

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

下载带有进度的文件

Alamofire.download(.GET, "https://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 { _, _, _, error in
             if let error = error {
                 print("Failed with error: \(error)")
             } else {
                 print("Downloaded file successfully")
             }
         }

访问失败下载的恢复数据

Alamofire.download(.GET, "https://httpbin.org/stream/100", destination: destination)
         .response { _, _, data, _ 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, "https://httpbin.org/stream/100", destination: destination)
download.response { _, _, _, _ in
    if let
        resumeData = download.resumeData,
        resumeDataString = NSString(data: resumeData, encoding: NSUTF8StringEncoding)
    {
        print("Resume Data: \(resumeDataString)")
    } else {
        print("Resume Data was empty")
    }
}

身份验证

身份验证在系统框架级别通过 NSURLCredentialNSURLAuthenticationChallenge 处理。

支持的身份验证方案

HTTP Basic 认证

Request 对象上的 authenticate 方法会自动在合适的时候为一个 NSURLAuthenticationChallenge 提供一个 NSURLCredential

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

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

根据服务器的实现,适当的还可以使用 Authorization 标头

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

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

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

Alamofire.request(.GET, "https://httpbin.org/basic-auth/user/password", headers: headers)
         .responseJSON { response in
             debugPrint(response)
         }

使用 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)
         .responseJSON { response in
             debugPrint(response)
         }

时间线

Alamofire 在 Request 的生命周期中收集计时,并在 Response 的属性上创建 Timeline 对象。

Alamofire.request(.GET, "https://httpbin.org/get", parameters: ["foo": "bar"])
         .validate()
         .responseJSON { response in
             print(response.timeline)
         }

上述报告以下 Timeline 信息

  • 延迟:0.428 秒
  • 请求持续时间:0.428 秒
  • 序列化持续时间:0.001 秒
  • 总持续时间:0.429 秒

可打印的

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

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

可调试打印的

let request = Alamofire.request(.GET, "https://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" \
    "https://httpbin.org/get?foo=bar"


高级使用

Alamofire 建立在 NSURLSession 和 Foundation URL 加载系统之上。为了最大限度地利用此框架,建议您熟悉底层网络堆栈的概念和功能。

推荐阅读

管理器

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

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

Alamofire.request(.GET, "https://httpbin.org/get")
let manager = Alamofire.Manager.sharedInstance
manager.request(NSURLRequest(URL: NSURL(string: "https://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 实例。请求始终通过拥有管理器的构造器方法创建,而不会直接初始化。

authenticatevalidateresponseData 之类的方返回调用者,以便利链接。

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

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

响应序列化

处理错误

在实现自定义响应序列化器或对象序列化方法之前,准备好处理可能发生的任何错误是很重要的。Alamofire建议通过使用你自己的 NSError 创建方法或一个简单地遵守 ErrorTypeenum 来处理这些错误。例如,以下是将在后面的示例中使用的 BackendError 类型。

public enum BackendError: ErrorType {
    case Network(error: NSError)
    case DataSerialization(reason: String)
    case JSONSerialization(error: NSError)
    case ObjectSerialization(reason: String)
    case XMLSerialization(error: NSError)
}

创建自定义响应序列化器

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

例如,以下是使用 Ono 实现的响应处理程序:

extension Request {
    public static func XMLResponseSerializer() -> ResponseSerializer<ONOXMLDocument, BackendError> {
        return ResponseSerializer { request, response, data, error in
            guard error == nil else { return .Failure(.Network(error: error!)) }

            guard let validData = data else {
                return .Failure(.DataSerialization(reason: "Data could not be serialized. Input data was nil."))
            }

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

    public func responseXMLDocument(completionHandler: Response<ONOXMLDocument, BackendError> -> 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: Response<T, BackendError> -> Void) -> Self {
        let responseSerializer = ResponseSerializer<T, BackendError> { request, response, data, error in
            guard error == nil else { return .Failure(.Network(error: error!)) }

            let JSONResponseSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
            let result = JSONResponseSerializer.serializeResponse(request, response, data, error)

            switch result {
            case .Success(let value):
                if let
                    response = response,
                    responseObject = T(response: response, representation: value)
                {
                    return .Success(responseObject)
                } else {
                    return .Failure(.ObjectSerialization(reason: "JSON could not be serialized into response object: \(value)"))
                }
            case .Failure(let error):
                return .Failure(.JSONSerialization(error: 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, "https://example.com/users/mattt")
         .responseObject { (response: Response<User, BackendError>) in
             debugPrint(response)
         }

同样的方法也可以用来处理返回对象集合表示的端点

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

extension ResponseCollectionSerializable where Self: ResponseObjectSerializable {
    static func collection(response response: NSHTTPURLResponse, representation: AnyObject) -> [Self] {
        var collection = [Self]()

        if let representation = representation as? [[String: AnyObject]] {
            for itemRepresentation in representation {
                if let item = Self(response: response, representation: itemRepresentation) {
                    collection.append(item)
                }
            }
        }

        return collection
    }
}

extension Alamofire.Request {
    public func responseCollection<T: ResponseCollectionSerializable>(completionHandler: Response<[T], BackendError> -> Void) -> Self {
        let responseSerializer = ResponseSerializer<[T], BackendError> { request, response, data, error in
            guard error == nil else { return .Failure(.Network(error: error!)) }

            let JSONSerializer = Request.JSONResponseSerializer(options: .AllowFragments)
            let result = JSONSerializer.serializeResponse(request, response, data, error)

            switch result {
            case .Success(let value):
                if let response = response {
                    return .Success(T.collection(response: response, representation: value))
                } else {
                    return .Failure(. ObjectSerialization(reason: "Response collection could not be serialized due to nil response"))
                }
            case .Failure(let error):
                return .Failure(.JSONSerialization(error: 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
    }
}
Alamofire.request(.GET, "http://example.com/users")
         .responseCollection { (response: Response<[User], BackendError>) in
             debugPrint(response)
         }

URLStringConvertible

采用URLStringConvertible协议的类型可用于构建URL字符串,这些字符串随后用于构建URL请求。NSStringNSURLNSURLComponentsNSURLRequest默认实现了URLStringConvertible协议,允许其中任何一个作为URLString参数传递给requestuploaddownload方法。

let string = NSString(string: "https://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: "https://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)

鼓励与Web应用程序有显著交互的应用程序,对其自定义类型实现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 > 0:
                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

SessionDelegate

默认情况下,Alamofire的Manager实例会创建一个内部SessionDelegate对象来处理由底层的NSURLSession生成的各种类型的代理回调。每个代理方法实现都处理这些类型调用最常用的用例,将复杂性从顶层API中抽象出来。然而,高级用户可能会因各种原因需要覆盖默认功能。

覆盖闭包

自定义SessionDelegate行为的第一种方式是通过使用覆盖闭包。每个闭包都能让你覆盖匹配的SessionDelegate API的实现,同时仍可使用其他所有API的默认实现。这使得定制代理功能的一部分变得很容易。以下是几个可用覆盖闭包的例子。

/// Overrides default behavior for NSURLSessionDelegate method `URLSession:didReceiveChallenge:completionHandler:`.
public var sessionDidReceiveChallenge: ((NSURLSession, NSURLAuthenticationChallenge) -> (NSURLSessionAuthChallengeDisposition, NSURLCredential?))?

/// Overrides default behavior for NSURLSessionDelegate method `URLSessionDidFinishEventsForBackgroundURLSession:`.
public var sessionDidFinishEventsForBackgroundURLSession: ((NSURLSession) -> Void)?

/// Overrides default behavior for NSURLSessionTaskDelegate method `URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:`.
public var taskWillPerformHTTPRedirection: ((NSURLSession, NSURLSessionTask, NSHTTPURLResponse, NSURLRequest) -> NSURLRequest?)?

/// Overrides default behavior for NSURLSessionDataDelegate method `URLSession:dataTask:willCacheResponse:completionHandler:`.
public var dataTaskWillCacheResponse: ((NSURLSession, NSURLSessionDataTask, NSCachedURLResponse) -> NSCachedURLResponse?)?

以下是如何使用taskWillPerformHTTPRedirection来避免跟随重定向到任何apple.com域的简单示例。

let delegate: Alamofire.Manager.SessionDelegate = manager.delegate

delegate.taskWillPerformHTTPRedirection = { session, task, response, request in
    var finalRequest = request

    if let originalRequest = task.originalRequest where originalRequest.URLString.containsString("apple.com") {
        finalRequest = originalRequest
    }

    return finalRequest
}

子类化

覆盖SessionDelegate默认实现的另一种方法是子类化。子类化允许你完全自定义API的行为,或者为API创建一个代理,并仍然使用默认实现。创建代理允许你记录事件、发布通知、提供预处理和后处理钩子实现等。以下是一个子类化SessionDelegate并在重定向发生时记录消息的快速示例。

class LoggingSessionDelegate: Manager.SessionDelegate {
    override func URLSession(
        session: NSURLSession,
        task: NSURLSessionTask,
        willPerformHTTPRedirection response: NSHTTPURLResponse,
        newRequest request: NSURLRequest,
        completionHandler: NSURLRequest? -> Void)
    {
        print("URLSession will perform HTTP redirection to request: \(request)")

        super.URLSession(
            session,
            task: task,
            willPerformHTTPRedirection: response,
            newRequest: request,
            completionHandler: completionHandler
        )
    }
}

通常,默认实现或覆盖闭包应该提供所需的必要功能。只有在极端情况下才应该使用子类化。

需要注意的是,默认实现中初始化和销毁的是subdelegates。在子类化时要小心,以避免引入内存泄漏。

安全

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

ServerTrustPolicy

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

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

有许多不同的服务器信任评估情况,让您完全控制验证过程。

  • PerformDefaultEvaluation:使用默认的服务器信任评估,同时允许您控制是否验证挑战提供的域名。
  • PinCertificates:使用固定证书来验证服务器信任。如果固定证书之一与服务器证书之一匹配,则服务器信任被视为有效。
  • PinPublicKeys:使用固定公钥来验证服务器信任。如果固定公钥之一与服务器证书的公钥之一匹配,则服务器信任被视为有效。
  • DisableEvaluation:禁用所有评估,相应地总是将任何服务器信任视为有效。
  • CustomEvaluation:使用关联的闭包来评估服务器信任的有效性,从而为您提供对验证过程的完全控制。请谨慎使用。

Server Trust Policy Manager

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(
    serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)

请务必保留新Manager实例的引用,否则在《manager》被释放时,您的所有请求都将被取消。

这些服务器信任策略会产生以下行为

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

  • insecure.expired-apis.com永远不会评估证书链,并将始终允许TLS握手成功。
  • 所有其他主机都将使用Apple提供的默认评估。
Subclassing Server Trust Policy Manager

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

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

        // Implement your custom domain matching behavior...

        return policy
    }
}

Validating the Host

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

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

验证证书链

通过使用validateCertificateChain参数,同时固定证书和公钥都可以选择验证证书链。将该值设置为true,除了执行固定证书或公钥的字节比较之外,还会评估完整的证书链。将值设置为false将跳过证书链验证,但仍将执行字节比较。

有一些情况可能需要在生产环境中禁用证书链验证。禁用验证最常见的情况是自签名证书和过期证书。在这两种情况下,评估总是会失败,但字节比较仍然可以确保您从服务器接收到的证书是您期望的。

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

应用传输安全

在iOS 9中添加应用传输安全(ATS)后,使用具有多个ServerTrustPolicy对象的自定义ServerTrustPolicyManager可能没有任何效果。如果您不断遇到CFNetwork SSLHandshake failed (-9806)错误,您可能遇到了这个问题。除非您在您的应用程序的plist中配置ATS设置以禁用足够的功能,否则Apple的ATS系统将覆盖整个挑战系统。

如果您遇到这个问题(使用自签名证书时概率较高),可以通过在您的Info.plist中添加以下内容来解决这个问题。

<dict>
    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSExceptionDomains</key>
        <dict>
            <key>example.com</key>
            <dict>
                <key>NSExceptionAllowsInsecureHTTPLoads</key>
                <true/>
                <key>NSExceptionRequiresForwardSecrecy</key>
                <false/>
                <key>NSIncludesSubdomains</key>
                <true/>
                <!-- Optional: Specify minimum TLS version -->
                <key>NSTemporaryExceptionMinimumTLSVersion</key>
                <string>TLSv1.2</string>
            </dict>
        </dict>
    </dict>
</dict>

您是否需要将NSExceptionRequiresForwardSecrecy设置为NO取决于您的TLS连接是否使用允许的密码套件。在某些情况下,您可能需要将其设置为NO。要允许SessionDelegate接收挑战回调,必须将NSExceptionAllowsInsecureHTTPLoads设置为YES。一旦挑战回调被调用,ServerTrustPolicyManager将接管服务器信任评估。如果您尝试连接到仅支持TLS版本小于1.2的主机,您可能还需要指定NSTemporaryExceptionMinimumTLSVersion

建议在生产环境中始终使用有效的证书。

网络可达性

NetworkReachabilityManager将对WAN和WiFi网络接口的宿主和地址的可达性变化进行监听。

let manager = NetworkReachabilityManager(host: "www.apple.com")

manager?.listener = { status in
    print("Network Status Changed: \(status)")
}

manager?.startListening()

在上述示例中,请务必记住保留manager,否则不会报告状态变化。

使用网络可达性确定下一步要做什么时,有一些重要的事情需要记住。

  • 不要使用可达性来决定是否发送网络请求。
    • 您应该始终发送它。

  • 当可达性恢复时,请使用事件重试失败的网络请求。
    • 即使网络请求可能仍然会失败,这也是重试它们的好时机。

  • 网络可达性状态对于确定网络请求可能失败的原因非常有用。
    • 如果网络请求失败,告诉用户网络请求失败是因为离线而不是更复杂的技术错误(如“请求超时”),这更有用。

建议查阅WWDC 2012会议706,“网络最佳实践”获取更多信息。


开放的rdars

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

  • rdar://21349340 - 编译器因测试用例中的免费用桥接问题抛出警告
  • rdar://26761490 - Swift字符串插值在常用情况下导致内存泄漏

常见问题解答(FAQ)

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

Alamofire的名字来源于Alamo Fire花,这是德克萨斯州官方州花蓝bonnet的杂交品种。


鸣谢

Alamofire由Alamofire软件基金会拥有和维护。您可以在Twitter上关注他们@AlamofireSF以获取项目更新和发布信息。

安全披露

如果您认为您已经发现了与Alamofire相关的安全漏洞,应尽快通过电子邮件[email protected]报告。请不要将其发布到公共问题跟踪器。

捐赠

ASF正在寻求筹集资金以正式注册为联邦非营利组织。注册将允许我们成员获得一些法律保护,并允许我们将捐款用于免税。向ASF捐赠将使我们能够

  • 支付我们注册为联邦非营利组织的法律费用
  • 支付我们每年维持非营利组织良好状态的法律费用
  • 支付我们的邮件服务器费用,帮助我们处理所有问题和安全问题
  • 可能资助测试服务器,使我们可以更容易地测试边缘情况
  • 可能资助开发者全职工作在我们项目之一

ASF库的社区采用情况令人惊叹。我们对您对这些项目的热情感到非常谦卑,并想尽一切可能推动项目的发展。有了您的持续支持,ASF将能够提高其影响力,并为核心成员提供更好的法律保护。如果您在工作中使用了我们的任何库,请看看您的雇主是否有兴趣捐赠。我们的初步目标是筹集1000美元,以确保所有法律手续齐全并启动这项运动。今天您能捐赠的任何金额都将对我们实现目标有很大帮助。

Click here to lend your support to: Alamofire Software Foundation and make a donation at pledgie.com !

许可

Alamofire遵循MIT许可。详情请见LICENSE。