简单易懂的Alamofire使用及源码分析

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(statusCode acceptableStatusCodes: S) -> Self where S.Iterator.Element == Int {}

那么到底是如何认证的呢?这里我们再进入它的内部实现,看到:

fileprivate func validate(
    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,并且学习它的设计思路,还是需要到源码中一点点分析。

觉得有帮助的话,点个赞吧~

你可能感兴趣的:(简单易懂的Alamofire使用及源码分析)