Alamofire-Request
一、简述
在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
,一般url
为String
类型的资源链接,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: "&")
}
- 传入一个字典参数
- 对字典
key
的ASCII码
进行排序,重新组合成一个元组数组 queryComponents
内部进行递归操作,将key
和value
进行百分号编码,并放回元组中- 通过
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
}
- 递归操作,遍历到数据的每一层,直到键值对的值为非字典和数组类型,开始对
key
和value
做百分号编码操作 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)}
}
这里只做了三步处理:
在这个方法中并没有发起请求,而是将发起任务的
session
和request
,交给了DataRequest
,对任务分层,manager
说我这只管维持你们需要的参数,具体任务自己带回家去做,分工明确,思路更清晰。绑定绑定返回
task
和request
,以便SessionDelegate
做任务下发,在SessionDelegate
中通过task获取taskDelegate
对象有
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相关推荐
- Alamofire源码解读系列(十二)之请求(Request)
本篇是Alamofire中的请求抽象层的讲解 前言 在Alamofire中,围绕着Request,设计了很多额外的特性,这也恰恰表明,Request是所有请求的基础部分和发起点.这无疑给我们一个Req ...
- Swift - 使用Alamofire通过HTTPS进行网络请求,及证书的使用
(本文代码已升级至Swift3) 我原来写过一篇文章介绍如何使用证书通过SSL/TLS方式进行网络请求(Swift - 使用URLSession通过HTTPS进行网络请求,及证书的使用),当时用的是 ...
- swift集成alamofire的简单封装
import UIKit import Alamofire enum MethodType{ case GET case POST } class NetworkTool: NSObject { cl ...
- Swift 3.0封装 URLSession 的GET/SET方法代替 Alamofire
升级到 Swift3.0 之后,新版本的 Alamofire 只支持 iOS 9.0 以上的系统,如果要适配 iOS 8,需要自己封装 URLSession,下面是笔者的方案: 这里使用的是 Swif ...
- Swift: 用Alamofire做http请求,用ObjectMapper解析JSON
示例代码看最后. 跟不上时代的人突然间走在了时代的前列,果然有别样的风景.首先鄙视一下AFNetworking.这个东西实在太难用了.不想封装都不行,要不写一大堆代码. NSURL *URL = [N ...
- Alamofire源码解读系列(九)之响应封装(Response)
本篇主要带来Alamofire中Response的解读 前言 在每篇文章的前言部分,我都会把我认为的本篇最重要的内容提前讲一下.我更想同大家分享这些顶级框架在设计和编码层次究竟有哪些过人的地方?当然, ...
- Alamofire 的使用
最近,AFNetworking 的作者Mattt Thompson提交了一个新的类似于 AFNetworking 的网络 基础库,并且是专门使用最新的 Swift 语言来编写的,名为:Alamofir ...
- 用Macbook开发桌面应用,使用Alamofire链接.Net Core Webapi的注意事项!
------------https方式----------------------- 因为Swift9之后访问接口只能使用https,所以在后台加入pfx文件(怎么生成,自行百度吧) 1.将pfx放在 ...
- [Alamofire] 错误总结
fun f1(){} fun f2(){} func fetch() { Alamofire.request(.POST, url, parameters: params).responseJs ...
- 用Alamofire进行网络请求的一段代码解析(一)
//AnyObject:json //Mark:利用alamofire 发送网络请求,并返回数据json:AnyObject,需要强制转换为NSDictionary字典类型. /* 向服务器发送请求: ...
最新文章
- 青茶什么时候拆_篮球:挡拆是艺术,绝知要躬行,最简单也是最复杂的篮球战术...
- 最新:2020年度陈嘉庚科学奖出炉!施一公获生命科学奖
- Spring 源码分析, ApplicationContext build 包找不到编译异常
- Unable to open debugger port (127.0.0.1:4184): java.net.SocketException socket closed
- 【BZOJ3387】[Usaco2004 Dec]Fence Obstacle Course栅栏行动 线段树
- 计算机视觉论文-2021-06-14
- Bailian4016 班级排名【稳定排序】
- grafana设置mysql为数据源,并进行可视化
- 超分辨率分析(四)--Deep Image Prior
- 开发管理 -启动项目(转)
- 敢不敢做一个复杂的人
- AIDE手机编程初级教程(零基础向) 1.1 认识我的第一个应用
- 【hadoop权威指南第四版】第四章hadoop的IO【笔记+代码】
- Unity+罗技G29方向盘+Realistic Car Controller 制作简单的模拟驾驶
- 少儿编程scratch(源码)
- SM6S系列TVS二级管 可通过ISO 7637-2 5a/5b测试
- JDK8下载安装与Win10下Java环境变量配置
- Unity如何开发微信小游戏
- ArangoDB查询语言(AQL) 基本语法用法
- 170312-python爬虫 steam愿望单打折商品