一、简述

Alamofire中为了方便管理,明确分工,Alamofire对整个请求过程做了明确划分,并统一交由SessionManager来管理。SessionManager负责SessionDelegate、URLSession、URLRequest等对象创建与管理。先看一段请求示例:

let urlStr = "http://onapp.yahibo.top/public/?s=api/test/list"
let url = URL.init(string: urlStr)!
Alamofire.request(url,method: .post,parameters: ["page":"1","size":"20"]).responseJSON {(response) inswitch response.result{case .success(let json):print("json:\(json)")breakcase .failure(let error):print("error:\(error)")break}
}

这里的request相当于Alamofire的一个请求api,内部实现了所有需要的请求配置,此外也封装了download、upload、stream等请求api供用户直接使用。一般请求流程为url -> request -> task -> response,一般urlString类型的资源链接,request才是我们请求的重中之重。在Alamofire中对请求流程都做了任务细分,下面以request作为主线向下探索,看看框架是如何细分的。

二、URLRequest常规配置

下面看一下常规配置:

let url = URL.init(string: "1⃣协议://2⃣主机地址/3⃣路径/4⃣参数1&参数2")!
var request  = URLRequest.init(url: url)
request.httpMethod = HTTPMethod.post.rawValue
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let postData = ["username":"hibo","password":"123456"]
request.httpBody = try?JSONSerialization.data(withJSONObject: postData, options: [])
request.timeoutInterval = 30
request.cachePolicy = .useProtocolCachePolicy

需要一系列参数配置,可能根据不同需要要做更多的配置,项目中几乎每个页面都需要网络请求,对于以上的代码量,肯定是要做封装处理,提取通用代码复用,开放配置参数,供特殊需求使用。

三、对URLRequest的封装

open func request(_ url: URLConvertible,method: HTTPMethod = .get,parameters: Parameters? = nil,encoding: ParameterEncoding = URLEncoding.default,headers: HTTPHeaders? = nil)-> DataRequest
{var originalRequest: URLRequest?do {originalRequest = try URLRequest(url: url, method: method, headers: headers)let encodedURLRequest = try encoding.encode(originalRequest!, with: parameters)return request(encodedURLRequest)} catch {return request(originalRequest, failedWith: error)}
}
  • url:请求资源连接
  • method:HTTPMethod枚举类型参数,设置请求类型默认get类型
  • Parameters:请求参数,字典类型,默认为nil
  • encoding:支持编码格式,有三种类型:URLEncoding、JSONEncoding、PropertyListEncoding默认URLEncoding
  • headers:请求头参数设置默认为空

以上为对外引出常规参数,用户根据需求设置,内部有默认值,用户不设置则启用默认值(通用属性值)。以上几个参数即是URLRequest的属性,请求中的主要属性。

四、参数处理

在开发中,我们常常使用GET请求或POST请求。GET请求,请求参数是拼接在地址上的&或/符合分隔,常用于获取数据;POST请求将参数封装在请求体中,常用数据提交。

下面看一下框架是如何处理这些参数的:

public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {var urlRequest = try urlRequest.asURLRequest()guard let parameters = parameters else { return urlRequest }if let method = HTTPMethod(rawValue: urlRequest.httpMethod ?? "GET"), encodesParametersInURL(with: method) {guard let url = urlRequest.url else {throw AFError.parameterEncodingFailed(reason: .missingURL)}if var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false), !parameters.isEmpty {let percentEncodedQuery = (urlComponents.percentEncodedQuery.map { $0 + "&" } ?? "") + query(parameters)urlComponents.percentEncodedQuery = percentEncodedQueryurlRequest.url = urlComponents.url}} else {if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {urlRequest.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")}urlRequest.httpBody = query(parameters).data(using: .utf8, allowLossyConversion: false)}return urlRequest
}
  • 没有参数直接返回URLRequest对象
  • 有参数则需要对分别处理参数,GET需要拼接,POST需要添加添加至httpBody请求体中
  • percentEncodedQuery获取域名后的参数并与请求参数拼接

query函数对参数做了拼接处理。如下:

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: "&")
}
  • 传入一个字典参数
  • 对字典keyASCII码进行排序,重新组合成一个元组数组
  • queryComponents内部进行递归操作,将keyvalue进行百分号编码,并放回元组中
  • 通过map结合元组元素形成新的数组,在通过joined函数将数组元素拼接,两个元素直接用&符分隔

queryComponents

public func queryComponents(fromKey key: String, value: Any) -> [(String, String)] {var components: [(String, String)] = []if let dictionary = value as? [String: Any] {for (nestedKey, value) in dictionary {components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value)}} else if let array = value as? [Any] {for value in array {components += queryComponents(fromKey: arrayEncoding.encode(key: key), value: value)}} else if let value = value as? NSNumber {if value.isBool {components.append((escape(key), escape(boolEncoding.encode(value: value.boolValue))))} else {components.append((escape(key), escape("\(value)")))}} else if let bool = value as? Bool {components.append((escape(key), escape(boolEncoding.encode(value: bool))))} else {components.append((escape(key), escape("\(value)")))}return components
}
  • 递归操作,遍历到数据的每一层,直到键值对的值为非字典和数组类型,开始对keyvalue做百分号编码操作
  • escape为编码函数

GET请求

通过query来拼接并做百分号编码所有的键值对,处理成"username=hibo&password=123456"这种形式,直接拼接至请求域名上,request.url为域名,上面通过urlComponents.percentEncodedQuery已经拆分出来了资源路径因此,拼接的连接为:host+资源路径(s=api/test/list/)+&+参数

POST请求

在没有Content-Type配置的情况下给默认配置。如下:

urlRequest.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")

query拼接后的参数转换为Data类型数据,传给请求体httpBody。至此GET或其他请求的参数就已经设置完毕。URLRequest设置完成后,就可以创建任务发送请求了。下面看一下框架怎么处理。

五、发送请求

顺着代码找到另一个request方法:

open func request(_ urlRequest: URLRequestConvertible) -> DataRequest {var originalRequest: URLRequest?do {originalRequest = try urlRequest.asURLRequest()let originalTask = DataRequest.Requestable(urlRequest: originalRequest!)let task = try originalTask.task(session: session, adapter: adapter, queue: queue)let request = DataRequest(session: session, requestTask: .data(originalTask, task))delegate[task] = requestif startRequestsImmediately { request.resume() }return request} catch {return request(originalRequest, failedWith: error)}
}

这里只做了三步处理:

  1. 在这个方法中并没有发起请求,而是将发起任务的sessionrequest,交给了DataRequest,对任务分层,manager说我这只管维持你们需要的参数,具体任务自己带回家去做,分工明确,思路更清晰。

  2. 绑定绑定返回taskrequest,以便SessionDelegate做任务下发,在SessionDelegate中通过task获取taskDelegate对象

  3. manager管理任务的启动request.resume()

DataRequest

init(session: URLSession, requestTask: RequestTask, error: Error? = nil) {self.session = sessionswitch requestTask {case .data(let originalTask, let task):taskDelegate = DataTaskDelegate(task: task)self.originalTask = originalTaskcase .download(let originalTask, let task):taskDelegate = DownloadTaskDelegate(task: task)self.originalTask = originalTaskcase .upload(let originalTask, let task):taskDelegate = UploadTaskDelegate(task: task)self.originalTask = originalTaskcase .stream(let originalTask, let task):taskDelegate = TaskDelegate(task: task)self.originalTask = originalTask}delegate.error = errordelegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent() }
}
  • 通过枚举来配置不同的任务代理

TaskDelegate

分多个子类,具有不同职能。子类如下:

DataTaskDelegate
DownloadTaskDelegate
UploadTaskDelegate

根据不同任务类型,由Request类确定不同的处理类,stream类的任务有基类处理。该类中都对外声明了相应代理事件的闭包,以便对外传递代理消息。如下:

var progressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)?
var uploadProgressHandler: (closure: Request.ProgressHandler, queue: DispatchQueue)?

该闭包都是在Request类中或者拓展方法中实现的,实际不是实现只是桥接到界面中实现,从而代理事件传递直接传到界面闭包。

总结

1、SessionManager

  • httpAdditionalHeaders参数配置
  • 创建URLRequest
  • 创建URLSession
  • URLSessionDelegate代理移交给SessionDelegate
  • 发起任务请求

2、SessionDelegate

  • 实现所有网络请求的代理方法,并对外声明闭包,通过闭包向外传递代理响应事件
  • 如果外部没有实现接收代理事件的闭包,会将代理事件通过task标识移交给TaskDelegate中具体的子类来处理

3、Request

  • 负责任务的创建,保留URLSession对象即URLRequest对象
  • 负责任务下发,下发至TaskDelegate
  • 实现数据传递方法,对外设置闭包参数,内部将闭包桥接至TaskDelegate中,即实现TaskDelegate中对外声明的闭包
  • 方法返回self实现链式调用

4、TaskDelegate

  • 创建子类,负责具体任务实现
  • 对外声明闭包向外传递代理消息
  • 改类对象在Request中使用

Alamofire-Request相关推荐

  1. Alamofire源码解读系列(十二)之请求(Request)

    本篇是Alamofire中的请求抽象层的讲解 前言 在Alamofire中,围绕着Request,设计了很多额外的特性,这也恰恰表明,Request是所有请求的基础部分和发起点.这无疑给我们一个Req ...

  2. Swift - 使用Alamofire通过HTTPS进行网络请求,及证书的使用

    (本文代码已升级至Swift3) 我原来写过一篇文章介绍如何使用证书通过SSL/TLS方式进行网络请求(Swift - 使用URLSession通过HTTPS进行网络请求,及证书的使用),当时用的是 ...

  3. swift集成alamofire的简单封装

    import UIKit import Alamofire enum MethodType{ case GET case POST } class NetworkTool: NSObject { cl ...

  4. Swift 3.0封装 URLSession 的GET/SET方法代替 Alamofire

    升级到 Swift3.0 之后,新版本的 Alamofire 只支持 iOS 9.0 以上的系统,如果要适配 iOS 8,需要自己封装 URLSession,下面是笔者的方案: 这里使用的是 Swif ...

  5. Swift: 用Alamofire做http请求,用ObjectMapper解析JSON

    示例代码看最后. 跟不上时代的人突然间走在了时代的前列,果然有别样的风景.首先鄙视一下AFNetworking.这个东西实在太难用了.不想封装都不行,要不写一大堆代码. NSURL *URL = [N ...

  6. Alamofire源码解读系列(九)之响应封装(Response)

    本篇主要带来Alamofire中Response的解读 前言 在每篇文章的前言部分,我都会把我认为的本篇最重要的内容提前讲一下.我更想同大家分享这些顶级框架在设计和编码层次究竟有哪些过人的地方?当然, ...

  7. Alamofire 的使用

    最近,AFNetworking 的作者Mattt Thompson提交了一个新的类似于 AFNetworking 的网络 基础库,并且是专门使用最新的 Swift 语言来编写的,名为:Alamofir ...

  8. 用Macbook开发桌面应用,使用Alamofire链接.Net Core Webapi的注意事项!

    ------------https方式----------------------- 因为Swift9之后访问接口只能使用https,所以在后台加入pfx文件(怎么生成,自行百度吧) 1.将pfx放在 ...

  9. [Alamofire] 错误总结

    fun f1(){} fun f2(){} func fetch() {    Alamofire.request(.POST, url, parameters: params).responseJs ...

  10. 用Alamofire进行网络请求的一段代码解析(一)

    //AnyObject:json //Mark:利用alamofire 发送网络请求,并返回数据json:AnyObject,需要强制转换为NSDictionary字典类型. /* 向服务器发送请求: ...

最新文章

  1. 青茶什么时候拆_篮球:挡拆是艺术,绝知要躬行,最简单也是最复杂的篮球战术...
  2. 最新:2020年度陈嘉庚科学奖出炉!施一公获生命科学奖
  3. Spring 源码分析, ApplicationContext build 包找不到编译异常
  4. Unable to open debugger port (127.0.0.1:4184): java.net.SocketException socket closed
  5. 【BZOJ3387】[Usaco2004 Dec]Fence Obstacle Course栅栏行动 线段树
  6. 计算机视觉论文-2021-06-14
  7. Bailian4016 班级排名【稳定排序】
  8. grafana设置mysql为数据源,并进行可视化
  9. 超分辨率分析(四)--Deep Image Prior
  10. 开发管理 -启动项目(转)
  11. 敢不敢做一个复杂的人
  12. AIDE手机编程初级教程(零基础向) 1.1 认识我的第一个应用
  13. 【hadoop权威指南第四版】第四章hadoop的IO【笔记+代码】
  14. Unity+罗技G29方向盘+Realistic Car Controller 制作简单的模拟驾驶
  15. 少儿编程scratch(源码)
  16. SM6S系列TVS二级管 可通过ISO 7637-2 5a/5b测试
  17. JDK8下载安装与Win10下Java环境变量配置
  18. Unity如何开发微信小游戏
  19. ArangoDB查询语言(AQL) 基本语法用法
  20. 170312-python爬虫 steam愿望单打折商品

热门文章

  1. Oracle 多表联合查询优化
  2. 前台离岗提示语_前台规范服务用语
  3. 鲁大师智慧电动车排行:小牛“高光”不再,产品力下降是主因?
  4. C 语言 二级指针的使用
  5. 华为显示打开定位服务器地址,如何打开华为的定位服务器地址
  6. 源码编译安装LNMP
  7. 北风网白帽子信息安全培训
  8. 计算机入门模拟券b,计算机入门模拟卷B(有详细答案).doc
  9. 动态规划——openjudge7624山区建小学
  10. CAD教程:CAD建筑户型图纸还能这么画?