https://www.cnblogs.com/jukaiit/p/9283507.html
Alamofire是在URLSession
和URL加载系统的基础上写的。所以,为了更好地学习这个框架,建议先熟悉下列几个底层网络协议栈:
- URL Loading System Programming Guide >>
- URLSession Class Reference >>
- URLCache Class Reference >>
- URLAuthenticationChallenge Class Reference >>
Session Manager
高级别的方便的方法,例如Alamofire.request
,使用的是默认的Alamofire.SessionManager
,并且这个SessionManager是用默认URLSessionConfiguration
配置的。
例如,下面两个语句是等价的:
我们可以自己创建后台会话和短暂会话的session manager,还可以自定义默认的会话配置来创建新的session manager,例如修改默认的header httpAdditionalHeaders
和timeoutIntervalForRequest
。
用默认的会话配置创建一个Session Manager
用后台会话配置创建一个Session Manager
用默短暂会话配置创建一个Session Manager
修改会话配置
注意:不推荐在Authorization
或者Content-Type
header使用。而应该使用Alamofire.request
API、URLRequestConvertible
和ParameterEncoding
的headers参数。
会话代理
默认情况下,一个SessionManager
实例创建一个SessionDelegate
对象来处理底层URLSession
生成的不同类型的代理回调。每个代理方法的实现处理常见的情况。然后,高级用户可能由于各种原因需要重写默认功能。
重写闭包
第一种自定义SessionDelegate
的方法是通过重写闭包。我们可以在每个闭包重写SessionDelegate
API对应的实现。下面是重写闭包的示例:
下面的示例演示了如何使用taskWillPerformHTTPRedirection
来避免回调到任何apple.com
域名。
子类化
另一个重写SessionDelegate
的实现的方法是把它子类化。通过子类化,我们可以完全自定义他的行为,或者为这个API创建一个代理并且仍然使用它的默认实现。通过创建代理,我们可以跟踪日志事件、发通知、提供前后实现。下面这个例子演示了如何子类化SessionDelegate
,并且有回调的时候打印信息:
总的来说,无论是默认实现还是重写闭包,都应该提供必要的功能。子类化应该作为最后的选择。
请求
request
、download
、upload
和stream
方法的结果是DataRequest
、DownloadRequest
、UploadRequest
和StreamRequest
,并且所有请求都继承自Request
。所有的Request
并不是直接创建的,而是由session manager创建的。
每个子类都有特定的方法,例如authenticate
、validate
、responseJSON
和uploadProgress
,都返回一个实例,以便方法链接(也就是用点语法连续调用方法)。
请求可以被暂停、恢复和取消:
suspend()
:暂停底层的任务和调度队列resume()
:恢复底层的任务和调度队列。如果manager的startRequestsImmediately
不是true
,那么必须调用resume()
来开始请求。cancel()
:取消底层的任务,并产生一个error,error被传入任何已经注册的响应handlers。
传送请求
随着应用的不多增大,当我们建立网络栈的时候要使用通用的模式。在通用模式的设计中,一个很重要的部分就是如何传送请求。遵循Router
设计模式的URLConvertible
和URLRequestConvertible
协议可以帮助我们。
URLConvertible
遵循了URLConvertible
协议的类型可以被用来构建URL,然后用来创建URL请求。String
、URL
和URLComponent
默认是遵循URLConvertible
协议的。它们都可以作为url
参数传入request
、upload
和download
方法:
以一种有意义的方式和web应用程序交互的应用,都鼓励使用自定义的遵循URLConvertible
协议的类型将特定领域模型映射到服务器资源,因为这样比较方便。
类型安全传送
URLRequestConvertible
遵循URLRequestConvertible
协议的类型可以被用来构建URL请求。URLRequest
默认遵循了URLRequestConvertible
,允许被直接传入request
、upload
和download
(推荐用这种方法为单个请求自定义请求头)。
以一种有意义的方式和web应用程序交互的应用,都鼓励使用自定义的遵循URLRequestConvertible
协议的类型来保证请求端点的一致性。这种方法可以用来抽象服务器端的不一致性,并提供类型安全传送,以及管理身份验证凭据和其他状态。
API参数抽象
CRUD和授权
适配和重试请求
现在的大多数Web服务,都需要身份认证。现在比较常见的是OAuth。通常是需要一个access token来授权应用或者用户,然后才可以使用各种支持的Web服务。创建这些access token是比较麻烦的,当access token过期之后就比较麻烦了,我们需要重新创建一个新的。有许多线程安全问题要考虑。
RequestAdapter
和RequestRetrier
协议可以让我们更容易地为特定的Web服务创建一个线程安全的认证系统。
RequestAdapter
RequestAdapter
协议允许每一个SessionManager
的Request
在创建之前被检查和适配。一个非常特别的使用适配器方法是,在一个特定的认证类型,把Authorization
header拼接到请求。
RequestRetrier
RequestRetrier
协议允许一个在执行过程中遇到error的请求被重试。当一起使用RequestAdapter
和RequestRetrier
协议时,我们可以为OAuth1、OAuth2、Basic Auth(每次请求API都要提供用户名和密码)甚至是exponential backoff重试策略创建资格恢复系统。下面的例子演示了如何实现一个OAuth2 access token的恢复流程。
免责声明:这不是一个全面的OAuth2解决方案。这仅仅是演示如何把RequestAdapter
和RequestRetrier
协议结合起来创建一个线程安全的恢复系统。
重申: 不要把这个例子复制到实际的开发应用中,这仅仅是一个例子。每个认证系统必须为每个特定的平台和认证类型重新定制。
一旦OAuth2Handler
为SessionManager
被应用与adapter
和retrier
,他将会通过自动恢复access token来处理一个非法的access token error,并且根据失败的顺序来重试所有失败的请求。(如果需要让他们按照创建的时间顺序来执行,可以使用他们的task identifier来排序)
上面这个例子仅仅检查了401
响应码,不是演示如何检查一个非法的access token error。在实际开发应用中,我们想要检查realm
和www-authenticate
header响应,虽然这取决于OAuth2的实现。
还有一个要重点注意的是,这个认证系统可以在多个session manager之间共享。例如,可以在同一个Web服务集合使用default
和ephemeral
会话配置。上面这个例子可以在多个session manager间共享一个oauthHandler
实例,来管理一个恢复流程。
自定义响应序列化
Alamofire为data、strings、JSON和Property List提供了内置的响应序列化:
这些响应包装了反序列化的值(Data, String, Any)或者error (network, validation errors),以及元数据 (URL Request, HTTP headers, status code, metrics, ...)。
我们可以有多个方法来自定义所有响应元素:
- 响应映射
- 处理错误
- 创建一个自定义的响应序列化器
- 泛型响应对象序列化
响应映射
响应映射是自定义响应最简单的方式。它转换响应的值,同时保留最终错误和元数据。例如,我们可以把一个json响应DataResponse<Any>
转换为一个保存应用模型的的响应,例如DataResponse<User>
。使用DataResponse.map
来进行响应映射:
当转换可能会抛出错误时,使用flatMap
方法:
响应映射非常适合自定义completion handler:
上面代码中loadUser
方法被@discardableResult
标记,意思是调用loadUser
方法可以不接收它的返回值;也可以用_
来忽略返回值。
当 map/flatMap 闭包会产生比较大的数据量时,要保证这个闭包在子线程中执行:
map
和flatMap
也可以用于下载响应。
处理错误
在实现自定义响应序列化器或者对象序列化方法前,思考如何处理所有可能出现的错误是非常重要的。有两个方法:1)传递未修改的错误,在响应时间处理;2)把所有的错误封装在一个Error
类型中。
例如,下面是等会要用用到的后端错误:
创建一个自定义的响应序列化器
Alamofire为strings、JSON和Property List提供了内置的响应序列化,但是我们可以通过扩展Alamofire.DataRequest
或者Alamofire.DownloadRequest
来添加其他序列化。
例如,下面这个例子是一个使用Ono (一个实用的处理iOS和macOS平台的XML和HTML的方式)的响应handler的实现:
泛型响应对象序列化
泛型可以用来提供自动的、类型安全的响应对象序列化。
同样地方法可以用来处理返回对象集合的接口:
安全
对于安全敏感的数据来说,在与服务器和web服务交互时使用安全的HTTPS连接是非常重要的一步。默认情况下,Alamofire会使用苹果安全框架内置的验证方法来评估服务器提供的证书链。虽然保证了证书链是有效的,但是不能防止man-in-the-middle (MITM)攻击或者其他潜在的漏洞。为了减少MITM攻击,处理用户的敏感数据或财务信息的应用,应该使用ServerTrustPolicy
提供的certificate或者public key pinning。
ServerTrustPolicy
在通过HTTPS安全连接连接到服务器时,ServerTrustPolicy
枚举通常会评估URLAuthenticationChallenge
提供的server trust。
在验证的过程中,有多种方法可以让我们完全控制server trust的评估:
performDefaultEvaluation
:使用默认的server trust评估,允许我们控制是否验证challenge提供的host。pinCertificates
:使用pinned certificates来验证server trust。如果pinned certificates匹配其中一个服务器证书,那么认为server trust是有效的。pinPublicKeys
:使用pinned public keys来验证server trust。如果pinned public keys匹配其中一个服务器证书公钥,那么认为server trust是有效的。disableEvaluation
:禁用所有评估,总是认为server trust是有效的。customEvaluation
:使用相关的闭包来评估server trust的有效性,我们可以完全控制整个验证过程。但是要谨慎使用。
服务器信任策略管理者 (Server Trust Policy Manager)
ServerTrustPolicyManager
负责存储一个内部的服务器信任策略到特定主机的映射。这样Alamofire就可以评估每个主机不同服务器信任策略。
注意:要确保有一个强引用引用着SessionManager
实例,否则当sessionManager
被销毁时,请求将会取消。
这些服务器信任策略将会形成下面的结果:
test.example.com
:始终使用证书链固定的证书和启用主机验证,因此需要以下条件才能是TLS握手成功:- 证书链必须是有效的。
- 证书链必须包含一个已经固定的证书。
- Challenge主机必须匹配主机证书链的子证书。
insecure.expired-apis.com
:将从不评估证书链,并且总是允许TLS握手成功。- 其他主机将会默认使用苹果提供的验证。
子类化服务器信任策略管理者
如果我们需要一个更灵活的服务器信任策略来匹配其他行为(例如通配符域名),可以子类化ServerTrustPolicyManager
,并且重写serverTrustPolicyForHost
方法。
验证主机
.performDefaultEvaluation
、.pinCertificates
和.pinPublicKeys
这三个服务器信任策略都带有一个validateHost
参数。把这个值设为true
,服务器信任评估就会验证与challenge主机名字匹配的在证书里面的主机名字。如果他们不匹配,验证失败。如果设置为false
,仍然会评估整个证书链,但是不会验证子证书的主机名字。
注意:建议在实际开发中,把validateHost
设置为true
。
验证证书链
Pinning certificate 和 public keys 都可以通过validateCertificateChain
参数拥有验证证书链的选项。把它设置为true
,除了对Pinning certificate 和 public keys进行字节相等检查外,还将会验证整个证书链。如果是false
,将会跳过证书链验证,但还会进行字节相等检查。
还有很多情况会导致禁用证书链认证。最常用的方式就是自签名和过期的证书。在这些情况下,验证始终会失败。但是字节相等检查会保证我们从服务器接收到证书。
注意:建议在实际开发中,把validateCertificateChain
设置为true
。
应用传输安全 (App Transport Security)
从iOS9开始,就添加了App Transport Security (ATS),使用ServerTrustPolicyManager
和多个ServerTrustPolicy
对象可能没什么影响。如果我们不断看到CFNetwork SSLHandshake failed (-9806)
错误,我们可能遇到了这个问题。苹果的ATS系统重写了整个challenge系统,除非我们在plist文件中配置ATS设置来允许应用评估服务器信任。
是否需要把NSExceptionRequiresForwardSecrecy
设置为NO
取决于TLS连接是否使用一个允许的密码套件。在某些情况下,它需要设置为NO
。NSExceptionAllowsInsecureHTTPLoads
必须设置为YES
,然后SessionDelegate
才能接收到challenge回调。一旦challenge回调被调用,ServerTrustPolicyManager
将接管服务器信任评估。如果我们要连接到一个仅支持小于1.2
版本的TSL主机,那么还要指定NSTemporaryExceptionMinimumTLSVersion
。
注意:在实际开发中,建议始终使用有效的证书。
网络可达性 (Network Reachability)
NetworkReachabilityManager
监听WWAN
和WiFi
网络接口和主机地址的可达性变化。
注意:要确保manager被强引用,否则会接收不到状态变化。另外,在主机字符串中不要包含scheme,也就是说要把https://
去掉,否则无法监听。
当使用网络可达性来决定接下来要做什么时,有以下几点需要重点注意的:
- 不要使用Reachability来决定是否发送一个网络请求。
- 我们必须要发送请求。
- 当Reachability恢复了,要重试网络请求。
- 即使网络请求失败,在这个时候也非常适合重试请求。
- 网络可达性的状态非常适合用来决定为什么网络请求会失败。
- 如果一个请求失败,应该告诉用户是离线导致请求失败的,而不是技术错误,例如请求超时。
有兴趣的可以看看WWDC 2012 Session 706, "Networking Best Practices"。
FAQ
Alamofire的起源是什么?
Alamofire是根据 Alamo Fire flower 命名的,是一种矢车菊的混合变种,德克萨斯的州花。
Router和Request Adapter的逻辑是什么?
简单和静态的数据,例如paths、parameters和共同的headers放在Router
。动态的数据,例如一个Authorization
header,它的值会随着一个认证系统变化,放在RequestAdapter
。
动态的数据必须放在ReqeustAdapter
的原因是要支持重试操作。当重试一个请求时,原来的请求不会重新建立,也就意味着Router
不会再重新调用。RequestAdapter
可以重新调用,这可以让我们在重试请求之前更新原始请求的动态数据。
完
作者:Lebron_James
链接:https://www.jianshu.com/p/903b678d2d3f
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。