姬長信(Redy)

Alamofire-初探

>对于iOS开发者来说/uff0c`AFNetworking`是我们大家所熟知的/uff0c而`Alamofire`呢/uff1f`Alamofire`框架其实就是`AFNetworking`兄弟/uff0c出自于同一个作者。既是同一个作者/uff0c那么他们的使用方法/uff0c框架结构上应该也是保持一致的。[AFNetworking](https://github.com/AFNetworking/AFNetworking)、[Alamofire](https://github.com/Alamofire/Alamofire) #### 一、网络请求步骤 1. 设置请求`url` 2. 设置`URLRequest`对象/uff0c配置请求相关信息 3. 创建会话配置`URLSessionConfiguration` 4. 创建会话`URLSession` 5. 创建任务和设置请求回调/uff0c并发起请求 >一般通过以上几个步来完成网络请求/uff0c当然要根据不同应用场景来配置请求属性。 #### 二、体验 发起一个请求/uff1a ``` func responseData() { let url = "http://onapp.yahibo.top/public/?s=api/test/list" Alamofire.request(url).responseJSON { (response) in switch response.result{ case .success(let json): print("json:/(json)") let dict = json as! Dictionary let list = dict["data"] as! Array guard let result = [UserModel1].deserialize(from: list) else{return} self.observable.onNext(result as [Any]) break case .failure(let error): print("error:/(error)") break } } } ``` * `responseJSON`为相应类型/uff0c指定为`json`数据类型 * `response`是`Alamofire`对服务器响应结果的封装 * 使用常规解包获取数据`headimg` * 通过`HandyJSON`转换为模型数据/uff0c供`UI`展示 直接通过`Alamofire`发起请求通过一个闭包返回请求结果/uff0c不需要二次封装使用简单。这里我们没有标明请求类型/uff0c没有请求参数/uff0c那`Alamofire`是如何封装这些请求参数的呢/uff0c点击进入查看方法定义/uff1a ``` public func request( _ url: URLConvertible, method: HTTPMethod = .get, parameters: Parameters? = nil, encoding: ParameterEncoding = URLEncoding.default, headers: HTTPHeaders? = nil) -> DataRequest { return SessionManager.default.request( url, method: method, parameters: parameters, encoding: encoding, headers: headers ) } ``` 这里已经提供了请求所需要的参数/uff0c并设置了默认值/uff0c因此外界在没有指定方法时默认为`get`方式。根据实际开发需求设置响应的参数/uff1a ``` Alamofire.request(url,method: .post,parameters: ["page":"1","size":"20"]).responseJSON ``` 这是我们开发中常见的设置请求方式/uff0c请求参数/uff0c这里的`url`支持多种数据类型/uff0c可以是`String、URL、URLRequest`等类型/uff0c为什么这么设计呢/uff1f因为在项目中可能当前我们跟前有一个`String`类型的连接/uff0c也有可能是个`URL`类型的连接/uff0c这时候在不需要转换的情况下就可以直接使用/uff0c方便快捷/uff0c更加灵活。 效果如下/uff1a ![](https://cocosbcx.oss-cn-beijing.aliyuncs.com/article/201909111649406525) #### 三、URLSession 同样在`Alamofire`中也是对`URLSession`封装的/uff0c在`OC`中为`NSURLSession`/uff0c其实是一样的。一般网络请求分三个步骤/uff1a ##### 1、设置URL请求地址 ``` let url = URL.init(string: "协议://主机地址/路径/参数1&参数2")! ``` * 协议/uff1a指定`http`协议还是`https`协议 * 主机/uff1a即服务器地址`ip`地址或绑定`ip`的域名 * 路径/uff1a项目在服务器上的位置 * 参数/uff1a即`get`参数拼接在连接上 ##### 2、设置URLRequest属性 ``` 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 ``` * `httpMethod`设置请求方式`post`或`get` * `setValue`设置请求头信息 * `httpBody`设置请求参数/uff0c参数打包在请求体中 * `timeoutInterval`设置请求超时时间 * `cachePolicy`设置网络请求缓存策略 >通过以上的参数设置/uff0c能够感受到发送一次请求是多么不容易/uff0c因此网络请求是必须要被封装的 ##### 3、发起请求 ``` URLSession.shared.dataTask(with: request) { (data, response, error) in print("*******网络请求*******") do { let list = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) print(list) }catch{ print(error) } }.resume() ``` * `URLSession.shared`为全局共享单例会话对象 * 调用`dataTask`创建网络请求任务 * `resume`默认为挂起状态/uff0c调用重新启动网络请求任务 * `data`/uff1a请求到的数据流/uff0c通过`JSONSerialization`序列化为`json`格式使用 #### 四、URLSessionConfiguration 在`URLSession.shared`中内部已经配置了该项/uff0c此项为会话配置项/uff0c一般使用都会进行配置以适用于不同场景。查看该类如下/uff1a ``` open class var `default`: URLSessionConfiguration { get } open class var ephemeral: URLSessionConfiguration { get } @available(iOS 8.0, *) open class func background(withIdentifier identifier: String) -> URLSessionConfiguration ``` * `default`/uff1a默认模式/uff0c常用模式/uff0c在该模式下系统会创建持久化缓存/uff0c并在用户的钥匙串中保存证书 * `ephemeral`/uff1a不支持持久性存储/uff0c所有内容的会随着`session`的生命周期结束而释放 * `background`/uff1a与`default`模式类似/uff0c在该模式下会创建一个独立线程来传输网络请求数据/uff0c可以在后台乃至APP关闭的时候也可以进行数据传输 创建一个会话/uff1a ``` let configuration = URLSessionConfiguration.background(withIdentifier: "request_id") let session = URLSession.init(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main) session.dataTask(with: request) { (data, response, error) in print("*******网络请求*******") do { let list = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) print(list) }catch{ print(error) } }.resume() ``` * 设置一个唯一会话标识/uff0c通过标识来区分不同的会话任务 * 遵循`URLSessionDelegate`代理/uff0c实现代理方法/uff0c在本类中监控任务进度 下载任务/uff1a ``` let configuration = URLSessionConfiguration.background(withIdentifier: "request_id") let session = URLSession.init(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main) session.downloadTask(with: url).resume() ``` **注意在使用`background`模式时一定要开启后台下载权限/uff0c否则无法完成后台下载并回调数据。**需要以下两步才能完成/uff1a 1、开启后台下载权限 ``` var backgroundHandler: (()->Void)? = nil //设置此处开启后台下载权限 func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) { self.backgroundHandler = completionHandler } ``` 2、实现`SessionDelegate`代理方法/uff0c调用闭包方法/uff0c通知系统更新屏幕 ``` func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) { print("后台任务下载回来") DispatchQueue.main.async { guard let appDelegate = UIApplication.shared.delegate as? AppDelegate, let backgroundHandle = appDelegate.backgroundHandler else { return } backgroundHandle() } } ``` >苹果官方给出需要实现以上两个方法来完成后台下载/uff0c通知系统及时更新屏幕。[官方文档](https://developer.apple.com/documentation/foundation/nsurlsessiondelegate/1617185-urlsessiondidfinisheventsforback?language=objc) #### 五、相关属性 [官方文档](https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration?language=objc) **1、常规属性** * `identifier/uff1a`配置对象的后台会话标识符 * `httpAdditionalHeaders/uff1a`与请求一起发送的附加头文件字典 * `networkServiceType/uff1a`网络服务的类型 * `allowsCellularAccess/uff1a`一个布尔值/uff0c用于是否应通过蜂窝网络进行连接 * `timeoutIntervalForRequest/uff1a`等待附加数据的超时时间 * `timeoutIntervalForResource/uff1a`资源请求允许的最大时间范围 * `sharedContainerIdentifier/uff1a`应将后台URL会话中的文件下载到的共享容器的标识符 * `waitsForConnectivity/uff1a`一个布尔值/uff0c指示会话是否应等待连接变为可用还是立即失败 **2、设置Cookie策略** * `httpCookieAcceptPolicy/uff1a`决定何时接受cookie的策略常量 * `httpShouldSetCookies/uff1a`一个布尔值/uff0c确定请求是否包含来自`cookie`存储区的`cookie` * `httpCookieStorage`/uff1a用于会话中存储`cookie`的`cookie`存储区 * `HTTPCookie/uff1a`该对象为不可变对象/uff0c从包含`cookie`属性的字典初始化/uff0c支持两个不同的cookie版本/uff0c`v0、v1` **3、设置安全策略** * `TLS协议/uff1a`用于在两个通信应用程序之间提供保密性和数据完整性 * `tlsMaximumSupportedProtocol/uff1a`在此会话中建立连接时客户端应请求的最大`TLS协议`版本 * `tlsMinimumSupportedProtocol/uff1a`协议协商期间应接受的最小`TLS协议` * `urlCredentialStorage/uff1a`为身份验证提供凭据的凭据存储区 **4、设置缓存策略** * `urlCache/uff1a`用于为会话中的请求提供缓存响应的`URL`缓存 * `requestCachePolicy/uff1a`决定何时从缓存中返回响应的预定义常量 **5、支持后台转移** * `sessionSendsLaunchEvents/uff1a`一个布尔值/uff0c指示当传输完成时/uff0c应用程序应在后台恢复还是启动 * `isDiscretionary/uff1a`一个布尔值/uff0c用于确定后台任务是否可以由系统自行安排已获得最佳性能 * `shouldUseExtendedBackgroundIdleMode/uff1a`一个布尔值/uff0c指示当应用程序转移到后台时是否应保持`TCP`连接打开 **6、支持自定义协议** * `protocolClasses/uff1a`在会话中处理请求的额外协议子类的数组 * `URLProtocol/uff1a`该对象用来处理加载协议特定`URL`数据 **7、支持多路径TCP** * `multipathServiceType/uff1a`指定用于通过`Wi-Fi`和蜂窝接口传输数据的多路径`TCP`连接策略的服务类型 **8、设置HTTP策略和代理属性** * `httpMaximumConnectionsPerHost/uff1a`同时连接到给定主机的最大数量 * `httpShouldUsePipelining/uff1a`一个布尔值/uff0c用于确定会话是否使用`HTTP`流水线 * `connectionProxyDictionary/uff1a`包含相关要在此会话中使用的代理信息的字典 **9、支持连接更改** * `waitsForConnectivity/uff1a`一个布尔值/uff0c指示会话应等待连接可用还是立即失败 >一个请求任务就有如此多的配置属性/uff0c可想而知对一个网络框架的整合/uff0c工作量也是非常大的。接下来我们就一步步来了解一下Alamofire是如何一步步整合的。