AlamoFire 分析

文章目录
  1. 1. Foundation 中提供的网络相关接口
    1. 1.1. 后台 session下载流程&注意事项
  2. 2. 使用 AlamoFire
    1. 2.1. 处理后台下载
  3. 3. request
    1. 3.1. 编码

分析源码:

  1. 这个第三方库,他设计的目标是什么?
  2. 需要哪些基础知识
  3. 整体结构是什么样的
  4. 由外而内逐层分析层次结构
  5. 每个层次结构的意图是什么,为了实现这个意图他使用了什么方式,这么做有什么优点
  6. 为了让用户使用方便,实现了什么样的接口,为了实现这样的接口底层又是如何实践的呢?

为什么要分析 AlamoFire

  • 分析它的接口设计
  • 分析他是如何封装 URL Loading System 的
  • 分析网络层次思考数据传输过程

AlamoFire 都做了什么?

  • Chainable Request / Response Methods
  • URL / JSON Parameter Encoding
  • Upload File / Data / Stream / MultipartFormData
  • Download File using Request or Resume Data
  • Authentication with URLCredential
  • HTTP Response Validation
  • Upload and Download Progress Closures with Progress
  • cURL Command Output
  • Dynamically Adapt and Retry Requests
  • TLS Certificate and Public Key Pinning
  • Network Reachability

Foundation 中提供的网络相关接口

AlamoFire 是一个网络请求库,底层封装的事 Apple 提供的 URL Loading System

后台 session下载流程&注意事项

后台 sessionConfiguration 下载文件保存 到本地沙盒

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 开启后台 请求 task
let configuration = URLSessionConfiguration.background(withIdentifier: "com.czw.backgroundDownload")
let session = URLSession.init(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main)
session.downloadTask(with: URL(string: urlDownloadStr)!).resume()

extension ViewController: URLSessionDownloadDelegate {
// 下载完成后代理回调方法,将文件移动到沙盒指定位置
func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) {
print("下载完成 - \(location)")
let locationPath = location.path
let documnets = NSHomeDirectory() + "/Documents/xxxx" + ".zip"
print("移动地址:\(documnets)")
let fileManager = FileManager.default
try! fileManager.moveItem(atPath: locationPath, toPath: documnets)
}

// http 分段传输,会不断调用这个方法直到,全部下载完成
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")
}
}

后台请求,需要申请后台权限,这样 app 进入 background 的时候才可以继续下载

1
2
3
4
5
6
7
//用于保存后台下载的completionHandler
var backgroundSessionCompletionHandler: (() -> Void)?

func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) {
print("background: \(identifier)")
self.backgroundSessionCompletionHandler = completionHandler
}

使用 AlamoFire

1
2
3
4
Alamofire.request(urlString)
.responseJSON { (data) in
print(data)
}

跟上面对比[原生的方法(#code1), 思考 AlamoFire 都封装了什么,如何实现链式调用的,内部如何使用 oop,pop 的, 思考 AlamoFire 接口设计

下面这些都是怎么实现的?

  • 链式语法
  • 直接传 string
  • 封装调用 session,resume
  • Response 回调的返回的是 json 数据
  • Alamofire 模块导入调用

调用的第一层:
Alamofire.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//Alamofire Model
public func request(
_ url: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default,
headers: HTTPHeaders? = nil)
-> DataRequest {

return SessionManager.default.request(
url,
method: method,
parameters: parameters,
encoding: encoding,
headers: headers
)
}

SessionManager.swift

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//SessionManager

/// A default instance of `SessionManager`, used by top-level Alamofire request methods, and suitable for use
/// directly for any ad hoc requests.
public static let `default`: SessionManager = {
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
return SessionManager(configuration: configuration)
}()

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

public init?(
session: URLSession,
delegate: SessionDelegate,
serverTrustPolicyManager: ServerTrustPolicyManager? = nil) {

guard delegate === session.delegate else { return nil }

self.delegate = delegate
self.session = session

commonInit(serverTrustPolicyManager: serverTrustPolicyManager)
}

SessionDelegate() 代理移交 SessionDelegate 是个 class, 处理 URLSession 的事件回调代理方法。

1
2
3
extension SessionDelegate: URLSessionDataDelegate {
extension SessionDelegate: URLSessionTaskDelegate {
extension SessionDelegate: URLSessionStreamDelegate {

为什么 Manager 不处理 URLSession 的代理回调方法,要搞一个 SessionDelegate 处理,因为 Manager 是一个中介者,用于管理各个 class 之间的联系,处理回调的那些方法应该归属于 xxxClass 来处理

AlamoFire 基本核心框架

使用 Manager 主要作用是把代码模块之间的 频繁调用关系化简。划分业务层,管理层
Manager 统一管理调度
各个模块处理完成后,回调给 Manager

处理后台下载

request

编码

提供的3种编码方式

  • url
  • json
  • propertyList

url 是ASCII码编码的,他不是 Unicode,ASCII 码中的外文无法识别,所以要百分号编码——将 ACSII -> Unicode 这样可以被识别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()

guard let parameters = parameters else { return urlRequest }

// header
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 {
if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")
}

// body
urlRequest.httpBody = query(parameters).data(using: .utf8, allowLossyConversion: false)
}

return urlRequest
}

p.s. 传输组的时候一定要传 json 字符串!

iOS 10: Background Session won’t inform when downloads have failed #1920

URL Loading System