高级用法
Alamofire建立在URLSession
Foundation URL加载系统之上。要充分利用此框架,建议您熟悉底层网络堆栈的概念和功能。
推荐阅读
会话管理器
顶级便捷方法,例如Alamofire.request
使用默认实例Alamofire.SessionManager
配置的默认实例URLSessionConfiguration
。
因此,以下两个陈述是等效的:
1 | Alamofire.request("https://httpbin.org/get") |
应用程序可以为后台和短暂会话创建会话管理器,以及自定义默认会话配置的新管理器,例如默认标头(httpAdditionalHeaders
)或超时间隔(timeoutIntervalForRequest
)。
使用默认配置创建会话管理器
1 | let configuration = URLSessionConfiguration.default |
使用后台配置创建会话管理器
1 | let configuration = URLSessionConfiguration.background(withIdentifier: "com.example.app.background") |
使用临时配置创建会话管理器
1 | let configuration = URLSessionConfiguration.ephemeral |
修改会话配置
1 | var defaultHeaders = Alamofire.SessionManager.defaultHTTPHeaders |
这是不推荐
Authorization
或Content-Type
头。相反,使用headers
参数在顶层Alamofire.request
的API,URLRequestConvertible
并ParameterEncoding
分别。
会话代表
默认情况下,Alamofire SessionManager
实例创建一个SessionDelegate
对象来处理由底层生成的所有各种类型的委托回调URLSession
。每个委托方法的实现处理这些类型的调用的最常见用例,从顶层API中抽象出复杂性。但是,高级用户可能会因各种原因发现需要覆盖默认功能。
覆盖闭包
自定义SessionDelegate
行为的第一种方法是使用覆盖闭包。每个闭包使您能够覆盖匹配SessionDelegate
API 的实现,但仍然使用所有其他API的默认实现。这样可以轻松自定义委托功能的子集。以下是一些可用的覆盖闭包的示例:
1 | /// Overrides default behavior for URLSessionDelegate method `urlSession(_:didReceive:completionHandler:)`. |
以下是如何使用taskWillPerformHTTPRedirection
以避免重定向到任何apple.com
域的简短示例。
1 | let sessionManager = Alamofire.SessionManager(configuration: URLSessionConfiguration.default) |
子类
覆盖默认实现的另一种方法SessionDelegate
是将其子类化。子类化允许您完全自定义API的行为或为API创建代理,并仍使用默认实现。创建代理允许您记录事件,发出通知,提供前后钩实现等。这是一个SessionDelegate
在重定向发生时子类化和记录消息的快速示例。
1 | class LoggingSessionDelegate: SessionDelegate { |
一般来说,默认实现或覆盖闭包应提供所需的必要功能。子类只应作为最后的手段使用。
请记住,
subdelegates
在默认实现中初始化和销毁它是很重要的。子类化时要小心,不要引入内存泄漏。
请求
的结果request
,download
,upload
或stream
方法是a DataRequest
,DownloadRequest
,UploadRequest
和StreamRequest
其中所有的继承Request
。所有Request
实例始终由拥有的会话管理器创建,并且从不直接初始化。
每个子类有专门的方法,比如authenticate
,validate
,responseJSON
和uploadProgress
每个以利于方法链接返回调用者的实例。
请求可以暂停,恢复和取消:
suspend()
:暂停基础任务和调度队列。resume()
:恢复基础任务和调度队列。如果拥有管理器未startRequestsImmediately
设置为true
,则请求必须调用resume()
才能启动。cancel()
:取消基础任务,产生传递给任何已注册响应处理程序的错误。
路由请求
随着应用程序规模的扩大,在构建网络堆栈时采用通用模式非常重要。该设计的一个重要部分是如何路由您的请求。Alamofire URLConvertible
和URLRequestConvertible
协议以及Router
设计模式可以提供帮助。
URLConvertible
采用该URLConvertible
协议的类型可用于构造URL,然后用于在内部构造URL请求。String
,URL
以及URLComponents
符合URLConvertible
在默认情况下,允许任何人将其交给url
参数的request
,upload
和download
方法:
1 | let urlString = "https://httpbin.org/post" |
鼓励以显着方式与Web应用程序交互的应用程序使自定义类型符合将URLConvertible
特定于域的模型映射到服务器资源的便捷方式。
类型安全路由
1 | extension User: URLConvertible { |
URLRequestConvertible
采用该URLRequestConvertible
协议的类型可用于构造URL请求。默认情况下URLRequest
符合URLRequestConvertible
,允许它直接传递给request
,upload
和download
方法(这是为各个请求指定自定义HTTP正文的推荐方法):
1 | let url = URL(string: "https://httpbin.org/post")! |
鼓励以显着方式与Web应用程序交互的应用程序使自定义类型符合URLRequestConvertible
确保所请求端点的一致性的方式。这种方法可用于抽象出服务器端的不一致性,并提供类型安全的路由,以及管理身份验证凭据和其他状态。
API参数抽象
1 | enum Router: URLRequestConvertible { |
CRUD和授权
1 | import Alamofire |
适应和重试请求
如今,大多数Web服务都支持某种身份验证系统。今天比较常见的一个是OAuth。这通常涉及生成访问令牌,授权您的应用程序或用户调用各种支持的Web服务。虽然创建这些初始访问令牌可能很麻烦,但是当访问令牌过期并且您需要获取新令牌时,它可能会更加复杂。需要考虑许多线程安全问题。
在RequestAdapter
和RequestRetrier
协议的建立是为了使它更容易为一组特定的Web服务创建一个线程安全的身份验证系统。
RequestAdapter
该RequestAdapter
协议允许在创建之前Request
对每个上面的内容SessionManager
进行检查和调整。使用适配器的一种非常具体的方法是向Authorization
特定类型的身份验证后面的请求附加标头。
1 | class AccessTokenAdapter: RequestAdapter { |
RequestRetrier
该RequestRetrier
协议允许重试执行Request
的Error
一段时间。同时使用RequestAdapter
和RequestRetrier
协议时,您可以为OAuth1,OAuth2,Basic Auth甚至指数退避重试策略创建凭据刷新系统。可能性是无止境。以下是如何为OAuth2访问令牌实现刷新流程的示例。
免责声明:这不是一个全球性的
OAuth2
解决方案。它只是一个示例,演示了如何RequestAdapter
结合使用它RequestRetrier
来创建一个线程安全的刷新系统。
重申一下,请勿复制此示例代码并将其放入生产应用程序中。这只是一个例子。每个身份验证系统必须针对特定平台和身份验证类型进行定制。
1 | class OAuth2Handler: RequestAdapter, RequestRetrier { |
一旦OAuth2Handler
被同时作为应用adapter
和retrier
为SessionManager
,它会自动刷新访问令牌,并在重试失败,他们以相同的顺序所有失败的请求的处理无效访问令牌错误。
如果您需要按照创建它们的顺序执行它们,则可以按任务标识符对它们进行排序。
上面的示例仅检查401
响应代码,该代码不够健壮,但确实演示了如何检查无效的访问令牌错误。在生产应用程序中,尽管取决于OAuth2实现,但仍希望检查realm
并且最有可能是www-authenticate
标头响应。
另一个重要的注意事项是该认证系统可以在多个会话管理器之间共享。例如,您可能需要对同一组Web服务使用a default
和ephemeral
会话配置。上面的示例允许oauthHandler
在多个会话管理器之间共享同一实例,以管理单个刷新流。
自定义响应序列化
Alamofire为数据,字符串,JSON和属性列表提供内置的响应序列化:
1 | Alamofire.request(...).responseData { (resp: DataResponse<Data>) in ... } |
这些响应包括反序列化的值(数据,字符串,任何)或错误(网络,验证错误),以及元数据(URL请求,HTTP标头,状态代码,度量标准 ……)。
您可以通过多种方式自定义所有响应元素:
响应映射
响应映射是生成自定义响应的最简单方法。它可以转换响应的值,同时保留最终的错误和元数据。例如,您可以将json响应DataResponse<Any>
转换为包含应用程序模型的响应,例如DataResponse<User>
。您使用以下DataResponse.map
方法执行响应映射:
1 | Alamofire.request("https://example.com/users/mattt").responseJSON { (response: DataResponse<Any>) in |
当转换可能引发错误时,请flatMap
改为使用:
1 | Alamofire.request("https://example.com/users/mattt").responseJSON { response in |
响应映射非常适合您的自定义完成处理程序:
1 | @discardableResult |
当map / flatMap闭包可以处理大量数据时,请确保在主线程之外执行它:
1 | @discardableResult |
map
并且flatMap
还可以下载回复。
处理错误
在实现自定义响应序列化程序或对象序列化方法之前,考虑如何处理可能发生的任何错误非常重要。有两个基本选项:沿未修改的方式传递现有错误,在响应时处理; 或者,将所有错误包装在Error
特定于您的应用的类型中。
例如,这是一个简单的BackendError
枚举,将在后面的例子中使用:
1 | enum BackendError: Error { |
创建自定义响应序列化程序
Alamofire为字符串,JSON和属性列表提供内置的响应序列化,但是其他可以在扩展中添加Alamofire.DataRequest
和/或Alamofire.DownloadRequest
。
例如,以下是如何实现使用Ono的响应处理程序:
1 | extension DataRequest { |
通用响应对象序列化
泛型可用于提供自动,类型安全的响应对象序列化。
1 | protocol ResponseObjectSerializable { |
同样的方法也可用于处理返回对象集合表示的端点:
1 | protocol ResponseCollectionSerializable { |
安全
在与服务器和Web服务通信时使用安全HTTPS连接是保护敏感数据的重要步骤。默认情况下,Alamofire将使用安全框架提供的Apple内置验证来评估服务器提供的证书链。虽然这可以保证证书链的有效性,但它不会阻止中间人(MITM)攻击或其他潜在漏洞。为了减轻MITM攻击,处理敏感客户数据或财务信息的应用程序应使用由提供的证书或公钥固定ServerTrustPolicy
。
ServerTrustPolicy
该ServerTrustPolicy
枚举评估一般由提供的服务器信任URLAuthenticationChallenge
连接到服务器通过安全的HTTPS连接时。
1 | let serverTrustPolicy = ServerTrustPolicy.pinCertificates( |
服务器信任评估有许多不同的情况,使您可以完全控制验证过程:
performDefaultEvaluation
:使用默认服务器信任评估,同时允许您控制是否验证质询提供的主机。pinCertificates
:使用固定证书来验证服务器信任。如果其中一个固定证书与其中一个服务器证书匹配,则认为服务器信任有效。pinPublicKeys
:使用固定的公钥来验证服务器信任。如果其中一个固定公钥与其中一个服务器证书公钥匹配,则认为服务器信任有效。disableEvaluation
:禁用所有评估,而评估又始终将任何服务器信任视为有效。customEvaluation
:使用关联的闭包来评估服务器信任的有效性,从而使您可以完全控制验证过程。谨慎使用。
服务器信任策略管理器
该ServerTrustPolicyManager
负责存储服务器的信任策略的内部映射到特定的主机。这允许Alamofire根据不同的服务器信任策略评估每个主机。
1 | let serverTrustPolicies: [String: ServerTrustPolicy] = [ |
确保保留对新
SessionManager
实例的引用,否则在取消sessionManager
分配时,您的请求都将被取消。
这些服务器信任策略将导致以下行为:
1
test.example.com
将始终使用证书链接和证书链并启用主机验证,因此需要满足以下条件才能使TLS握手成功:
- 证书链必须有效。
- 证书链必须包含一个固定证书。
- 挑战主机必须匹配证书链的叶证书中的主机。
insecure.expired-apis.com
永远不会评估证书链,并始终允许TLS握手成功。所有其他主机将使用Apple提供的默认评估。
子类化服务器信任策略管理器
如果您发现自己需要更灵活的服务器信任策略匹配行为(即通配域),则使用您自己的自定义实现继承ServerTrustPolicyManager
并覆盖该serverTrustPolicyForHost
方法。
1 | class CustomServerTrustPolicyManager: ServerTrustPolicyManager { |
验证主机
的.performDefaultEvaluation
,.pinCertificates
和.pinPublicKeys
服务器信任的政策都将一个validateHost
参数。将值设置为true
将导致服务器信任评估验证证书中的主机名是否与质询的主机名匹配。如果它们不匹配,评估将失败。一个validateHost
价值false
仍然会评估整个证书链,但不会验证叶证书的主机名。
建议
validateHost
始终true
在生产环境中进行设置。
验证证书链
固定证书和公钥都可以选择使用validateCertificateChain
参数验证证书链。通过将此值设置为true
,除了对固定证书或公钥执行字节相等性检查之外,还将评估完整证书链。值false
将跳过证书链验证,但仍将执行字节相等性检查。
在某些情况下,禁用证书链验证可能是有意义的。禁用验证的最常见用例是自签名和过期证书。在这两种情况下,评估总是会失败,但字节相等检查仍将确保您收到您希望从服务器获得的证书。
建议
validateCertificateChain
始终true
在生产环境中进行设置。
App Transport Security
通过在iOS 9中添加App Transport Security(ATS),使用ServerTrustPolicyManager
具有多个ServerTrustPolicy
对象的自定义可能无效。如果你不断看到CFNetwork SSLHandshake failed (-9806)
错误,你可能遇到了这个问题。除非您在应用程序的plist中配置ATS设置以禁用足够的ATS设置以允许您的应用评估服务器信任,否则Apple的ATS系统会覆盖整个质询系统。
如果您遇到此问题(使用自签名证书的概率很高),您可以通过将以下内容添加到您的问题来解决此问题Info.plist
。
1 | <dict> |
是否需要设置NSExceptionRequiresForwardSecrecy
为NO
取决于您的TLS连接是否使用允许的密码套件。在某些情况下,需要将其设置为NO
。在NSExceptionAllowsInsecureHTTPLoads
必须设置为YES
以允许SessionDelegate
接受挑战回调。一旦调用挑战回调,ServerTrustPolicyManager
将接管服务器信任评估。您可能还需要指定NSTemporaryExceptionMinimumTLSVersion
是否尝试连接到仅支持低于TLS版本的主机1.2
。
建议始终在生产环境中使用有效证书。
将自签名证书与本地网络配合使用
如果您尝试连接到localhost上运行的服务器,并且您使用的是自签名证书,则需要将以下内容添加到您的Info.plist
。
1 | <dict> |
根据苹果的文档,设置NSAllowsLocalNetworking
以YES
允许本地资源负载,而无需禁用ATS为您的应用程序的其余部分。
网络可达性
该NetworkReachabilityManager
主机和地址都WWAN和WiFi网络接口的可达性变化监听。
1 | let manager = NetworkReachabilityManager(host: "www.apple.com") |
请务必记住保留
manager
上面的示例,否则不会报告任何状态更改。此外,不要在host
字符串中包含该方案,否则可达性将无法正常运行。
使用网络可达性来确定下一步该做什么时,需要记住一些重要的事项。
请勿
使用Reachability确定是否应发送网络请求。
- 你应该总是发送它。
恢复可达性后,使用该事件重试失败的网络请求。
- 即使网络请求可能仍然失败,这是重试它们的好时机。
网络可达性状态可用于确定网络请求可能失败的原因。
- 如果网络请求失败,告诉用户网络请求由于脱机而不是更技术性错误(例如“请求超时”)更有用。
建议查看WWDC 2012 Session 706,“Networking Best Practices”以获取更多信息。