Alamofire 1

2022-07-04  本文已影响0人  f8d1cf28626a

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.shared.dataTask(with: url) { (data, response, error) in
    if error == nil {
        print("请求成功\(String(describing: response))" )
    }
}.resume()

2. URLSessionConfiguration

URLSessionConfiguration初始化有三种模式:

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)
// 初始化一个后台的模式的会话配置
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()

下载进度监控

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

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()
//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")
    }
}
网络帧

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

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
    }
}

在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()
    }
}

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

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)")
}
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的时候不是挺好的?

func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
    LGBackgroundManger.shared.manager.backgroundCompletionHandler = completionHandler
}

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)
}

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

URLSessionDelegate
URLSessionTaskDelegate
URLSessionDataDelegate
URLSessionDownloadDelegate
URLSessionStreamDelegate

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

open func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
    sessionDidFinishEventsForBackgroundURLSession?(session)
}

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

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

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

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

SesssionManager的总结

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

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

Alamofire3 (Request)

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

Request

URLEncoding JSONEncoding PropertyListEncoding

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
}
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: "&")
}

普通方法就直接拼接到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)
    }
}

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

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)
    }
}
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
    }
}

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

上一篇 下一篇

猜你喜欢

热点阅读