精华内容
下载资源
问答
  • Alamofire

    2021-04-28 13:24:22
    Alamofire Alamofire这个Swift网络框架还是挺好用的,拿来就可以直接使用。 下面是基于 Alamofire 4.8.1。 导入头文件 import Alamofire import SwiftyJSON 声明一个SessionManager对象 var manager: Alamofire...

    Alamofire

    先会用,然后再封装和拓展,之后再看源码,可能才算懂点。

    Alamofire这个Swift网络框架还是挺好用的,拿来就可以直接使用。
    下面是基于 Alamofire 4.8.1。

    1. 导入头文件
    import Alamofire
    import SwiftyJSON
    
    1. 声明一个SessionManager对象
    var manager: Alamofire.SessionManager?
    
    1. 初始化SessionManager对象
    let sessionConfig = URLSessionConfiguration.default
    sessionConfig.timeoutIntervalForRequest = 30
    self.manager = Alamofire.SessionManager(configuration: sessionConfig)
    
    1. 发起HTTP请求
    manager?.request(loginURL!, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: headers).validate().responseJSON { [weak self] response in
        guard let strongSelf = self else {
            return
        }
        switch response.result {
        case .success(let value):
            let json = JSON(value)
            print(json)
            break
        case .failure(let error):
            strongSelf.requestFailed(error as NSError)
            break
        }
    }
    

    配合SwiftyJSON来使用,如上发起一起get请求,如果还有其他参数,传一个parameters,框架里面会将需要拼的参数帮我们添加到url中。拿来直接就可以用还是挺方便的。

    参考:
    封装Alamofire: https://www.bilibili.com/read/cv7520682
    HWNetworking : https://github.com/HouWan/HWNetworking

    Alamofire已经封装的很好了,一般的拿来直接用。
    如果后台一般需要验签啊什么的,拿来稍微封装一下就够用了。
    感觉项目不是特别需求的话,不需要过度封装,简单的封装保留requestTask和request两个类抽象出来就能满足需求了。

    展开全文
  • The build target for Alamofire will be listed as Alamofire iOS, Alamofire macOS, Alamofire tvOS, or Alamofire watchOS. And that's it! The Alamofire.framework is automagically added as a target ...
  • 第18课AlamofireAlamofire图片 ВсепередалатьчерезAlamofireиразобраться
  • Alamofire Support

    2020-12-26 03:24:22
    <p>This PR makes it possible to use DVR in projects that use <a href="https://github.com/Alamofire/Alamofire/">Alamofire</a> to do their networking. The Alamofire team got us most of the way towards ...
  • Alamofire 4.0.0

    2021-01-07 00:38:53
    <div><p>Hey, got a question how to install AlamofireObjectMapper with new version of Alamofire 4.0.0. <p>After pod install Terminal print: - <code>Alamofire (~> 4.0)</code> required by <code>...
  • Alamofire 5

    2021-01-07 00:37:01
    <div><p>Hello. Thank you for ...In this pull request I replaced Alamofire 5.0.0-rc3 with Alamofire 5.</p><p>该提问来源于开源项目:tristanhimmelman/AlamofireObjectMapper</p></div>
  • 源码解析alamofire

    2017-11-21 11:14:34
    源码解析alamofire源码解析alamofire源码解析alamofire源码解析alamofire源码解析alamofire源码解析alamofire源码解析alamofire源码解析alamofire源码解析alamofire源码解析alamofire源码解析alamofire源码解析...
  • 测试Alamofire-源码

    2021-02-17 20:13:34
    测试Alamofire
  • <p>I see there is an Alamofire Request extension example in the README, but if I'm correct it is not for the newest, 3.x version of Alamofire. Can you update it to show how can OCMapper be used ...
  • Missing Alamofire

    2020-12-01 21:26:50
    <div><p>When I clone this project, it seems it is missing Alamofire. I could add this in but I'm wondering what approach you'd want to take for this? I'd personally prefer either Carthage ...
  • Unnecessary Alamofire

    2020-12-26 22:04:19
    Is there any reason in particular for using Alamofire and SwiftJSON rather than the native URLSession provided by Foundation? Over the past few years, the API for URLSession has become much nicer to ...
  • Alamofire初探

    千次阅读 2019-09-01 11:39:32
    Alamofire初探一. Alamofire概述二. URLSesstion基础三、TCP的三次握手四、TCP数据的传输过程五、TCP的四次挥手 一. Alamofire概述 对于使用Objective-C的开发者,一定非常熟悉AFNetworking这个网络框架。在苹果推出...

    一. Alamofire概述

    对于使用Objective-C的开发者,一定非常熟悉AFNetworking这个网络框架。在苹果推出的Swift之后,AFNetworking的作者专门用Swift来编写一个类似AFNetworking的网络框架,称为Alamofire。Alamofire地址
    因为Alamofire是对苹果URLSesstion的封装,所以先来了解下URLSesstion的基础

    二. URLSesstion基础

    URLSession.shared.dataTask(with: url) { (data, response, error) in
                if error == nil {
                    print("请求成功\(String(describing: response))" )
                }
            }.resume()
    

    此过程省略了一个重要的东西:URLSessionConfiguration

    open class var `default`: URLSessionConfiguration { get }
    open class var ephemeral: URLSessionConfiguration { get }
        @available(iOS 8.0, *)
        open class func background(withIdentifier identifier: String) -> URLSessionConfiguration
    

    URLSessionConfiguration有三种模式

    • default:默认模式,通常使用这种模式就够了,default模式下系统会创建一个持久化的缓存并在用户的钥匙串中存储证书
    • ephemeral:系统没有任何持久性存储,所有内容的生命周期与session相同,当session无效时,所有内容自动释放
            let configuration1 = URLSessionConfiguration.default
            let configuration2 = URLSessionConfiguration.ephemeral
            print("沙盒大小: \(String(describing: configuration1.urlCache?.diskCapacity))")
            print("内存大小: \(String(describing: configuration1.urlCache?.memoryCapacity))")
            print("沙盒大小: \(String(describing: configuration2.urlCache?.diskCapacity))")
            print("内存大小: \(String(describing: configuration2.urlCache?.memoryCapacity))")
    
    • background创建一个可以在后台甚至app已经关闭的时候仍然在传输数据的会话。background模式可以在程序挂起,退出,崩溃的情况下运行task.也可以利用标识符来进行恢复。注意:后台session一定要在创建的时候赋予一个唯一的identifier,这样在app下次运行的时候,能够根据identifier来进行相关的区分,如果用户关闭了app,ios系统会关闭所有的background Session.而且被用户强制关闭了以后,iOS系统不回主动唤醒app,只有用户下次启动了app,数据传输才会继续
    let configuration = URLSessionConfiguration.background(withIdentifier: self.createID())
    let session = URLSession.init(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main)        
    session.downloadTask(with: url).resume()
    

    session代理

    extension ViewController:URLSessionDownloadDelegate{
        func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
            // 下载完成 - 开始沙盒迁移
            print("下载完成 - \(location)")
            let locationPath = location.path
            //拷贝到用户目录(文件名以时间戳命名)
            let documnets = NSHomeDirectory() + "/Documents/" + self.lgCurrentDataTurnString() + ".mp4"
            print("移动地址:\(documnets)")
            //创建文件管理器
            let fileManager = FileManager.default
            try! fileManager.moveItem(atPath: locationPath, toPath: documnets)
    
        }
        
        func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) {
            print(" bytesWritten \(bytesWritten)\n totalBytesWritten \(totalBytesWritten)\n totalBytesExpectedToWrite \(totalBytesExpectedToWrite)")
            print("下载进度: \(Double(totalBytesWritten)/Double(totalBytesExpectedToWrite))\n")
        }
    }
    

    注意上面的设置还是不能达到后台下载,还需要设置下面2步

    • 开启后台下载权限 (The completion handler to call when you finish processing the events. Calling this completion handler lets the system know that your app’s user interface is updated and a new snapshot can be taken.)
        用于保存后台下载的completionHandler
        var backgroundSessionCompletionHandler: (() -> Void)?
        
        func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
            self.backgroundSessionCompletionHandler = completionHandler
        }
    
    • 回调系统回调,告诉系统及时更新屏幕
    func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
            print("后台任务下载回来")
            DispatchQueue.main.async {
                guard let appDelegate = UIApplication.shared.delegate as? AppDelegate, let backgroundHandle = appDelegate.backgroundSessionCompletionHandler else { return }
                backgroundHandle()
            }
        }
    

    三、TCP的三次握手

    • http请求是基于tcp连接的
    • tcp有6种表示位
      1. SYN(synchronous建立联机)
      2. ACK(acknowledgement确认)
      3. PSH(push传送)
      4. FIN(finish结束)
      5. RST(reset重置)
      6. URG(urgent紧急)
    • sequence number(顺序号码)
    • acknowledge number(确认号码)
      在这里插入图片描述
    1. 客户端向服务器发出连接请求报文,这时报文首部中的同部位SYN=1,同时随机生成初始序列号seq=x,此时,TCP客户端进程进入了SYN-SENT(同步已发送状态)状态。TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。这个三次握手中的开始,表示客户端想要和服务端建立连接。
    2. TCP服务器收到请求报文后,如果同意连接,则发出确认报文,确认报文中应该ACK=1,SYN=1,确认号是ack=x+1,同时也要自己随机初始化一个序列号seq=y,此时,TCP服务器进程进入了SYN-RCVD(同步收到)状态,这个报文也不能携带数据,但是同样要消耗一个序号。这个报文带有SYN(建立连接)和ACK(确认)标志,询问客户端是否准备好。
    3. TCP客户进程收到确认后,还要向服务器给出确认。确认报文的ACK=1,ack=y+1,此时,TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态,TCP规定,ACK报文段可以携带数据,但是如果不携带数据则不消耗序号,这里客户端表示我已经准备好。
    • 为什么要三次握手呢
      • 举例:已失效的连接请求报文段
      • 客户端发送了第一个连接的请求报文,但由于网络不好,这个请求没有立即到达服务端,而是在某个网络节点中滞留了,直到某个时间才到达server,本来这已经是一个失效的报文,但是server端收到这个请求报文后,还是会像客户端发送确认的报文,表示同意连接。假如不采用三次握手,那么server发出确认后,新的建立就连接了,但其实这个请求是失效的请求,客户端是不会理睬server端的确认信息的,也不会像服务端发送确认的请求,但是server认为新的连接已经建立起来了,并一直等待客户端发来的数据,这样server端很多资源都白白浪费掉了,采用三次握手就是为了防止这种情况的发生,server会因为收不到确认的报文,就知道客户端并没有建立连接这就是三次握手的作用。

    四、TCP数据的传输过程

    • 建立连接后,两台主机就可以相互传输数据了。如下图所示:
      在这里插入图片描述

    • 主机A初始seq为1200,滑动窗体为100,向主机B传递数据的过程。

    • 假设主机B在完全成功接收数据的基础上,那么主机B为了确认这一点,向主机A发送 ACK 包,并将 Ack 号设置为 1301。因此按如下的公式确认 Ack 号:
      Ack号 = Seq号 + 传递的字节数 + 1 (这是在完全接受成功的情况下)

    • 主机A获得B传来的ack(1301)后,开始发送seq为1301,滑动窗体为100的数据。…

    • 与三次握手协议相同,最后加 1 是为了告诉对方要传递的 Seq 号。上面说了,主机B完全成功接收A发来的数据才是这样的,如果存在丢包该如何

    • 下面分析传输过程中数据包丢失的情况,如下图所示:
      在这里插入图片描述

    • 上图表示通过 Seq 1301 数据包向主机B传递100字节的数据,但中间发生了错误,主机B未收到。经过一段时间后,主机A仍未收到对于 Seq 1301 的ACK确认,因此尝试

    • 重传数据。为了完成数据包的重传,TCP套接字每次发送数据包时都会启动定时器,如果在一定时间内没有收到目标机器传回的 ACK 包,那么定时器超时,数据包会重传。

    五、TCP的四次挥手

    在这里插入图片描述

    1. TCP发送一个FIN(结束),用来关闭客户端到服务端的连接
      • 客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号+1)
      • 此时,客户端进入FIN-WAIT-1(终止等待1)的状态。TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
    2. 服务端收到这个FIN,他发回一个ACK(确认),确认收到序号为收到序号+1,和SYN一样,一个FIN将占用一个序号。
      • 服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器
      • 通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个
      • 状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
      • 客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
    3. 服务端发送一个FIN(结束)到客户端,服务端关闭客户端的连接。
      • 服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,
      • 此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
    4. 客户端发送ACK(确认)报文确认,并将确认的序号+1,这样关闭完成。
      • 客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时
      • TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
      • 服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
    • 为什么是4次挥手呢?
      • 为了确保数据能够完成传输。
      • 关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可以未必会马上会关闭SOCKET,也
      • 即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。
      • 可能有人会有疑问,tcp我握手的时候为何ACK(确认)和SYN(建立连接)是一起发送。挥手的时候为什么是分开的时候发送呢.
      • 因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到
      • FIN报文时,很可能并不会立即关闭 SOCKET,所以只能先回复一个ACK报文,告诉Client端,“你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能
      • 发送FIN报文,因此不能一起发送。故需要四步握手。
    • 客户端突然挂掉了怎么办?
      • 正常连接时,客户端突然挂掉了,如果没有措施处理这种情况,那么就会出现客户端和服务器端出现长时期的空闲。解决办法是在服务器端设置保活计时器,每当服务器收到
      • 客户端的消息,就将计时器复位。超时时间通常设置为2小时。若服务器超过2小时没收到客户的信息,他就发送探测报文段。若发送了10个探测报文段,每一个相隔75秒,
      • 还没有响应就认为客户端出了故障,因而终止该连接。
    展开全文
  • Swift Alamofire

    2018-12-03 23:34:13
    Alamofire 是由AFNetworking的作者Matt Thompson使用swift写的一个网络请求库。Git地址https://github.com/Alamofire/Alamofire 1.安装Alamofire platform :ios, '10.0' use_frameworks! target '&lt;Your ...

    Alamofire 是由AFNetworking的作者Matt Thompson使用swift写的一个网络请求库。Git地址https://github.com/Alamofire/Alamofire

    1.安装Alamofire

    platform :ios, '10.0'
    use_frameworks!
    
    target '<Your Target Name>' do
        pod 'Alamofire', '~> 4.7'
    end
    cd /..../yourprojectpath
    pod install

    2.导入Alamfire

    import Alamfire

    3.基本请求

            3.1 get请求

    Alamofire.request("http://www.webpath.com", method: .get, parameters: nil, encoding: URLEncoding.default, headers: nil).responseJSON { (response) in
       if (response.error == nil){
           print("Alamofire请求成功:\(response.result.value)")
       }else{
           print("Alamofire请求失败:\(response.error ?? "" as! Error)")
       }
    }

         3.2 post请求

    Alamofire.request("http://www.webpath.com", method: .post, parameters: nil, encoding: URLEncoding.default, headers: nil).responseJSON { (response) in
        if (response.error == nil){
           print("Alamofire请求成功:\(response.result.value)")
        }else{
           print("Alamofire请求失败:\(response.error ?? "" as! Error)")
        }
    }

    4.http请求错误(iOS9以后,苹果把原http协议改成了https协议,所以不能直接在http协议下GET/POST)

    解决方案之一:
        直接编辑工程文件下的Info.plist文件,加入以下代码

    <key>NSAppTransportSecurity</key>  
      <dict>  
           <key>NSAllowsArbitraryLoads</key>
          <true/>  
        </dict>

    解决方案之二:
        1.在Xcode里选中info.plist,点击右边的information Property List 后边的加号
        2.写入App Transport Security Settings后,点击右键添加Add Row,选中Allow Arbitrary Loads添加,直接回车
        3.将Allow Arbitrary Loads默认的值改成YES

    其实效果是一样的,打开info.plist文件,发现方案一中的文字已经自动添加

    展开全文
  • Alamofire-SwiftyJSON 一个扩展,可以轻松地使用序列化的响应。 :warning_selector: 要与Swift 3.x一起使用,请确保您使用的是> = 2.0.0 :warning_selector: :warning_selector: 要与Swift 4.x一起使用,请确保...
  • Alamofire(四)怎么合理使用Alamofire

    千次阅读 2019-08-24 22:45:26
    Alamofire(四)怎么合理使用Alamofire(一) Alamofire框架功能简介(二)Alamofire api使用1. 发请求2. 响应处理2.1 五种不同的响应handler2.1.1 响应 Handler2.1.2 响应数据 Handler2.1.3 响应字符串 Handler...

    Alamofire(四)怎么合理使用Alamofire

    (一) Alamofire框架功能简介

    • 前面写了三篇关于Alamofire框架的博客,基本是循序渐进的方式,先讲解了必须了解的网络原理,协议等知识,然后讲解了苹果自带框架api UISession的一些知识。而Alamofire就是基于这些实现的网络框架,专注于网络相关的api。
    • 为了更好的理解Alamofire框架的实现原理,很有必要先了解Alamofire框架是什么?它提供了一些什么功能?如果正确高效的使用它?
    • 带着这三个问题,本篇博客将围绕这三个方向来阐述。
      Alamofire框架
    • (1) Alamofire框架是什么?

    Alamofire就是牛逼的框架AFNetwork开发者的母公司开发的一套基于swift实现的网络框架。Alamofire专注于核心网络的实现,Alamofire生态系统还有另外两个库:AlamofireImageAlamofireNetworkActivityIndicator

    1. AlamofireImage
      一个图片库,包括图像响应序列化器、UIImage和UIImageView的扩展、自定义图像滤镜、内存中自动清除和基于优先级的图像下载系统。
    1. AlamofireNetworkActivityIndicator
      控制iOS应用的网络活动指示器。包含可配置的延迟计时器来帮助减少闪光,并且支持不受Alamofire管理的URLSession实例。
    • (2) Alamofire框架提供了什么功能?
    1. 链式请求 / 响应方法
    2. URL / JSON / plist参数编码
    3. 上传文件 / 数据 / 流 / 多表单数据
    4. 使用请求或者断点下载来下载文件
    5. 使用URL凭据进行身份认证
    6. HTTP响应验证
    7. 包含进度的上传和下载闭包
    8. cURL命令的输出
    9. 动态适配和重试请求
    10. TLS证书和Public Key Pinning
    11. 网络可达性
    12. 全面的单元和集成测试覆盖率
    1. 要求的使用环境:

    iOS 10.0+ / macOS 10.12+ / tvOS 10.0+ / watchOS 3.0+
    Xcode 10.2+
    Swift 5+

    1. 安装方法:
    source 'https://github.com/CocoaPods/Specs.git'
    platform :ios, '10.0'
    use_frameworks!
    
    target '项目名称' do
        pod 'Alamofire', '~> 5.0.0-beta.5'
    end
    
    1. api使用,下面将详细讲述

    (二)Alamofire api使用

    1. 发请求

    Alamofire.request("http://qq.com/")
    

    2. 响应处理

    • 直接在请求后面用点语法链接响应处理:

    实例1:

    Alamofire.request("https://httpbin.org/get").responseJSON { response in
        print(response.request)  // 原始的URL请求
        print(response.response) // HTTP URL响应
        print(response.data)     // 服务器返回的数据
        print(response.result)   // 响应序列化结果,在这个闭包里,存储的是JSON数据
    
        if let JSON = response.result.value {
            print("JSON: \(JSON)")
        }
    }
    

    在上面的例子中,responseJSON handler直接拼接到请求后面,当请求完成后被调用。这个闭包一旦收到响应后,就会处理这个响应,并不会因为等待服务器的响应而造成阻塞执行。请求的结果仅在响应闭包的范围内可用。其他任何与服务器返回的响应或者数据相关的操作,都必须在这个闭包内执行。

    2.1 五种不同的响应handler

    实例2:

    • 所有的响应handler都不会对响应进行验证。也就是说响应状态码在400…<500和500…<600范围内,都不会触发错误。
    // 响应 Handler - 未序列化的响应
    func response(
        queue: DispatchQueue?,
        completionHandler: @escaping (DefaultDataResponse) -> Void)
        -> Self
    
    // 响应数据 Handler - 序列化成数据类型
    func responseData(
        queue: DispatchQueue?,
        completionHandler: @escaping (DataResponse<Data>) -> Void)
        -> Self
    
    // 响应字符串 Handler - 序列化成字符串类型
    func responseString(
        queue: DispatchQueue?,
        encoding: String.Encoding?,
        completionHandler: @escaping (DataResponse<String>) -> Void)
        -> Self
    
    // 响应 JSON Handler - 序列化成Any类型
    func responseJSON(
        queue: DispatchQueue?,
        completionHandler: @escaping (DataResponse<Any>) -> Void)
        -> Self
    
    // 响应 PropertyList (plist) Handler - 序列化成Any类型
    func responsePropertyList(
        queue: DispatchQueue?,
        completionHandler: @escaping (DataResponse<Any>) -> Void))
        -> Self
    

    2.1.1 响应 Handler

    • response handler不处理任何响应数据。它仅仅是从URL session delegate中转发信息。
    Alamofire.request("https://httpbin.org/get").response { response in
        print("Request: \(response.request)")
        print("Response: \(response.response)")
        print("Error: \(response.error)")
    
        if let data = response.data, let utf8Text = String(data: data, encoding: .utf8) {
            print("Data: \(utf8Text)")
        }
    }
    
    • 一般情况下不建议使用这种没有响应序列化器的handler,而应该使用下面有特定序列化器的handler。

    2.1.2 响应数据 Handler

    • responseData handler使用responseDataSerializer(这个对象把服务器的数据序列化成其他类型)来提取服务器返回的数据。如果没有返回错误并且有数据返回,那么响应Result将会是.successvalueData类型。
    Alamofire.request("https://httpbin.org/get").responseData { response in
        debugPrint("All Response Info: \(response)")
    
        if let data = response.result.value, let utf8Text = String(data: data, encoding: .utf8) {
            print("Data: \(utf8Text)")
        }
    }
    

    2.1.3 响应字符串 Handler

    • responseString handler使用responseStringSerializer对象根据指定的编码格式把服务器返回的数据转换成String。如果没有返回错误并且服务器的数据成功地转换为String,那么响应Result将会是.success,value是String类型。
    Alamofire.request("https://httpbin.org/get").responseString { response in
        print("Success: \(response.result.isSuccess)")
        print("Response String: \(response.result.value)")
    }
    
    • 如果没有指定编码格式,将会使用服务器的HTTPURLResponse指定的格式。如果服务器无法确定编码格式,那么默认使用.isoLatin1

    2.1.4 响应 JSON Handler

    • responseJSON handler使用responseJSONSerializer根据指定的JSONSerialization.ReadingOptions把服务器返回的数据转换成Any类型。如果没有返回错误并且服务器的数据成功地转换为JSON对象,那么响应Result将会是.success,value是Any类型。
    Alamofire.request("https://httpbin.org/get").responseJSON { response in
        debugPrint(response)
    
        if let json = response.result.value {
            print("JSON: \(json)")
        }
    }
    
    • 所有JSON的序列化,都是使用JSONSerialization完成的。

    2.1.5 链式响应handler

    • 响应handler可以链接在一起:
    Alamofire.request("https://httpbin.org/get")
        .responseString { response in
            print("Response String: \(response.result.value)")
        }
        .responseJSON { response in
            print("Response JSON: \(response.result.value)")
        }
    
    • 在同一个请求中使用多个响应handler,要求服务器的数据会被序列化多次,每次对应一个handler。

    2.2 响应handler队列

    • 默认情况下,响应handler是在主队列执行的。但是我们也可以自定义队列:
    let utilityQueue = DispatchQueue.global(qos: .utility)
    
    Alamofire.request("https://httpbin.org/get").responseJSON(queue: utilityQueue) { response in
        print("Executing response handler on utility queue")
    }
    

    2.3 响应验证

    • 默认情况下,Alamofire把所有完成的请求当做是成功的请求,无论响应的内容是什么。如果响应有一个不能被接受的状态码或者MIME类型,在响应handler之前调用validate将会产生错误。

    • 手动验证:

    Alamofire.request("https://httpbin.org/get")
        .validate(statusCode: 200..<300)
        .validate(contentType: ["application/json"])
        .responseData { response in
        switch response.result {
        case .success:
            print("Validation Successful")
        case .failure(let error):
            print(error)
        }
    }
    
    • 自动验证:自动验证在200…299范围内的状态码;如果请求头中有指定Accept,那么也会验证响应头的与请求头Accept一样的Content-Type。
    Alamofire.request("https://httpbin.org/get").validate().responseJSON { response in
        switch response.result {
        case .success:
            print("Validation Successful")
        case .failure(let error):
            print(error)
        }
    }
    

    2.4 响应缓存

    • 响应缓存是使用系统的框架URLCache来处理的。它提供了内存和磁盘上的缓存,并允许我们控制内存和磁盘的大小。
    • 默认情况下,Alamofire利用共享的URLCache。

    2.5 HTTP协议相关

    2.5.1 HTTP方法

    • HTTPMethod列举了下面的这些方法:
    public enum HTTPMethod: String {
        case options = "OPTIONS"
        case get     = "GET"
        case head    = "HEAD"
        case post    = "POST"
        case put     = "PUT"
        case patch   = "PATCH"
        case delete  = "DELETE"
        case trace   = "TRACE"
        case connect = "CONNECT"
    }
    
    • 在使用Alamofire.request时,可以传入方法参数:
    Alamofire.request("https://httpbin.org/get") // 默认是get请求
    
    Alamofire.request("https://httpbin.org/post", method: .post)
    Alamofire.request("https://httpbin.org/put", method: .put)
    Alamofire.request("https://httpbin.org/delete", method: .delete)
    

    2.5.2 HTTP请求头

    • 可以直接在请求方法添加自定义HTTP请求头,这有利于我们在请求中添加请求头。
    let headers: HTTPHeaders = [
        "Authorization": "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
        "Accept": "application/json"
    ]
    
    Alamofire.request("https://httpbin.org/headers", headers: headers).responseJSON { response in
        debugPrint(response)
    }
    
    • 对于那些不变的请求头,建议在URLSessionConfiguration设置,这样就可以自动被用于任何URLSession创建的URLSessionTask
    • 默认的Alamofire SessionManager为每一个请求提供了一个默认的请求头集合,包括:

    Accept-Encoding,默认是gzip;q=1.0, compress;q=0.5。

    Accept-Language,默认是系统的前6个偏好语言,格式类似于en;q=1.0。

    User-Agent,包含当前应用程序的版本信息。例如iOS Example/1.0 (com.alamofire.iOS-Example; build:1; iOS 10.0.0) Alamofire/4.0.0。

    • 如果要自定义这些请求头集合,我们必须创建一个自定义的URLSessionConfigurationdefaultHTTPHeaders属性将会被更新,并且自定义的会话配置也会应用到新的SessionManager实例。

    2.5.3 认证

    • 认证是使用系统框架URLCredentialURLAuthenticationChallenge实现的。
    • 支持的认证方案:

    HTTP Basic
    HTTP Digest
    Kerberos
    NTLM

    2.5.3.1 HTTP Basic认证
    • 在合适的时候,在一个请求的authenticate方法会自动提供一个URLCredential给URLAuthenticationChallenge:
    let user = "user"
    let password = "password"
    
    Alamofire.request("https://httpbin.org/basic-auth/\(user)/\(password)")
        .authenticate(user: user, password: password)
        .responseJSON { response in
            debugPrint(response)
    }
    
    • 根据服务器实现,Authorization header也可能是适合的:
    let user = "user"
    let password = "password"
    
    var headers: HTTPHeaders = [:]
    
    if let authorizationHeader = Request.authorizationHeader(user: user, password: password) {
        headers[authorizationHeader.key] = authorizationHeader.value
    }
    
    Alamofire.request("https://httpbin.org/basic-auth/user/password", headers: headers)
        .responseJSON { response in
            debugPrint(response)
    }
    
    2.5.3.2 使用URLCredential认证
    • 使用URLCredential来做认证,如果服务器发出一个challenge,底层的URLSession实际上最终会发两次请求。第一次请求不会包含credential,并且可能会触发服务器发出一个challenge。这个challenge会被Alamofire接收,credential会被添加,然后URLSessin会重试请求。
    let user = "user"
    let password = "password"
    
    let credential = URLCredential(user: user, password: password, persistence: .forSession)
    
    Alamofire.request("https://httpbin.org/basic-auth/\(user)/\(password)")
        .authenticate(usingCredential: credential)
        .responseJSON { response in
            debugPrint(response)
    }
    

    2.6 编码相关

    2.6.1 参数编码

    • Alamofire支持三种参数编码:URLJSONPropertyList。还支持遵循了ParameterEncoding协议的自定义编码。
    2.6.1.1 URL编码
    • URLEncoding类型创建了一个URL编码的查询字符串来设置或者添加到一个现有的URL查询字符串,或者设置URL请求的请求体。查询字符串是否被设置或者添加到现有的URL查询字符串,或者被作为HTTP请求体,决定于编码的Destination。编码的Destination有三个case:

      • .methodDependent:为GET、HEAD和DELETE请求使用编码查询字符串来设置或者添加到现有查询字符串,并且使用其他HTTP方法来设置请求体。

      • .queryString:设置或者添加编码查询字符串到现有查询字符串

      • .httpBody:把编码查询字符串作为URL请求的请求体

    • 一个编码请求的请求体的Content-Type字段被设置为application/x-www-form-urlencoded; charset=utf-8。因为没有公开的标准说明如何编码集合类型,所以按照惯例在key后面添加[]来表示数组的值(foo[]=1&foo[]=2),在key外面包一个中括号来表示字典的值(foo[bar]=baz)。

    • 使用URL编码参数的GET请求

    let parameters: Parameters = ["foo": "bar"]
    
    // 下面这三种写法是等价的
    Alamofire.request("https://httpbin.org/get", parameters: parameters) // encoding 默认是`URLEncoding.default`
    Alamofire.request("https://httpbin.org/get", parameters: parameters, encoding: URLEncoding.default)
    Alamofire.request("https://httpbin.org/get", parameters: parameters, encoding: URLEncoding(destination: .methodDependent))
    
    // https://httpbin.org/get?foo=bar
    
    • 使用URL编码参数的POST请求
    let parameters: Parameters = [
        "foo": "bar",
        "baz": ["a", 1],
        "qux": [
            "x": 1,
            "y": 2,
            "z": 3
        ]
    ]
    
    // 下面这三种写法是等价的
    Alamofire.request("https://httpbin.org/post", method: .post, parameters: parameters)
    Alamofire.request("https://httpbin.org/post", method: .post, parameters: parameters, encoding: URLEncoding.default)
    Alamofire.request("https://httpbin.org/post", method: .post, parameters: parameters, encoding: URLEncoding.httpBody)
    
    // HTTP body: foo=bar&baz[]=a&baz[]=1&qux[x]=1&qux[y]=2&qux[z]=3
    
    设置Bool类型参数的编码
    • URLEncoding.BoolEncoding提供了两种编码方式:

    .numeric:把true编码为1,false编码为0
    .literal:把true编码为true,false编码为false

    • 默认情况下:Alamofire使用.numeric。
    • 可以使用下面的初始化函数来创建URLEncoding,指定Bool编码的类型:
    let encoding = URLEncoding(boolEncoding: .literal)
    
    设置Array类型参数编码
    • URLEncoding.ArrayEncoding提供了两种编码方式:

    .brackets: 在每个元素值的key后面加上一个[],如foo=[1,2]编码成foo[]=1&foo[]=2
    .noBrackets:不添加[],例如foo=[1,2]编码成``foo=1&foo=2`

    • 默认情况下,Alamofire使用.brackets。
    • 可以使用下面的初始化函数来创建URLEncoding,指定Array编码的类型:
    let encoding = URLEncoding(arrayEncoding: .noBrackets)
    
    2.6.1.2 JSON编码
    • JSONEncoding类型创建了一个JOSN对象,并作为请求体。编码请求的请求头的Content-Type请求字段被设置为application/json
    • 使用JSON编码参数的POST请求
    let parameters: Parameters = [
        "foo": [1,2,3],
        "bar": [
            "baz": "qux"
        ]
    ]
    
    // 下面这两种写法是等价的
    Alamofire.request("https://httpbin.org/post", method: .post, parameters: parameters, encoding: JSONEncoding.default)
    Alamofire.request("https://httpbin.org/post", method: .post, parameters: parameters, encoding: JSONEncoding(options: []))
    
    // HTTP body: {"foo": [1, 2, 3], "bar": {"baz": "qux"}}
    
    2.6.1.3 属性列表编码
    • PropertyListEncoding根据关联格式和写选项值,使用PropertyListSerialization来创建一个属性列表对象,并作为请求体。编码请求的请求头的Content-Type请求字段被设置为application/x-plist
    2.6.1.4 自定义编码
    • 如果提供的ParameterEncoding类型不能满足我们的要求,可以创建自定义编码。下面演示如何快速自定义一个JSONStringArrayEncoding类型把JSON字符串数组编码到请求中。
    struct JSONStringArrayEncoding: ParameterEncoding {
        private let array: [String]
    
        init(array: [String]) {
            self.array = array
        }
    
        func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
            var urlRequest = urlRequest.urlRequest
    
            let data = try JSONSerialization.data(withJSONObject: array, options: [])
    
            if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
                urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
            }
    
            urlRequest.httpBody = data
    
            return urlRequest
        }
    }
    
    2.6.1.5 手动URL请求参数编码
    • ParameterEncodingAPI可以在创建网络请求外面使用。
    let url = URL(string: "https://httpbin.org/get")!
    var urlRequest = URLRequest(url: url)
    
    let parameters: Parameters = ["foo": "bar"]
    let encodedURLRequest = try URLEncoding.queryString.encode(urlRequest, with: parameters)
    

    2.7 下载

    2.7.1 将数据下载到文件

    • Alamofire可以把服务器的数据下载到内存(in-memory)或者硬盘(on-disk)中。所有Alamofire.requestAPI下载的数据都是存储在内存中。这比较适合小文件,更高效;但是不适合大文件,因为大文件会把内存耗尽。我们要使用Alamofire.downloadAPI把服务器的数据下载到硬盘中。

    • 下面这个方法只适用于macOS。因为在其他平台不允许在应用沙盒外访问文件系统。下面会讲到如何在其他平台下载文件。

    Alamofire.download("https://httpbin.org/image/png").responseData { response in
        if let data = response.result.value {
            let image = UIImage(data: data)
        }
    }
    

    2.7.2 下载文件存储位置

    • 我们可以提供一个DownloadFileDestination闭包把临时文件夹的文件移动到一个目标文件夹。在临时文件真正移动到destinationURL之前,闭包内部指定的DownloadOptions将会被执行。目前支持的DownloadOptions有下面两个:
      • .createIntermediateDirectories:如果指定了目标URL,将会创建中间目录。
      • .removePreviousFile:如果指定了目标URL,将会移除之前的文件
    let destination: DownloadRequest.DownloadFileDestination = { _, _ in
        let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
        let fileURL = documentsURL.appendPathComponent("pig.png")
    
        return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
    }
    
    Alamofire.download(urlString, to: destination).response { response in
        print(response)
    
        if response.error == nil, let imagePath = response.destinationURL?.path {
            let image = UIImage(contentsOfFile: imagePath)
        }
    }
    
    • 也可以直接使用建议的下载目标API:
    let destination = DownloadRequest.suggestedDownloadDestination(directory: .documentDirectory)
    Alamofire.download("https://httpbin.org/image/png", to: destination)
    

    2.7.3 下载进度

    • 所有的DownloadRequest都可以使用downloadProgressAPI来反馈下载进度。
    Alamofire.download("https://httpbin.org/image/png")
        .downloadProgress { progress in
            print("Download Progress: \(progress.fractionCompleted)")
        }
        .responseData { response in
            if let data = response.result.value {
                let image = UIImage(data: data)
        }
    }
    
    • downloadProgressAPI还可以接受一个queue参数来指定下载进度闭包在哪个DispatchQueue中执行。
    let utilityQueue = DispatchQueue.global(qos: .utility)
    
    Alamofire.download("https://httpbin.org/image/png")
        .downloadProgress(queue: utilityQueue) { progress in
            print("Download Progress: \(progress.fractionCompleted)")
        }
        .responseData { response in
            if let data = response.result.value {
                let image = UIImage(data: data)
        }
    }
    
    

    2.7.4 恢复下载

    • 如果一个DownloadRequest被取消或中断,底层的URL会话会生成一个恢复数据。恢复数据可以被重新利用并在中断的位置继续下载。恢复数据可以通过下载响应访问,然后在重新开始请求的时候被利用。
    • 在iOS 10 - 10.2, macOS 10.12 - 10.12.2, tvOS 10 - 10.1, watchOS 3 - 3.1.1中,resumeData会被后台URL会话配置破坏。因为在resumeData的生成逻辑有一个底层的bug,不能恢复下载。具体情况可以到Stack Overflow看看
    class ImageRequestor {
        private var resumeData: Data?
        private var image: UIImage?
    
        func fetchImage(completion: (UIImage?) -> Void) {
            guard image == nil else { completion(image) ; return }
    
            let destination: DownloadRequest.DownloadFileDestination = { _, _ in
                let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
                let fileURL = documentsURL.appendPathComponent("pig.png")
    
                return (fileURL, [.removePreviousFile, .createIntermediateDirectories])
            }
    
            let request: DownloadRequest
    
            if let resumeData = resumeData {
                request = Alamofire.download(resumingWith: resumeData)
            } else {
                request = Alamofire.download("https://httpbin.org/image/png")
            }
    
            request.responseData { response in
                switch response.result {
                case .success(let data):
                    self.image = UIImage(data: data)
                case .failure:
                    self.resumeData = response.resumeData
                }
            }
        }
    }
    

    2.8 上传

    2.8.1 上传数据到服务器

    • 使用JOSN或者URL编码参数上传一些小数据到服务器,使用Alamofire.request API就已经足够了。如果需要发送很大的数据,需要使用Alamofire.upload API。当我们需要在后台上传数据时,也可以使用Alamofire.upload。

    2.8.2 上传数据

    let imageData = UIPNGRepresentation(image)!
    
    Alamofire.upload(imageData, to: "https://httpbin.org/post").responseJSON { response in
        debugPrint(response)
    }
    

    2.8.3 上传文件

    let fileURL = Bundle.main.url(forResource: "video", withExtension: "mov")
    
    Alamofire.upload(fileURL, to: "https://httpbin.org/post").responseJSON { response in
        debugPrint(response)
    }
    

    2.8.4 上传多部分表单数据

    Alamofire.upload(
        multipartFormData: { multipartFormData in
            multipartFormData.append(unicornImageURL, withName: "unicorn")
            multipartFormData.append(rainbowImageURL, withName: "rainbow")
        },
        to: "https://httpbin.org/post",
        encodingCompletion: { encodingResult in
            switch encodingResult {
            case .success(let upload, _, _):
                upload.responseJSON { response in
                    debugPrint(response)
                }
            case .failure(let encodingError):
                print(encodingError)
            }
        }
    )
    

    2.8.5 上传进度

    • 所有的UploadRequest都可以使用uploadProgress和downloadProgress APIs来反馈上传和下载进度。
    let fileURL = Bundle.main.url(forResource: "video", withExtension: "mov")
    
    Alamofire.upload(fileURL, to: "https://httpbin.org/post")
        .uploadProgress { progress in // 默认在主线程中执行
            print("Upload Progress: \(progress.fractionCompleted)")
        }
        .downloadProgress { progress in // 默认在主线程中执行
            print("Download Progress: \(progress.fractionCompleted)")
        }
        .responseJSON { response in
            debugPrint(response)
    }
    

    2.9 统计指标

    2.9.1 时间表

    • Alamofire在一个请求周期内收集时间,并创建一个Tineline对象,它是响应类型的一个属性。
    Alamofire.request("https://httpbin.org/get").responseJSON { response in
        print(response.timeline)
    }
    

    上面的Timeline信息包括:
    Latency: 0.428 seconds (延迟)
    Request Duration: 0.428 seconds (请求时间)
    Serialization Duration: 0.001 seconds (序列化时间)
    Total Duration: 0.429 seconds (总时间)

    2.9.2 URL会话任务指标

    • 在iOS和tvOS 10和macOS 10.12中,苹果发布了新的URLSessionTaskMetrics APIs。这个任务指标封装了关于请求和响应执行的神奇统计信息。这个API和Timeline非常相似,但是提供了很多Alamofire没有提供的统计信息。这些指标可以通过任何响应去访问。
    Alamofire.request("https://httpbin.org/get").responseJSON { response in
        print(response.metrics)
    }
    
    • 这些API只能在iOS和tvOS 10和macOS 10.12中使用。所以,根据部署目标,可能需要加入版本判断:
    Alamofire.request("https://httpbin.org/get").responseJSON { response in
        if #available(iOS 10.0. *) {
            print(response.metrics)
        }
    }
    

    2.9.3 cURL命令输出

    • 调试平台问题很让人厌烦。庆幸的是,Alamofire的Request对象遵循了CustomStringConvertibleCustomDebugStringConvertible协议来提供一些非常有用的调试工具。
    • CustomStringConvertible
    let request = Alamofire.request("https://httpbin.org/ip")
    print(request)
    // GET https://httpbin.org/ip (200)
    
    • CustomDebugStringConvertible
    let request = Alamofire.request("https://httpbin.org/get", parameters: ["foo": "bar"])
    debugPrint(request)
    

    输出结果:

    $ curl -i \
        -H "User-Agent: Alamofire/4.0.0" \
        -H "Accept-Encoding: gzip;q=1.0, compress;q=0.5" \
        -H "Accept-Language: en;q=1.0,fr;q=0.9,de;q=0.8,zh-Hans;q=0.7,zh-Hant;q=0.6,ja;q=0.5" \
        "https://httpbin.org/get?foo=bar"
    

    (三)Alamofire不同场景api使用

    1. Session Manager

    • 我们可以自己创建后台会话和短暂会话的session manager,还可以自定义默认的会话配置来创建新的session manager,例如修改默认的header httpAdditionalHeaders和timeoutIntervalForRequest。
    Alamofire.request("https://httpbin.org/get")
    
    let sessionManager = Alamofire.SessionManager.default
    sessionManager.request("https://httpbin.org/get")
    
    • 用默认的会话配置创建一个Session Manager
    let configuration = URLSessionConfiguration.default
    let sessionManager = Alamofire.SessionManager(configuration: configuration)
    
    • 用后台会话配置创建一个Session Manager
    let configuration = URLSessionConfiguration.background(withIdentifier: "com.example.app.background")
    let sessionManager = Alamofire.SessionManager(configuration: configuration)
    
    • 用默短暂会话配置创建一个Session Manager
    let configuration = URLSessionConfiguration.ephemeral
    let sessionManager = Alamofire.SessionManager(configuration: configuration)
    
    • 修改会话配置
    //不推荐在Authorization或者Content-Type header使用。而应该使用Alamofire.requestAPI、URLRequestConvertible和ParameterEncoding的headers参数。
    var defaultHeaders = Alamofire.SessionManager.defaultHTTPHeaders
    defaultHeaders["DNT"] = "1 (Do Not Track Enabled)"
    
    let configuration = URLSessionConfiguration.default
    configuration.httpAdditionalHeaders = defaultHeaders
    
    let sessionManager = Alamofire.SessionManager(configuration: configuration)
    

    1.1 会话代理 SessionDelegate

    • 默认情况下,一个SessionManager实例创建一个SessionDelegate对象来处理底层URLSession生成的不同类型的代理回调。每个代理方法的实现处理常见的情况。然后,高级用户可能由于各种原因需要重写默认功能。

    • 有两种方式实现SessionDelegate:

      • 方式一:自定义SessionDelegate的方法是通过重写闭包。我们可以在每个闭包重写SessionDelegate API对应的实现。

      • 方式二:重写SessionDelegate的实现的方法是把它子类化。通过子类化,我们可以完全自定义他的行为,或者为这个API创建一个代理并且仍然使用它的默认实现。通过创建代理,我们可以跟踪日志事件、发通知、提供前后实现。

    • 实现SessionDelegate 代码实例:

    1. 重写闭包的示例:
    /// 重写URLSessionDelegate的`urlSession(_:didReceive:completionHandler:)`方法
    open var sessionDidReceiveChallenge: ((URLSession, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))?
    
    /// 重写URLSessionDelegate的`urlSessionDidFinishEvents(forBackgroundURLSession:)`方法 
    open var sessionDidFinishEventsForBackgroundURLSession: ((URLSession) -> Void)?
    
    /// 重写URLSessionTaskDelegate的`urlSession(_:task:willPerformHTTPRedirection:newRequest:completionHandler:)`方法 
    open var taskWillPerformHTTPRedirection: ((URLSession, URLSessionTask, HTTPURLResponse, URLRequest) -> URLRequest?)?
    
    /// 重写URLSessionDataDelegate的`urlSession(_:dataTask:willCacheResponse:completionHandler:)`方法 
    open var dataTaskWillCacheResponse: ((URLSession, URLSessionDataTask, CachedURLResponse) -> CachedURLResponse?)?
    
    1. 使用taskWillPerformHTTPRedirection来避免回调到任何apple.com域名。
    let sessionManager = Alamofire.SessionManager(configuration: URLSessionConfiguration.default)
    let delegate: Alamofire.SessionDelegate = sessionManager.delegate
    
    delegate.taskWillPerformHTTPRedirection = { session, task, response, request in
        var finalRequest = request
    
        if
            let originalRequest = task.originalRequest,
            let urlString = originalRequest.url?.urlString,
            urlString.contains("apple.com")
        {
            finalRequest = originalRequest
        }
    
        return finalRequest
    }
    
    1. 下面这个例子演示了如何子类化SessionDelegate,并且有回调的时候打印信息:
    class LoggingSessionDelegate: SessionDelegate {
        override func urlSession(
            _ session: URLSession,
            task: URLSessionTask,
            willPerformHTTPRedirection response: HTTPURLResponse,
            newRequest request: URLRequest,
            completionHandler: @escaping (URLRequest?) -> Void)
        {
            print("URLSession will perform HTTP redirection to request: \(request)")
    
            super.urlSession(
                session,
                task: task,
                willPerformHTTPRedirection: response,
                newRequest: request,
                completionHandler: completionHandler
            )
        }
    }
    
    • 总的来说,无论是默认实现还是重写闭包,都应该提供必要的功能。子类化应该作为最后的选择。

    2. 请求 Request

    • requestdownloaduploadstream方法的结果是DataRequestDownloadRequestUploadRequestStreamRequest,并且所有请求都继承自Request。所有的Request并不是直接创建的,而是由session manager创建的。

    • 每个子类都有特定的方法,例如authenticatevalidateresponseJSONuploadProgress,都返回一个实例,以便方法链接(也就是用点语法连续调用方法)。

    • 请求可以被暂停、恢复和取消:

    suspend():暂停底层的任务和调度队列
    resume():恢复底层的任务和调度队列。如果manager的startRequestsImmediately不是true,那么必须调用resume()来开始请求。
    cancel():取消底层的任务,并产生一个error,error被传入任何已经注册的响应handlers。

    • 随着应用的不多增大,当我们建立网络栈的时候要使用通用的模式。在通用模式的设计中,一个很重要的部分就是如何传送请求。遵循Router设计模式的URLConvertibleURLRequestConvertible协议可以帮助我们

    2.1 DataRequest

    2.2 DownloadRequest

    2.3 UploadRequest

    2.4 StreamRequest

    2.5 URLConvertible

    • 遵循了URLConvertible协议的类型可以被用来构建URL,然后用来创建URL请求。String、URL和URLComponent默认是遵循URLConvertible协议的。它们都可以作为url参数传入requestuploaddownload方法.
    • 以一种有意义的方式和web应用程序交互的应用,都鼓励使用自定义的遵循URLConvertible协议的类型将特定领域模型映射到服务器资源,因为这样比较方便。
    let urlString = "https://httpbin.org/post"
    Alamofire.request(urlString, method: .post)
    
    let url = URL(string: urlString)!
    Alamofire.request(url, method: .post)
    
    let urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: true)!
    Alamofire.request(urlComponents, method: .post)
    
    • 类型安全传送
    extension User: URLConvertible {
        static let baseURLString = "https://example.com"
    
        func asURL() throws -> URL {
            let urlString = User.baseURLString + "/users/\(username)/"
            return try urlString.asURL()
        }
    }
    
    
    let user = User(username: "mattt")
    Alamofire.request(user) // https://example.com/users/mattt
    

    2.6 URLRequestConvertible

    • 遵循URLRequestConvertible协议的类型可以被用来构建URL请求。URLRequest默认遵循了URLRequestConvertible,允许被直接传入requestuploaddownload(推荐用这种方法为单个请求自定义请求头)
    • 以一种有意义的方式和web应用程序交互的应用,都鼓励使用自定义的遵循URLRequestConvertible协议的类型来保证请求端点的一致性。这种方法可以用来抽象服务器端的不一致性,并提供类型安全传送,以及管理身份验证凭据和其他状态。
    let url = URL(string: "https://httpbin.org/post")!
    var urlRequest = URLRequest(url: url)
    urlRequest.httpMethod = "POST"
    
    let parameters = ["foo": "bar"]
    
    do {
        urlRequest.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: [])
    } catch {
        // No-op
    }
    
    urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
    
    Alamofire.request(urlRequest)
    
    • API参数抽象
    enum Router: URLRequestConvertible {
        case search(query: String, page: Int)
    
        static let baseURLString = "https://example.com"
        static let perPage = 50
    
        // MARK: URLRequestConvertible
    
        func asURLRequest() throws -> URLRequest {
            let result: (path: String, parameters: Parameters) = {
                switch self {
                case let .search(query, page) where page > 0:
                    return ("/search", ["q": query, "offset": Router.perPage * page])
                case let .search(query, _):
                    return ("/search", ["q": query])
                }
            }()
    
            let url = try Router.baseURLString.asURL()
            let urlRequest = URLRequest(url: url.appendingPathComponent(result.path))
    
            return try URLEncoding.default.encode(urlRequest, with: result.parameters)
        }
    }
    
    Alamofire.request(Router.search(query: "foo bar", page: 1)) // https://example.com/search?q=foo%20bar&offset=50
    

    2.7 CRUD和授权

    import Alamofire
    
    enum Router: URLRequestConvertible {
        case createUser(parameters: Parameters)
        case readUser(username: String)
        case updateUser(username: String, parameters: Parameters)
        case destroyUser(username: String)
    
        static let baseURLString = "https://example.com"
    
        var method: HTTPMethod {
            switch self {
            case .createUser:
                return .post
            case .readUser:
                return .get
            case .updateUser:
                return .put
            case .destroyUser:
                return .delete
            }
        }
    
        var path: String {
            switch self {
            case .createUser:
                return "/users"
            case .readUser(let username):
                return "/users/\(username)"
            case .updateUser(let username, _):
                return "/users/\(username)"
            case .destroyUser(let username):
                return "/users/\(username)"
            }
        }
    
        // MARK: URLRequestConvertible
    
        func asURLRequest() throws -> URLRequest {
            let url = try Router.baseURLString.asURL()
    
            var urlRequest = URLRequest(url: url.appendingPathComponent(path))
            urlRequest.httpMethod = method.rawValue
    
            switch self {
            case .createUser(let parameters):
                urlRequest = try URLEncoding.default.encode(urlRequest, with: parameters)
            case .updateUser(_, let parameters):
                urlRequest = try URLEncoding.default.encode(urlRequest, with: parameters)
            default:
                break
            }
    
            return urlRequest
        }
    }
    
    Alamofire.request(Router.readUser("mattt")) // GET https://example.com/users/mattt
    

    2.8 适配和重试请求

    • 现在的大多数Web服务,都需要身份认证。现在比较常见的是OAuth。通常是需要一个access token来授权应用或者用户,然后才可以使用各种支持的Web服务。创建这些access token是比较麻烦的,当access token过期之后就比较麻烦了,我们需要重新创建一个新的。有许多线程安全问题要考虑。

    • RequestAdapterRequestRetrier协议可以让我们更容易地为特定的Web服务创建一个线程安全的认证系统。

    2.8.1 RequestAdapter

    • RequestAdapter协议允许每一个SessionManager的Request在创建之前被检查和适配。一个非常特别的使用适配器方法是,在一个特定的认证类型,把Authorization header拼接到请求。
    1. 创建一个AccessTokenAdapter类继承RequestAdapter
    class AccessTokenAdapter: RequestAdapter {
        private let accessToken: String
    
        init(accessToken: String) {
            self.accessToken = accessToken
        }
    
        func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
            var urlRequest = urlRequest
    
            if let urlString = urlRequest.url?.absoluteString, urlString.hasPrefix("https://httpbin.org") {
                urlRequest.setValue("Bearer " + accessToken, forHTTPHeaderField: "Authorization")
            }
    
            return urlRequest
        }
    
    }
    
    1. 创建SessionManager,并将AccessTokenAdapter赋值给sessionManager
    let sessionManager = SessionManager()
    sessionManager.adapter = AccessTokenAdapter(accessToken: "1234")
    
    sessionManager.request("https://httpbin.org/get")
    

    2.8.2 RequestRetrier

    • RequestRetrier协议允许一个在执行过程中遇到error的请求被重试。当一起使用RequestAdapterRequestRetrier协议时,我们可以为OAuth1OAuth2Basic Auth(每次请求API都要提供用户名和密码)甚至是exponential backoff重试策略创建资格恢复系统。下面的例子演示了如何实现一个OAuth2 access token的恢复流程。

    实例282

    注意:下面代码不是一个全面的OAuth2解决方案。这仅仅是演示如何把RequestAdapter和RequestRetrier协议结合起来创建一个线程安全的恢复系统。
    重申: 不要把这个例子复制到实际的开发应用中,这仅仅是一个例子。每个认证系统必须为每个特定的平台和认证类型重新定制。

    1. 创建一个类OAuth2Handler,同时继承:RequestAdapter, RequestRetrier 两个协议
    class OAuth2Handler: RequestAdapter, RequestRetrier {
        private typealias RefreshCompletion = (_ succeeded: Bool, _ accessToken: String?, _ refreshToken: String?) -> Void
    
        private let sessionManager: SessionManager = {
            let configuration = URLSessionConfiguration.default
            configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
    
            return SessionManager(configuration: configuration)
        }()
    
        private let lock = NSLock()
    
        private var clientID: String
        private var baseURLString: String
        private var accessToken: String
        private var refreshToken: String
    
        private var isRefreshing = false
        private var requestsToRetry: [RequestRetryCompletion] = []
    
        // MARK: - Initialization
    
        public init(clientID: String, baseURLString: String, accessToken: String, refreshToken: String) {
            self.clientID = clientID
            self.baseURLString = baseURLString
            self.accessToken = accessToken
            self.refreshToken = refreshToken
        }
    
        // MARK: - RequestAdapter
    
        func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
            if let urlString = urlRequest.url?.absoluteString, urlString.hasPrefix(baseURLString) {
                var urlRequest = urlRequest
                urlRequest.setValue("Bearer " + accessToken, forHTTPHeaderField: "Authorization")
                return urlRequest
            }
    
            return urlRequest
        }
    
        // MARK: - RequestRetrier
    
        func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
            lock.lock() ; defer { lock.unlock() }
    
            if let response = request.task?.response as? HTTPURLResponse, response.statusCode == 401 {
                requestsToRetry.append(completion)
    
                if !isRefreshing {
                    refreshTokens { [weak self] succeeded, accessToken, refreshToken in
                        guard let strongSelf = self else { return }
    
                        strongSelf.lock.lock() ; defer { strongSelf.lock.unlock() }
    
                        if let accessToken = accessToken, let refreshToken = refreshToken {
                            strongSelf.accessToken = accessToken
                            strongSelf.refreshToken = refreshToken
                        }
    
                        strongSelf.requestsToRetry.forEach { $0(succeeded, 0.0) }
                        strongSelf.requestsToRetry.removeAll()
                    }
                }
            } else {
                completion(false, 0.0)
            }
        }
    
        // MARK: - Private - Refresh Tokens
    
        private func refreshTokens(completion: @escaping RefreshCompletion) {
            guard !isRefreshing else { return }
    
            isRefreshing = true
    
            let urlString = "\(baseURLString)/oauth2/token"
    
            let parameters: [String: Any] = [
                "access_token": accessToken,
                "refresh_token": refreshToken,
                "client_id": clientID,
                "grant_type": "refresh_token"
            ]
    
            sessionManager.request(urlString, method: .post, parameters: parameters, encoding: JSONEncoding.default)
                .responseJSON { [weak self] response in
                    guard let strongSelf = self else { return }
    
                    if 
                        let json = response.result.value as? [String: Any], 
                        let accessToken = json["access_token"] as? String, 
                        let refreshToken = json["refresh_token"] as? String 
                    {
                        completion(true, accessToken, refreshToken)
                    } else {
                        completion(false, nil, nil)
                    }
    
                    strongSelf.isRefreshing = false
                }
        }
    }
    
    1. 创建sessionManager对象,并将sessionManager.adapter,sessionManager.retrier 都同时指向OAuth2Handler对象。
    let baseURLString = "https://some.domain-behind-oauth2.com"
    
    let oauthHandler = OAuth2Handler(
        clientID: "12345678",
        baseURLString: baseURLString,
        accessToken: "abcd1234",
        refreshToken: "ef56789a"
    )
    
    let sessionManager = SessionManager()
    sessionManager.adapter = oauthHandler
    sessionManager.retrier = oauthHandler
    
    let urlString = "\(baseURLString)/some/endpoint"
    
    sessionManager.request(urlString).validate().responseJSON { response in
        debugPrint(response)
    }
    
    • 一旦OAuth2HandlerSessionManager被应用与adapterretrier,他将会通过自动恢复access token来处理一个非法的access token error,并且根据失败的顺序来重试所有失败的请求。(如果需要让他们按照创建的时间顺序来执行,可以使用他们的task identifier来排序)

    • 上面这个例子仅仅检查了401响应码,不是演示如何检查一个非法的access token error。在实际开发应用中,我们想要检查realmwww-authenticate header响应,虽然这取决于OAuth2的实现。

    • 还有一个要重点注意的是,这个认证系统可以在多个session manager之间共享。例如,可以在同一个Web服务集合使用defaultephemeral会话配置。上面这个例子可以在多个session manager间共享一个oauthHandler实例,来管理一个恢复流程。

    2.9

    3. 序列化

    3.1 自定义响应序列化

    • Alamofire为datastringsJSONProperty List提供了内置的响应序列化:
    Alamofire.request(...).responseData { (resp: DataResponse<Data>) in ... }
    Alamofire.request(...).responseString { (resp: DataResponse<String>) in ... }
    Alamofire.request(...).responseJSON { (resp: DataResponse<Any>) in ... }
    Alamofire.request(...).responsePropertyList { resp: DataResponse<Any>) in ... }
    
    • 这些响应包装了反序列化的值(Data, String, Any)或者error (network, validation errors),以及元数据 (URL Request, HTTP headers, status code, metrics, …)。
    • 我们可以有多个方法来自定义所有响应元素:
      • 响应映射
      • 处理错误
      • 创建一个自定义的响应序列化器
      • 泛型响应对象序列化

    3.1.1 响应映射

    • 响应映射是自定义响应最简单的方式。它转换响应的值,同时保留最终错误和元数据。例如,我们可以把一个json响应DataResponse转换为一个保存应用模型的的响应,例如DataResponse。使用DataResponse.map来进行响应映射:
    Alamofire.request("https://example.com/users/mattt").responseJSON { (response: DataResponse<Any>) in
        let userResponse = response.map { json in
            // We assume an existing User(json: Any) initializer
            return User(json: json)
        }
    
        // Process userResponse, of type DataResponse<User>:
        if let user = userResponse.value {
            print("User: { username: \(user.username), name: \(user.name) }")
        }
    }
    
    • 当转换可能会抛出错误时,使用flatMap方法:
    Alamofire.request("https://example.com/users/mattt").responseJSON { response in
        let userResponse = response.flatMap { json in
            try User(json: json)
        }
    }
    
    • 响应映射非常适合自定义completion handler:
    @discardableResult
    func loadUser(completionHandler: @escaping (DataResponse<User>) -> Void) -> Alamofire.DataRequest {
        return Alamofire.request("https://example.com/users/mattt").responseJSON { response in
            let userResponse = response.flatMap { json in
                try User(json: json)
            }
    
            completionHandler(userResponse)
        }
    }
    
    loadUser { response in
        if let user = userResponse.value {
            print("User: { username: \(user.username), name: \(user.name) }")
        }
    }
    
    • 上面代码中loadUser方法被@discardableResult标记,意思是调用loadUser方法可以不接收它的返回值;也可以用_来忽略返回值。
    • 当 map/flatMap 闭包会产生比较大的数据量时,要保证这个闭包在子线程中执行:
    @discardableResult
    func loadUser(completionHandler: @escaping (DataResponse<User>) -> Void) -> Alamofire.DataRequest {
        let utilityQueue = DispatchQueue.global(qos: .utility)
    
        return Alamofire.request("https://example.com/users/mattt").responseJSON(queue: utilityQueue) { response in
            let userResponse = response.flatMap { json in
                try User(json: json)
            }
    
            DispatchQueue.main.async {
                completionHandler(userResponse)
            }
        }
    }
    
    • map和flatMap也可以用于下载响应。

    3.1.2 处理错误

    • 在实现自定义响应序列化器或者对象序列化方法前,思考如何处理所有可能出现的错误是非常重要的。有两个方法:1)传递未修改的错误,在响应时间处理;2)把所有的错误封装在一个Error类型中。
    • 例如,下面是等会要用用到的后端错误:
    enum BackendError: Error {
        case network(error: Error) // 捕获任何从URLSession API产生的错误
        case dataSerialization(error: Error)
        case jsonSerialization(error: Error)
        case xmlSerialization(error: Error)
        case objectSerialization(reason: String)
    }
    

    3.1.3 创建一个自定义的响应序列化器

    • Alamofire为stringsJSONProperty List提供了内置的响应序列化,但是我们可以通过扩展Alamofire.DataRequest或者Alamofire.DownloadRequest来添加其他序列化。
    • 例如,下面这个例子是一个使用Ono (一个实用的处理iOS和macOS平台的XML和HTML的方式)的响应handler的实现:
    extension DataRequest {
        static func xmlResponseSerializer() -> DataResponseSerializer<ONOXMLDocument> {
            return DataResponseSerializer { request, response, data, error in
                // 把任何底层的URLSession error传递给 .network case
                guard error == nil else { return .failure(BackendError.network(error: error!)) }
    
                // 使用Alamofire已有的数据序列化器来提取数据,error为nil,因为上一行代码已经把不是nil的error过滤了
                let result = Request.serializeResponseData(response: response, data: data, error: nil)
    
                guard case let .success(validData) = result else {
                    return .failure(BackendError.dataSerialization(error: result.error! as! AFError))
                }
    
                do {
                    let xml = try ONOXMLDocument(data: validData)
                    return .success(xml)
                } catch {
                    return .failure(BackendError.xmlSerialization(error: error))
                }
            }
        }
    
        @discardableResult
        func responseXMLDocument(
            queue: DispatchQueue? = nil,
            completionHandler: @escaping (DataResponse<ONOXMLDocument>) -> Void)
            -> Self
        {
            return response(
                queue: queue,
                responseSerializer: DataRequest.xmlResponseSerializer(),
                completionHandler: completionHandler
            )
        }
    }
    

    3.1.4 泛型响应对象序列化

    • 泛型可以用来提供自动的、类型安全的响应对象序列化。

    • 代码1

    protocol ResponseObjectSerializable {
        init?(response: HTTPURLResponse, representation: Any)
    }
    
    extension DataRequest {
        func responseObject<T: ResponseObjectSerializable>(
            queue: DispatchQueue? = nil,
            completionHandler: @escaping (DataResponse<T>) -> Void)
            -> Self
        {
            let responseSerializer = DataResponseSerializer<T> { request, response, data, error in
                guard error == nil else { return .failure(BackendError.network(error: error!)) }
    
                let jsonResponseSerializer = DataRequest.jsonResponseSerializer(options: .allowFragments)
                let result = jsonResponseSerializer.serializeResponse(request, response, data, nil)
    
                guard case let .success(jsonObject) = result else {
                    return .failure(BackendError.jsonSerialization(error: result.error!))
                }
    
                guard let response = response, let responseObject = T(response: response, representation: jsonObject) else {
                    return .failure(BackendError.objectSerialization(reason: "JSON could not be serialized: \(jsonObject)"))
                }
    
                return .success(responseObject)
            }
    
            return response(queue: queue, responseSerializer: responseSerializer, completionHandler: completionHandler)
        }
    }
    
    • 代码2
    struct User: ResponseObjectSerializable, CustomStringConvertible {
        let username: String
        let name: String
    
        var description: String {
            return "User: { username: \(username), name: \(name) }"
        }
    
        init?(response: HTTPURLResponse, representation: Any) {
            guard
                let username = response.url?.lastPathComponent,
                let representation = representation as? [String: Any],
                let name = representation["name"] as? String
            else { return nil }
    
            self.username = username
            self.name = name
        }
    }
    
    • 代码3
    Alamofire.request("https://example.com/users/mattt").responseObject { (response: DataResponse<User>) in
        debugPrint(response)
    
        if let user = response.result.value {
            print("User: { username: \(user.username), name: \(user.name) }")
        }
    }
    
    • 代码4
    protocol ResponseCollectionSerializable {
        static func collection(from response: HTTPURLResponse, withRepresentation representation: Any) -> [Self]
    }
    
    extension ResponseCollectionSerializable where Self: ResponseObjectSerializable {
        static func collection(from response: HTTPURLResponse, withRepresentation representation: Any) -> [Self] {
            var collection: [Self] = []
    
            if let representation = representation as? [[String: Any]] {
                for itemRepresentation in representation {
                    if let item = Self(response: response, representation: itemRepresentation) {
                        collection.append(item)
                    }
                }
            }
    
            return collection
        }
    }
    
    • 代码5
    extension DataRequest {
        @discardableResult
        func responseCollection<T: ResponseCollectionSerializable>(
            queue: DispatchQueue? = nil,
            completionHandler: @escaping (DataResponse<[T]>) -> Void) -> Self
        {
            let responseSerializer = DataResponseSerializer<[T]> { request, response, data, error in
                guard error == nil else { return .failure(BackendError.network(error: error!)) }
    
                let jsonSerializer = DataRequest.jsonResponseSerializer(options: .allowFragments)
                let result = jsonSerializer.serializeResponse(request, response, data, nil)
    
                guard case let .success(jsonObject) = result else {
                    return .failure(BackendError.jsonSerialization(error: result.error!))
                }
    
                guard let response = response else {
                    let reason = "Response collection could not be serialized due to nil response."
                    return .failure(BackendError.objectSerialization(reason: reason))
                }
    
                return .success(T.collection(from: response, withRepresentation: jsonObject))
            }
    
            return response(responseSerializer: responseSerializer, completionHandler: completionHandler)
        }
    }
    
    • 代码6
    struct User: ResponseObjectSerializable, ResponseCollectionSerializable, CustomStringConvertible {
        let username: String
        let name: String
    
        var description: String {
            return "User: { username: \(username), name: \(name) }"
        }
    
        init?(response: HTTPURLResponse, representation: Any) {
            guard
                let username = response.url?.lastPathComponent,
                let representation = representation as? [String: Any],
                let name = representation["name"] as? String
            else { return nil }
    
            self.username = username
            self.name = name
        }
    }
    
    • 代码7
    Alamofire.request("https://example.com/users").responseCollection { (response: DataResponse<[User]>) in
        debugPrint(response)
    
        if let users = response.result.value {
            users.forEach { print("- \($0)") }
        }
    }
    

    4. 安全

    • 对于安全敏感的数据来说,在与服务器和web服务交互时使用安全的HTTPS连接是非常重要的一步。默认情况下,Alamofire会使用苹果安全框架内置的验证方法来评估服务器提供的证书链。虽然保证了证书链是有效的,但是不能防止man-in-the-middle (MITM)攻击或者其他潜在的漏洞。为了减少MITM攻击,处理用户的敏感数据或财务信息的应用,应该使用ServerTrustPolicy提供的certificate或者public key pinning

    4. 1 ServerTrustPolicy

    • 在通过HTTPS安全连接连接到服务器时,ServerTrustPolicy枚举通常会评估URLAuthenticationChallenge提供的server trust。
    let serverTrustPolicy = ServerTrustPolicy.pinCertificates(
        certificates: ServerTrustPolicy.certificates(),
        validateCertificateChain: true,
        validateHost: true
    )
    
    • 在验证的过程中,有多种方法可以让我们完全控制server trust的评估:
    属性 作用
    performDefaultEvaluation 使用默认的server trust评估,允许我们控制是否验证challenge提供的host。
    pinCertificates 使用pinned certificates来验证server trust。如果pinned certificates匹配其中一个服务器证书,那么认为server trust是有效的。
    pinPublicKeys 使用pinned public keys来验证server trust。如果pinned public keys匹配其中一个服务器证书公钥,那么认为server trust是有效的。
    disableEvaluation 禁用所有评估,总是认为server trust是有效的。
    customEvaluation 使用相关的闭包来评估server trust的有效性,我们可以完全控制整个验证过程。但是要谨慎使用。

    4. 2 ServerTrustPolicyManager(服务器信任策略管理者 )

    • ServerTrustPolicyManager负责存储一个内部的服务器信任策略到特定主机的映射。这样Alamofire就可以评估每个主机不同服务器信任策略。
    let serverTrustPolicies: [String: ServerTrustPolicy] = [
        "test.example.com": .pinCertificates(
            certificates: ServerTrustPolicy.certificates(),
            validateCertificateChain: true,
            validateHost: true
        ),
        "insecure.expired-apis.com": .disableEvaluation
    ]
    
    let sessionManager = SessionManager(
        serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
    )
    
    • 要确保有一个强引用引用着SessionManager实例,否则当sessionManager被销毁时,请求将会取消。
    • 这些服务器信任策略将会形成下面的结果:
    1. test.example.com:始终使用证书链固定的证书和启用主机验证,因此需要以下条件才能是TLS握手成功:
      (1) 证书链必须是有效的。
      (2) 证书链必须包含一个已经固定的证书。
      (3) Challenge主机必须匹配主机证书链的子证书。
    2. insecure.expired-apis.com:将从不评估证书链,并且总是允许TLS握手成功。
    3. 其他主机将会默认使用苹果提供的验证。

    4. 3 子类化服务器信任策略管理者

    • 如果我们需要一个更灵活的服务器信任策略来匹配其他行为(例如通配符域名),可以子类化ServerTrustPolicyManager,并且重写serverTrustPolicyForHost方法。
    class CustomServerTrustPolicyManager: ServerTrustPolicyManager {
        override func serverTrustPolicy(forHost host: String) -> ServerTrustPolicy? {
            var policy: ServerTrustPolicy?
    
            // Implement your custom domain matching behavior...
    
            return policy
        }
    }
    

    4. 4 验证主机

    • .performDefaultEvaluation.pinCertificates.pinPublicKeys这三个服务器信任策略都带有一个validateHost参数。把这个值设为true,服务器信任评估就会验证与challenge主机名字匹配的在证书里面的主机名字。如果他们不匹配,验证失败。如果设置为false,仍然会评估整个证书链,但是不会验证子证书的主机名字。
    • 建议在实际开发中,把validateHost设置为true

    4. 5 验证证书链

    • Pinning certificatepublic keys 都可以通过validateCertificateChain参数拥有验证证书链的选项。把它设置为true,除了对Pinning certificatepublic keys进行字节相等检查外,还将会验证整个证书链。如果是false,将会跳过证书链验证,但还会进行字节相等检查。
    • 还有很多情况会导致禁用证书链认证。最常用的方式就是自签名和过期的证书。在这些情况下,验证始终会失败。但是字节相等检查会保证我们从服务器接收到证书。
    • 建议在实际开发中,把validateCertificateChain设置为true

    4. 6 ATS 应用传输安全 (App Transport Security)

    • 从iOS9开始,就添加了App Transport Security (ATS),使用ServerTrustPolicyManager和多个ServerTrustPolicy对象可能没什么影响。如果我们不断看到CFNetwork SSLHandshake failed (-9806)错误,我们可能遇到了这个问题。苹果的ATS系统重写了整个challenge系统,除非我们在plist文件中配置ATS设置来允许应用评估服务器信任。
    • plist文件设置如下:
    <dict>
        <key>NSAppTransportSecurity</key>
        <dict>
            <key>NSExceptionDomains</key>
            <dict>
                <key>example.com</key>
                <dict>
                    <key>NSExceptionAllowsInsecureHTTPLoads</key>
                    <true/>
                    <key>NSExceptionRequiresForwardSecrecy</key>
                    <false/>
                    <key>NSIncludesSubdomains</key>
                    <true/>
                    <!-- 可选的: 指定TLS的最小版本 -->
                    <key>NSTemporaryExceptionMinimumTLSVersion</key>
                    <string>TLSv1.2</string>
                </dict>
            </dict>
        </dict>
    </dict>
    
    • 是否需要把NSExceptionRequiresForwardSecrecy设置为NO取决于TLS连接是否使用一个允许的密码套件。在某些情况下,它需要设置为NO。NSExceptionAllowsInsecureHTTPLoads必须设置为YES,然后SessionDelegate才能接收到challenge回调。一旦challenge回调被调用,ServerTrustPolicyManager将接管服务器信任评估。如果我们要连接到一个仅支持小于1.2版本的TSL主机,那么还要指定NSTemporaryExceptionMinimumTLSVersion
    • 在实际开发中,建议始终使用有效的证书。

    5. 网络可达性

    5. 1 Network Reachability

    • NetworkReachabilityManager监听WWAN和WiFi网络接口和主机地址的可达性变化。
    let manager = NetworkReachabilityManager(host: "www.apple.com")
    manager?.listener = { status in
        print("Network Status Changed: \(status)")
    }
    manager?.startListening()
    
    • 要确保manager被强引用,否则会接收不到状态变化。另外,在主机字符串中不要包含scheme,也就是说要把https://去掉,否则无法监听。
    • 当使用网络可达性来决定接下来要做什么时,有以下几点需要重点注意的:
      • 不要使用Reachability来决定是否发送一个网络请求,我们必须要发送请求。
      • 当Reachability恢复了,要重试网络请求。即使网络请求失败,在这个时候也非常适合重试请求。
      • 网络可达性的状态非常适合用来决定为什么网络请求会失败。如果一个请求失败,应该告诉用户是离线导致请求失败的,而不是技术错误,例如请求超时。
    展开全文
  • EVURLCache and Alamofire

    2020-12-08 20:41:33
    But I use Alamofire and I can’t figure out how to get the result of the request. When cache is involed, Alamofire .Success or .Failure callback are not called. However I well see the log stating that...
  • alamofire监听网络I’ve previously written a network manager with the intention of testing it completely. Can I do the same using Alamofire? That is, test the requisite classes without making real ...
  • 加载Alamofire时出现提示: underlying Objective-C module 'Alamofire' not found 基本操作是按照github 中操作的.https://github.com/Alamofire/Alamofire 但是还是弹出这种提示: 其实加载Alamofire很简单. 1. ...
  • Alamofire 3.0 Support

    2021-01-07 00:41:43
    <div><p>This is not working with lastest version of Alamofire 3.0 </p><p>该提问来源于开源项目:tristanhimmelman/AlamofireObjectMapper</p></div>
  • Alamofire & Operations

    2020-11-23 02:06:09
    <div><p>How can we use Alamofire and Operations framework all together?</p><p>该提问来源于开源项目:ProcedureKit/ProcedureKit</p></div>
  • Pinning With Alamofire

    2020-11-28 11:35:24
    <div><p>Is there any way to implement pining with TrustKit and Alamofire? </p><p>该提问来源于开源项目:datatheorem/TrustKit</p></div>
  • Alamofire实践-源码

    2021-02-21 19:05:33
    Alamofireを使うための练习用リポジトリ sーバーサイドRails フロントエンドSwiftUI iOSアプリでHTTP通信のできるものを目指す。 机能 任意の文字を投稿,编集,削除するだけのもの。ログイン机能はつけない。 まず...
  • Alamofire-使用

    千次阅读 2019-08-26 18:31:00
    所有类型请求,Alamofire都直接提供,通过Alamofire直接调用,配置相关参数即可。示例如下: 1、GET请求 let urlStr = "http://onapp.yahibo.top/public/?s=api/test/list" Alamofire.request(url).responseJSON { ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 2,455
精华内容 982
关键字:

alamofire