Alamofire
应该是用Swift做iOS开发里最常用到的三方框架了。我们在开发过程中常会遇到的网络请求,如:向服务端请求json数据,上传图片,下载文件等,都可以直接调用Alamofire或者二次封装它再进行调用。这里假定你已经使用过Alamofire,那么本篇文章会对Alamofire的部分源码选择性的介绍。当然,这里并不会贴上源码里的大段代码,而是通过一个例子来进入到Alamofire中的各个模块,让读者更好理解。
我们先来看看一个Alamofire的调用例子:
let url = "http://suggest.taobao.com/sug" //淘宝的一个搜索api
let parameters: [String: Any] = [ //对`袜子`进行搜索
"code" : "utf-8",
"q" : "袜子"
]
`Alamofire`.request(url, method: .get, parameters: parameters, encoding: URLEncoding.default, headers: nil)
.validate(statusCode: [200])
.responseData(queue: DispatchQueue.global(), completionHandler: { (responseData) in
switch responseData.result {
case .success(let data):
guard let jsonString = String(data: data, encoding: .utf8) else { return }
print(jsonString)
case .failure(let error):
print(error)
}
})
// `Alamofire`两边的单引号是为了markdown语法的显示更清晰,请忽略掉。
这个方法是Alamofire
的基本用法。
我们通过Alamofire的request
开始调用,
1.传入url
2.使用get
方法
3.传入parameters
,并在encoding
中定义了参数的编码方式
4.因为是一个公共api,这里并不需要headers
,headers传nil
5.在validate
中传入需要验证的statusCode的数组
6.在responseData
方法里传入全局队列和回调的处理闭包completionHandler
7.在闭包里对返回的数据里进行判断,若返回成功,打印jsonString,若失败则打印错误。
我们从前往后一点点来看。
(1)request方法做了什么事情?
我们点击request进入Alamofire.swift从最上面看起(这里建议同步点开Alamofire里的源文件,便于对比)
-
URLConvertible
协议 的定义。我们在例子里传入的url是String类型,也可以将它转成URL类型再传入,结果都正确。
就是因为String和URL都遵循URLConvertible协议,通过实现协议里的
func asURL() throws -> URL
方法,从String,URL或URLComponents类型里转换为URL,若失败则抛出定位为AFError
的错误
往下看,是协议
URLRequestConvertible
的定义,它是负责URLRequest
的转换,和上面类似,就不多介绍了。再望下看,就是Alamofire对外公开的常用接口了。包括request,download,upload等方法的定义。而它们实际上都是调用了
SessionManager.default.request(...) -> Request //传入参数省略
SessionManager
是Alamofire中最外层用于发起网络请求的单例类,它会在init方法中定义实际的请求会话URLSession。而请求实际的调用者是URLSession。
结论是通过request
方法,传入所需参数,发起请求,并返回Request
的子类
如:DataRequest
,DownloadRequest
等,而DataRequest又继承于Request.
(2)返回给我们Request的子类有什么用呢?
我们进入 Request.swift 来看看Request类型的实现。
- 定义了
RequestTask
,为请求任务分类 - 持有了
task
(请求任务),session
(请求对话),request
(请求),response
(响应)等 - 定义了我们会常涉及到的任务管理方法,包含resume(激活),suspend(暂停),cancel(取消),这里我们来看一下cancel的实现
/// 取消请求.
open func cancel() {
guard let task = task else { return }
task.cancel() //这里的task是request持有的URLSessionTask,实际上是调用它的取消方法
NotificationCenter.default.post( //当取消任务时,发送DidCancel的通知
name: Notification.Name.Task.DidCancel,
object: self,
userInfo: [Notification.Key.Task: task]
)
}
如果一个页面里的请求未结束就关闭页面,我们想要结束这次请求时,
可以在Alamofire请求的末尾调用cancel()
方法来取消这次请求,它实际上是调用真正的网络请求URLSessionTask的cancel()方法。而Request
的其他实现以及它的子类DataRequest
,DownloadRequest
这里也不详细介绍了。这里也可以顺便看看 Notification.swift ,了解一下Alamofire封装了哪些通知
结论是:返回给我们的Request,可以让我们控制请求的暂停,恢复,取消等。
(3)我们在例子中向encoding传入URLEncoding.default
有什么用?
我们进入 ParameterEncoding.swift 看看它的实现
一来就看到了枚举类型HTTPMethod
的实现,在这个文件下定义.get
,.post
,.put
方法等类型,证明ParameterEncoding(即参数编码)和具体的请求方式是有联系的。
我们看到ParameterEncoding
是一个协议,并且只有一个方法
func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest
//此方法是为了完成对传入的parameters的编码工作
我们常会用到的URLEncoding
,JSONEncoding
,以及不常用到的PropertyListEncoding
都遵循这个协议。
这里截取URLEncoding中对该方法实现中会调用的一个方法
private func query(_ parameters: [String: Any]) -> String {
var components: [(String, String)] = []
for key in parameters.keys.sorted(by: <) {
let value = parameters[key]!
components += queryComponents(fromKey: key, value: value)
}
return components.map { "\($0)=\($1)" }.joined(separator: "&")
}
//本方法以及它的调用处可以看到URLEncoding方式是将parameters通过添加 & ,= 的方式拼接到url身后
而如果JSONEncoding方式是将parameters转化为二进制放入httpBody里。
因此也很好理解,为什么get方法的安全性不如post方法,因为get将请求参数暴露在外,而post在内部。
因此,我们将调用例子里的url变为
let url = "http://suggest.taobao.com/sug?code=utf-8&q=%E8%A2%9C%E5%AD%90"
//%E8%A2%9C%E5%AD%90由对`袜子`urlencode转化而来。
并且向parameter传nil,依然会得到同样的返回。因为现在这个url就是之前请求时,Alamofire转化之后的url。当然转化的过程不仅仅止于此,这里只说了其中的代表部分
结论是encoding: 是对参数进行编码,如果传入URLEncoding,会将参数拼接进url;如果传入JSONEncoding,则将参数转为二进制放入httpbody(Propertylist是以plist方式编码,我也很少用,就不介绍了)
(4)例子中传入的headers
做了些什么?
public typealias HTTPHeaders = [String: String]
由HTTPHeaders的别名我们得知它的类型是[String: String]。
我们在Alamofire.swift里顺藤摸瓜找到了我们传的headers实际上是怎么处理的
extension URLRequest {
public init(url: URLConvertible, method: HTTPMethod, headers: HTTPHeaders? = nil) throws {
let url = try url.asURL()
self.init(url: url)
httpMethod = method.rawValue
if let headers = headers {
for (headerField, headerValue) in headers {
setValue(headerValue, forHTTPHeaderField: headerField)
}
}
}
}
在URLRequest的扩展里,添加了它的init方法。在init方法里,遍历了headers里的key和value,
并调用setValue: forHTTPHeaderField:
方法将这些key,value放入URLRequest的请求头里
结论是:传入的headers
是会将其中的key,value赋给URLRequest的实例,也就是我们常说的请求头的设置
(5)例子中validate
做了些什么?
我们打开 Validation.swift 发现,validate是对Request及其子类的扩展中增加的方法,传入一个Request并进行认证,再返回本身的类型,我们这里先看对statusCode的认证
public func validate<S: Sequence>(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int {}
那么到底是如何认证的呢?这里我们再进入它的内部实现,看到:
fileprivate func validate<S: Sequence>(
statusCode acceptableStatusCodes: S,
response: HTTPURLResponse)
-> ValidationResult
where S.Iterator.Element == Int
{
if acceptableStatusCodes.contains(response.statusCode) { //若validate传入code包含response响应的code,则认证成功
return .success
} else { //若不包括,则返回失败,抛出AFError
let reason: ErrorReason = .unacceptableStatusCode(code: response.statusCode)
return .failure(AFError.responseValidationFailed(reason: reason))
}
}
//acceptableStatusCodes为我们实际在validate中传入的statusCode数组,在例子中为[200].
通过acceptableStatusCodes.contains(response.statusCode)
判断请求返回的code是否是我们想要的。
在这里我们可以做个测试,将例子中的[200]变为[201],就会打印出抛出的错误。因为http的正确返回码为200,我们传入的数组不包含200,就会抛出错误。当然我们也可以在例子中不调用validate,这样就不会有这些验证。
这里只介绍了statusCode的认证,实际上validate里还有对contentType等的认证。
结论是:validate
会对http响应码,或者响应内容类型等进行认证,若认证成功不影响请求,若认证失败,会抛出错误。
(6)queue: DispatchQueue.global()
做了些什么?
我们在一步步点进去在 ResponseSerialization.swift 的reponse方法中发现
(queue ?? DispatchQueue.main).async { completionHandler(dataResponse) }
//若queue为空则使用主队列处理返回数据
这行代码是传入queue的作用。
结论是:我们会把请求返回的处理放入queue:
所传入的队列中,若我们没有向queue
传入队列,那么会默认把处理放入主队列中。例子中我选择将处理放入全局队列中。
这里可以顺便看一下 DispatchQueue+Alamofire.swift 文件,里面是对DispatchQueue增加属性,获取不同优先级
的队列,并增加了一个名为after
的延迟调用方法。属性和方法都没有添加public关键字,那么它们只想在Alamofire内部调用,而不想暴露给我们。
(7)请求中的错误是如何分类的?
Alamofire里的错误都已经被定义到AFError
中,我们打开 AFError.swift ,可以看到
enum AFError { //这里为了看得方便,将AFError重新整理了下,源码中的内容要更多,但类型是一样的
case invalidURL //无效的URL
case parameterEncodingFailed //请求参数编码失败
case multipartEncodingFailed //多部分编码失败
case responseValidationFailed //响应验证失败
case responseSerializationFailed //响应序列化失败
}
而AFError的类型中,除了invalidURL
的参数是遵循协议URLConvertible
,其余类型的参数又由枚举类型组成。
这样就将很多种的错误类型,包括在了这5种类型中。
往下看,还能看到很多内容,例如对localizedDescription
的实现,在请求抛出错误时,我们如果打印error.localizedDescription,看到的错误信息就是在这里定义的。
(8)还有哪些文件需要了解?
-
MultipartFormData.swift
中会看到我们在使用上传功能时的编码方式 -
NetworkReachabilityManager.swift
是Alamofire基于SCNetworkReachability
封装的,用于监听当前手机的网络状态的工具,我们可以直接用
NetworkReachabilityManager().networkReachabilityStatus
来获取当前手机的网络状态
-ResponseSerialization.swift
里定义了对请求返回的请求方式
以上就是我们根据一个简单的网络请求,而定位到Alamofire的内部实现的过程。因为内容很多,并且为了博客的可读性,这里只挑了一些基础且常用的进行介绍。我们如果想深入了解Alamofire,并且学习它的设计思路,还是需要到源码中一点点分析。
觉得有帮助的话,点个赞吧😊~