美文网首页
Alamofire 1

Alamofire 1

作者: f8d1cf28626a | 来源:发表于2022-07-04 03:20 被阅读0次

Alamofire 1

1.URLSession

1.URLSession必备技能

Alamofire是一个为iOS和macOS打造的并基于Swift的网络库.
它在Apple的基础网络架构上提供了更加优雅的接口来简化繁重而常用的网络请求任务。
Alamofire提供了链式的request/response方法,JSON的传参和响应序列化,身份认证和其他特性。
Alamofire的优雅之处在于它完完全全是由Swift写成的,
并且没有从它的Objective-C版本-AFNetworking那继承任何特性。

Alamofire是对苹果URLSession的封装,所以在探索Alamofire之前,我们来看看URLSession的必备基础

  • URLSession

请求网络的基本格式

URLSession.shared.dataTask(with: url) { (data, response, error) in
    if error == nil {
        print("请求成功\(String(describing: response))" )
    }
}.resume()
  • URLSession.shared提供了一个共享的单例会话对象,它为创建任务提供了一个合理的默认行为。使用共享会话仅用几行代码将URL的内容获取到内存中。

  • dataTask创建一个网络会话数据任务。

  • resume默认网络任务是挂起的,调用执行开始连接请求网络:三次握手...

  • 请求成果或者失败都会回来闭包

  • 闭包只是一层封装,真正来的是URLSession的代理

  • 在这个过程中,我们省略一个重要的东西:URLSessionConfiguration

  • URLSessionConfiguration

2. 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))")

打印结果:
沙盒大小: Optional(10000000)
内存大小: Optional(512000)
沙盒大小: Optional(0)
内存大小: Optional(512000)
  • background创建一个可以在后台甚至APP已经关闭的时候仍然在传输数据的会话。background模式与default模式非常相似,不过background模式会用一个独立线程来进行数据传输。background模式可以在程序挂起,退出,崩溃的情况下运行task。也可以利用标识符来恢复进。注意,后台Session一定要在创建的时候赋予一个唯一的identifier,这样在APP下次运行的时候,能够根据identifier来进行相关的区分。如果用户关闭了APP,IOS 系统会关闭所有的background Session。而且,被用户强制关闭了以后,IOS系统不会主动唤醒APP,只有用户下次启动了APP,数据传输才会继续
// 初始化一个后台的模式的会话配置
let configuration = URLSessionConfiguration.background(withIdentifier: self.createID())
// 初始化session会话
let session = URLSession.init(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main)
// 传入url开启下载
session.downloadTask(with: url).resume()
  • 初始化一个后台的模式的会话配置
  • 初始化session会话
  • 传入url开启下载resume

下载进度监控

func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)

下载完成之后就回调URLSessionDownloadDelegate代理

func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL)

易错易忽略点

上面这么设置,还不能达到后台下载!还需要设置下面两步

  • 开启后台下载权限
//用于保存后台下载的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()
    }
}

为什么这么处理,苹果大佬说的算

苹果大佬说的算

2.常规属性

identifier:配置对象的后台会话标识符。
httpAdditionalHeaders:与请求一起发送的附加头文件的字典。
networkServiceType:网络服务的类型
allowsCellularAccess:一个布尔值,用于确定是否应通过蜂窝网络进行连接。
timeoutIntervalForRequest:等待其他数据时使用的超时间隔。
timeoutIntervalForResource:资源请求应该允许的最大时间量。
sharedContainerIdentifier:应该下载后台URL会话中的文件的共享容器的标识符。
waitsForConnectivity:一个布尔值,指示会话是否应等待连接变为可用或者立即失败
  1. 设置Cookie政策
httpCookieAcceptPolicy:决定何时应该接受Cookie的策略常量
httpShouldSetCookies:一个布尔值,用于确定请求是否应包含来自Cookie存储的Cookie。
httpCookieStorage:管理cookie存储的单一对象(共享实例)
HTTPCookie:表示HTTP cookie的对象。它是一个不可变的对象,从包含cookie属性的字典中初始化
  1. 设置安全策略
tlsMaximumSupportedProtocol:在此会话中进行连接时客户端应请求的最大TLS协议版本。
tlsMinimumSupportedProtocol:协议协商期间应该接受的最小TLS协议。
urlCredentialStorage:提供身份验证凭据的凭证存储
  1. 设置缓存策略
urlCache:用于向会话中的请求提供缓存响应的URL缓存
requestCachePolicy:一个预定义常量,用于确定何时从缓存中返回响应
  1. 支持后台转移
sessionSendsLaunchEvents:一个布尔值,指示在传输完成时是否应该在后台继续或启动应用程序.
isDiscretionary:一个布尔值,用于确定是否可以根据系统的判断来调度后台任务以获得最佳性能。
  1. 支持自定义协议
protocolClasses:在会话中处理请求的额外协议子类的数组

URLProtocol:一个NSURLProtocol对象处理加载协议特定的URL数据。
在NSURLProtocol类本身是一个抽象类,可以为与特定URL方案的URL处理基础设施。
您可以为您的应用支持的任何自定义协议或URL方案创建子类
  1. 支持多路径TCP
multipathServiceType:指定用于通过Wi-Fi和蜂窝接口传输数据的多路径TCP连接策略的服务类型

URLSessionConfiguration.MultipathServiceType:指定多路径TCP使用的服务类型的常量
  1. 设置HTTP策略和代理属性
httpMaximumConnectionsPerHost:同时连接到给定主机的最大数量。
httpShouldUsePipelining:一个布尔值,用于确定会话是否应使用HTTP流水线
connectionProxyDictionary:包含有关在此会话中使用的代理信息的字典
  1. 支持连接变化
waitsForConnectivity:一个布尔值,指示会话是否应等待连接变为可用或者立即失败。

3.NSURLRequestCachePolicy

  • NSURLRequestUseProtocolCachePolicy = 0,: 默认缓存策略
    如果一个NSCachedURLResponse对于请求并不存在,数据将会从源端获取。如果请求拥有一个缓存的响应,那么URL加载系统会检查这个响应来决定,如果它指定内容必须重新生效的话。假如内容必须重新生效,将建立一个连向源端的连接来查看内容是否发生变化。假如内容没有变化,那么响应就从本地缓存返回数据。如果内容变化了,那么数据将从源端获取

  • NSURLRequestReloadIgnoringLocalCacheData = 1:URL应该加载源端数据,不使用本地缓存数据

  • NSURLRequestReloadIgnoringLocalAndRemoteCacheData =4:本地缓存数据、代理和其他中介都要忽视他们的缓存,直接加载源数据

  • NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData

  • NSURLRequestReturnCacheDataElseLoad = 2:指定已存的缓存数据应该用来响应请求,不管它的生命时长和过期时间。如果在缓存中没有已存数据来响应请求的话,数据从源端加载

  • NSURLRequestReturnCacheDataDontLoad = 3:指定已存的缓存数据用来满足请求,不管生命时长和过期时间。如果在缓存中没有已存数据来响应URL加载请求的话,不去尝试从源段加载数据,此时认为加载请求失败。这个常量指定了一个类似于离线模式的行为

  • NSURLRequestReloadRevalidatingCacheData = 5:指定如果已存的缓存数据被提供它的源段确认为有效则允许使用缓存数据响应请求,否则从源段加载数据。

Alamofire 2 后台下载

通过 URLSession 和 Alamofire 两种形式分别展开讨论,对比学习才能更能体会 Alamofire 的设计思维

URLSession处理后台下载

URLSession在后台处理方面还是比较简单的

// 1:初始化一个background的模式的configuration
let configuration = URLSessionConfiguration.background(withIdentifier: self.createID())
// 2:通过configuration初始化网络下载会话
let session = URLSession.init(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main)
// 3:session创建downloadTask任务-resume启动
session.downloadTask(with: url).resume()
  • 初始化一个 background 的模式的 configuration。configuration有 三种模式 ,只有background 的模式才能进行后台下载。
  • 通过configuration初始化网络下载会话 session,设置相关代理,回调数据信号响应。
  • session创建downloadTask任务-resume启动 (默认状态:suspend)
  • 接下来依赖苹果封装的网络处理,发起连接 - 发送相关请求 - 回调代理响应
//MARK: - 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")
    }
}
  • 实现了 URLSessionDownloadDelegate的 didFinishDownloadingTo代理,实现下载完成转移临时文件里的数据到相应沙盒保存
  • 通过urlSession(_ session: downloadTask:didWriteData bytesWritten: totalBytesWritten: totalBytesExpectedToWrite: ) 的代理监听下载进度
  • 这里也是因为 http的分片传输 才导致的进度有段的感觉,其实证明内部也是对这个代理方法不断调用,才能进度回调!
网络帧

这里实现了下载功能,但是对于我们需要的后台下载还差一段

Applications using an NSURLSession with a background configuration may be launched
or resumed in the background in order to handle the completion of tasks in that session,
or to handle authentication.
This method will be called with the identifier of the session needing attention.

Once a session has been created from a configuration object with that identifier,
the session's delegate will begin receiving callbacks.
If such a session has already been created (if the app is being resumed, for instance),
then the delegate will start receiving callbacks without any action by the application.
You should call the completionHandler as soon as you're finished handling the callbacks.

苹果大佬总是能在合适时间给你优秀的建议,阅读文档的能力决定你是否能够在这个时代站稳自己的脚尖

class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    //用于保存后台下载的completionHandler
    var backgroundSessionCompletionHandler: (() -> Void)?
    func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
        self.backgroundSessionCompletionHandler = completionHandler
    }
}
  • 实现handleEventsForBackgroundURLSession就可以完美后台下载
  • 告诉代理与 URLSession 相关的事件正在等待处理。
  • 应用程序在所有与 URLSession对象 关联的后台传输完成后调用此方法,无论传输成功完成还是导致错误。如果一个或多个传输需要认证,应用程序也会调用这个方法。
  • 使用此方法可以重新连接任何 URLSession 并更新应用程序的用户界面。例如,您可以使用此方法更新进度指示器或将新内容合并到视图中。在处理事件之后,在 completionHandler 参数中执行 block,这样应用程序就可以获取用户界面的刷新。
  • 我们通过handleEventsForBackgroundURLSession保存相应的回调,这也是非常必要的!告诉系统后台下载回来及时刷新屏幕

在urlSessionDidFinishEvents的代理实现调用

func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
    print("后台任务下载回来")
    DispatchQueue.main.async {
        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate, let backgroundHandle = appDelegate.backgroundSessionCompletionHandler else { return }
        backgroundHandle()
    }
}
  • 拿到UIApplication.shared.delegate的回调函数执行
  • 注意线程切换主线程,毕竟刷新界面

那么如果不实现这个代理里面的回调函数的执行,那么会发生什么呢

  • 后台下载的能力是不会影响的
  • 但是会爆出非常验证界面刷新卡顿,影响用户体验
  • 同时打印台会爆出警告
Warning: Application delegate received call to -
application:handleEventsForBackgroundURLSession:completionHandler: 
but the completion handler was never called.

通过Alamofire后台下载

Alamofire框架还是比较有感觉的,这个节奏也是函数式回调,还支持链式请求和响应!事务逻辑非常清晰,还有代码可读性也是非常简洁

LGBackgroundManger.shared.manager
    .download(self.urlDownloadStr) { (url, response) -> (destinationURL: URL, options: DownloadRequest.DownloadOptions) in
    let documentUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
    let fileUrl     = documentUrl?.appendingPathComponent(response.suggestedFilename!)
    return (fileUrl!,[.removePreviousFile,.createIntermediateDirectories])
    }
    .response { (downloadResponse) in
        print("下载回调信息: \(downloadResponse)")
    }
    .downloadProgress { (progress) in
        print("下载进度 : \(progress)")
}
  • 这里封装了一个单利LGBackgroundManger的后台下载管理类,调用manger的手法也是非常直接。
  • 封装的思想再也不需要去处理恶心的代理事件
struct LGBackgroundManger {    
    static let shared = LGBackgroundManger()

    let manager: SessionManager = {
        let configuration = URLSessionConfiguration.background(withIdentifier: "com.lgroc.AlamofireTest.demo")
        configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
        configuration.timeoutIntervalForRequest = 10
        configuration.timeoutIntervalForResource = 10
        configuration.sharedContainerIdentifier = "group.com.lgroc.AlamofireTest"
        return SessionManager(configuration: configuration)
    }()
}

为什么要做成单利,URLSession的时候不是挺好的?

  • 如果你是 SessionManager.defalut 显然是不可以的!毕竟要求后台下载,那么我们的会话 session 的配置 URLSessionConfiguration 是要求 background模式的
  • 如果你配置出来不做成单利,或者不被持有!在进入后台就会释放,网络也就会报错:Error Domain=NSURLErrorDomain Code=-999 "cancelled"
  • 应用层与网络层也可以达到分离。
  • 能够帮助在AppDelegate 的回调方便直接接收
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
    LGBackgroundManger.shared.manager.backgroundCompletionHandler = completionHandler
}

SessionManger流程分析

  • 1、SessionManger初始化
public init(
    configuration: URLSessionConfiguration = URLSessionConfiguration.default,
    delegate: SessionDelegate = SessionDelegate(),
    serverTrustPolicyManager: ServerTrustPolicyManager? = nil)
{
    self.delegate = delegate
    self.session = URLSession(configuration: configuration, delegate: delegate, delegateQueue: nil)

    commonInit(serverTrustPolicyManager: serverTrustPolicyManager)
}
  • 初始化了session,其中configuration 是 default的模式,设置了一些基本的 SessionManager.defaultHTTPHeaders 请求头信息

  • 代理移交,通过创建 SessionDelegate 这个专门处理代理的类来实现 URLSession的代理

  • 2、代理完成回调

SessionDelegate 是一个非常重要的类,集合所有的代理

URLSessionDelegate
URLSessionTaskDelegate
URLSessionDataDelegate
URLSessionDownloadDelegate
URLSessionStreamDelegate

这里我们根据需求来到 urlSessionDidFinishEvents 的代理

open func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
    sessionDidFinishEventsForBackgroundURLSession?(session)
}
  • 这里执行了 sessionDidFinishEventsForBackgroundURLSession 闭包的执行,那么这个闭包在什么时候申明的呢?
  • 如果你足够聪明,这里你应该是能够想到的,SessionDelegate只是处理代理的专门类,但不是逻辑数据的处理类,按照封装设计的常规思路必将交给管理者类来下发

在我们的 SessionManger 里面的初始化的时候,有一个方法commonInit

delegate.sessionDidFinishEventsForBackgroundURLSession = { [weak self] session in
    guard let strongSelf = self else { return }
    DispatchQueue.main.async { strongSelf.backgroundCompletionHandler?() }
}

这里就是代理的delegate.sessionDidFinishEventsForBackgroundURLSession闭包的声明
只要后台下载完成就会来到这个闭包内部
回调了主线程,调用了 backgroundCompletionHandler , 这也是 SessionManger 对外提供的功能!聪明的你应该知道知道了我在application的操作的本质了!

  • 3、流程总结

  • 首先在 AppDelegate的handleEventsForBackgroundURLSession方法里,把回调闭包传给了 SessionManager 的 backgroundCompletionHandler

  • 在下载完成回来的时候 SessionDelegate 的 urlSessionDidFinishEvents代理的调用 -> sessionDidFinishEventsForBackgroundURLSession 调用

  • 然后sessionDidFinishEventsForBackgroundURLSession 执行 -> SessionManager 的 backgroundCompletionHandler的执行

  • 最后导致 AppDelegate 的 completionHandler 的调用

无论你是使用 URLSession 的方式,还是 Alamofire 进行后台下载,但是原理还是一样的,只是 Alamofire 使用更加达到依赖下沉,网络层下沉,使用更简洁,这也是很多时候我们需要第三方框架的原因

SesssionManager的总结

SesssionManager 就是对外提供的管理者,这个管理者具备整个 Alamofire 的所有功能。

  • request 请求、download、upload、stream

  • 请求头信息设置以及多表单头信息设置

  • session 直接对外提供,方便自由处理

  • 初始化暴露,方便自由工厂构造,比如 URLSessionConfiguration 的配置模式

  • 重试以及适配请求

  • 闭包对外提供:backgroundCompletionHandler 后台下载回来监听闭包

  • 其中一个非常重要的点就是 SesssionManager 把 Session 的代理移交给了一个专门的类 : SessionDelegate

  • SessionDelegate实现了 URLSessionDelegate 、 URLSessionTaskDelegate 、 URLSessionDataDelegate 、 URLSessionDownloadDelegate 、 URLSessionStreamDelegate 等代理

  • 更多有意思的是:SessionDelegate还提供了对外的闭包,意味着所有的内部实现的代理情况,再外界都可以进行监听。当然这个所有的对外闭包分为两种情况:

  • 在原来代理回调的内部添加闭包执行。

  • 另一种是二选一,如果代理回来就不执行下层代理下发,执行对外闭包回调

总结:SesssionManager负责创建和管理 Request 对象及其底层NSURLSession

Alamofire3 (Request)

上一篇 Alamofire-后台下载 其中就介绍了关于 SesssionManager 到 SessionDelegate的分层!下面我在总结一下,然后开始一个 Alamofire 非常重要的模块 Request!

  • 首先给大家贴出一张非常熟悉的图,看懂了这张图对下面理解 Request 的帮助大大的
  • 从上面这张图可以看出,我们的对外模块是SesssionManager,他给外界的用户提供了很多的功能
  • 但是这些工作的真正实现者是由iOS、Android、前端、后台、测试实现的!
  • 其中单拿 iOS 模块的任务来说,有 首页、发现、我的、SDK、视频....模块要实现,但是我们的项目经理有可能都不知道这些到底是什么,怎么实现!所有来说如果全部交给SesssionManager来实现,显然耦合性过强,还有任务乱七八糟,没有体现一个牛逼项目分层架构的效果。所以在 iOS 任务细化和SesssionManager 之间就缺了一个小管理者,对下:他知道具体事务和调度。对上:他能和SesssionManager协调配合。那就是 Request

Request

  • 首先说明一下 Alamofire 支持编码格式,具体格式差异根据名字很容易理解,实在不能理解的可以自行百度查漏补缺

URLEncoding JSONEncoding PropertyListEncoding

  • let encodedURLRequest = try encoding.encode(originalRequest!, with: parameters) 通过这句代码还原编码请求
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 = percentEncodedQuery
            urlRequest.url = urlComponents.url
        }
    } else { // post
        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
}
  • 首先取出请求方法,根据不同的请求方法,参数编码是不同的

  • .get, .head, .delete 这三个方法是把参数直接拼接到 URL 后面

  • 其他通过请求体 (httpBody) 的形式编码

  • 因为我们的请求是通过 ASCII编码 的,所以要进行百分号编码,第一步就是对当前请求的所有路由百分号编码

  • 参数便利编码, 拼接拿出

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: "&")
}
  • 通过 ASCII 有小到大进行排序

  • queryComponents 这个方法代码过度省略。

    • 里面进行递归参数,
    • 把 key和value 取出,然后进行了百分号编码。
    • 放进元组保存,形成参数对
  • 外面讲元组加入数组中

  • map 映射 (0)=\(1)

  • 元素之间键入一个分隔符号 &

普通方法就直接拼接到URL的后面,例如POST方法就是把这些编码好的参数对放入请求体中。其中还要加入Content-Type的请求头

Request内部关系梳理

探索完 request 繁琐事务之一的参数编码之后,开始分析内部:url -> request -> task 的过程。这个过程中还建立 task以及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] = request
        if startRequestsImmediately { request.resume() }

        return request
    } catch {
        return request(originalRequest, failedWith: error)
    }
}
  • 内部创建 Requestable 来帮助下层的 DataRequest 创建 Task,也是常规说法,任务分层,架构思路更清晰
  • 绑定 task 和 request , 方便在 SessionDelegate 下发任务,task 直接检索,request 方便直接获取
  • 直接任务 request.resume() 启动
  • Request 初始化的时候利用了枚举的便利性,构造创建,相关信息保存

不就是通过SessionDelegate实现代理, 为什么这里还要有一个 DataTaskDelegate

  • 首先一定要明白 SessionDelegate 是所有 Session 的代理遵循者,任何的代理都会来到这里响应。

  • DataTaskDelegate 是我们具体繁琐任务实现者,这里面所有方法都是由 SessionDelegate 下发响应的。

  • 说白了就是整体与局部的关系

  • SessionDelegate 是事件总响应者,但是具体的事务应该交给具体的人去执行,不能因为在 SessionDelegate 能够拿到所有的响应,那么所有的代码都罗列在这里,很显然如果那样做必然会导致混乱,我们根据不同的需求,响应总代理然后根据需求的不同交给专业的人去做专业的事。耦合性大大降低,架构的分层更加明显。

  • 其中还有异步非常重要的点:delegate[task] = request 给我们的SessionDelegate 提供一个方便及时获得能力

open func urlSession(
    _ session: URLSession,
    downloadTask: URLSessionDownloadTask,
    didFinishDownloadingTo location: URL)
{
  // 省略属性闭包回调的情况
    if let delegate = self[downloadTask]?.delegate as? DownloadTaskDelegate {
        delegate.urlSession(session, downloadTask: downloadTask, didFinishDownloadingTo: location)
    }
}
  • 这段代码典型的通过我们前面建立的绑定关系,通过每次代理回调的 task 找到相应的 Request 然后因为属性关系,直接拿出 Request 的属性delegate 来处理相关事务
func urlSession(
    _ session: URLSession,
    downloadTask: URLSessionDownloadTask,
    didFinishDownloadingTo location: URL)
{
    temporaryURL = location

    guard
        let destination = destination,
        let response = downloadTask.response as? HTTPURLResponse
    else { return }

    let result = destination(location, response)
    let destinationURL = result.destinationURL
    let options = result.options

    self.destinationURL = destinationURL

    do {
        if options.contains(.removePreviousFile), FileManager.default.fileExists(atPath: destinationURL.path) {
            try FileManager.default.removeItem(at: destinationURL)
        }

        if options.contains(.createIntermediateDirectories) {
            let directory = destinationURL.deletingLastPathComponent()
            try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true)
        }
        try FileManager.default.moveItem(at: location, to: destinationURL)
    } catch {
        self.error = error
    }
}
  • 文件下载完毕转移存储地址,文件处理相关 以及error情况
  • 上面就是最典型的繁重恶心操作,必然是不需要被SessionDelegate 所需要知道的

我们从 SessionManager -> Request 这一过程明面上就是直接交付,
但是背地都是通过代理响应联通的:SessionDelegate -> 具体的Request的delegate。
看到这里,是不是感觉非常的爽!优秀的框架总是能够给你在思想方面带来很大的提升.

相关文章

网友评论

      本文标题:Alamofire 1

      本文链接:https://www.haomeiwen.com/subject/emllbrtx.html