Alamofire 4.5.0源码解析-SessionManag
Alamofie中的SessionManager,SessionDelegate和TaskDelegate是网络请求的中枢,SessionManager持有SessionDelegate对象,SessionDelegate对象实现URLSessionDelegate,URLSessionTaskDelegate,URLSessionDataDelegate和URLSessionStreamDelegate协议.
SessionManager
�SessionManger初始化,默认将类型为SessionDelegate的对象赋值:
<pre><code>` 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)
}
/// Creates an instance with the specified `session`, `delegate` and `serverTrustPolicyManager`.
///
/// - parameter session: The URL session.
/// - parameter delegate: The delegate of the URL session. Must equal the URL session's delegate.
/// - parameter serverTrustPolicyManager: The server trust policy manager to use for evaluating all server trust
/// challenges. `nil` by default.
///
/// - returns: The new `SessionManager` instance if the URL session's delegate matches; `nil` otherwise.
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)
}
private func commonInit(serverTrustPolicyManager: ServerTrustPolicyManager?) {
session.serverTrustPolicyManager = serverTrustPolicyManager
delegate.sessionManager = self
delegate.sessionDidFinishEventsForBackgroundURLSession = { [weak self] session in
guard let strongSelf = self else { return }
DispatchQueue.main.async { strongSelf.backgroundCompletionHandler?() }
}
}
`</code></pre>
队列命名相对AFNetWorking有所改变:
<pre><code>let queue = DispatchQueue(label: "org.alamofire.session-manager." + UUID().uuidString)
</code></pre>
上传数据,作者通过创建目录的方式,确保数据不会被两个线程同时操作:
<pre><code>` open func upload(
multipartFormData: @escaping (MultipartFormData) -> Void,
usingThreshold encodingMemoryThreshold: UInt64 = SessionManager.multipartFormDataEncodingMemoryThreshold,
with urlRequest: URLRequestConvertible,
encodingCompletion: ((MultipartFormDataEncodingResult) -> Void)?)
{
DispatchQueue.global(qos: .utility).async {
let formData = MultipartFormData()
multipartFormData(formData)
var tempFileURL: URL?
do {
var urlRequestWithContentType = try urlRequest.asURLRequest()
urlRequestWithContentType.setValue(formData.contentType, forHTTPHeaderField: "Content-Type")
let isBackgroundSession = self.session.configuration.identifier != nil
if formData.contentLength < encodingMemoryThreshold && !isBackgroundSession {
let data = try formData.encode()
let encodingResult = MultipartFormDataEncodingResult.success(
request: self.upload(data, with: urlRequestWithContentType),
streamingFromDisk: false,
streamFileURL: nil
)
DispatchQueue.main.async { encodingCompletion?(encodingResult) }
} else {
let fileManager = FileManager.default
let tempDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory())
let directoryURL = tempDirectoryURL.appendingPathComponent("org.alamofire.manager/multipart.form.data")
let fileName = UUID().uuidString
let fileURL = directoryURL.appendingPathComponent(fileName)
tempFileURL = fileURL
var directoryError: Error?
// Create directory inside serial queue to ensure two threads don't do this in parallel
self.queue.sync {
do {
try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil)
} catch {
directoryError = error
}
}
if let directoryError = directoryError { throw directoryError }
try formData.writeEncodedData(to: fileURL)
let upload = self.upload(fileURL, with: urlRequestWithContentType)
// Cleanup the temp file once the upload is complete
upload.delegate.queue.addOperation {
do {
try FileManager.default.removeItem(at: fileURL)
} catch {
// No-op
}
}
DispatchQueue.main.async {
let encodingResult = MultipartFormDataEncodingResult.success(
request: upload,
streamingFromDisk: true,
streamFileURL: fileURL
)
encodingCompletion?(encodingResult)
}
}
} catch {
// Cleanup the temp file in the event that the multipart form data encoding failed
if let tempFileURL = tempFileURL {
do {
try FileManager.default.removeItem(at: tempFileURL)
} catch {
// No-op
}
}
DispatchQueue.main.async { encodingCompletion?(.failure(error)) }
}
}
}`</code></pre>
SessionDelegate & TaskDelegate
SessionDelegate对象实现URLSessionDelegate,URLSessionTaskDelegate,URLSessionDataDelegate和URLSessionStreamDelegate协议.
<pre><code>open func urlSession( _ session: URLSession, downloadTask: URLSessionDownloadTask, didWriteData bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64) { if let downloadTaskDidWriteData = downloadTaskDidWriteData { downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite) } else if let delegate = self[downloadTask]?.delegate as? DownloadTaskDelegate { delegate.urlSession( session, downloadTask: downloadTask, didWriteData: bytesWritten, totalBytesWritten: totalBytesWritten, totalBytesExpectedToWrite: totalBytesExpectedToWrite ) } }
</code></pre>
SessionDelegate通过自定义下标,取出TaskDelegate的代码比较有参考意义:
<pre><code>/// Access the task delegate for the specified task in a thread-safe manner. open subscript(task: URLSessionTask) -> Request? { get { lock.lock() ; defer { lock.unlock() } return requests[task.taskIdentifier] } set { lock.lock() ; defer { lock.unlock() } requests[task.taskIdentifier] = newValue } }
</code></pre>
TaskDelegate的子类DownloadTaskDelegate关于下载进度方法的回调实现:
<pre><code>` func urlSession(
_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didWriteData bytesWritten: Int64,
totalBytesWritten: Int64,
totalBytesExpectedToWrite: Int64)
{
if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
if let downloadTaskDidWriteData = downloadTaskDidWriteData {
downloadTaskDidWriteData(
session,
downloadTask,
bytesWritten,
totalBytesWritten,
totalBytesExpectedToWrite
)
} else {
progress.totalUnitCount = totalBytesExpectedToWrite
progress.completedUnitCount = totalBytesWritten
if let progressHandler = progressHandler {
progressHandler.queue.async { progressHandler.closure(self.progress) }
}
}
}`</code></pre>
DispatchQueue
Alamofire对DispatchQueue调用队列设置参数类型的进行简单的扩展:
<pre><code>`import Dispatch
import Foundation
extension DispatchQueue {
static var userInteractive: DispatchQueue { return DispatchQueue.global(qos: .userInteractive) }
static var userInitiated: DispatchQueue { return DispatchQueue.global(qos: .userInitiated) }
static var utility: DispatchQueue { return DispatchQueue.global(qos: .utility) }
static var background: DispatchQueue { return DispatchQueue.global(qos: .background) }
func after(_ delay: TimeInterval, execute closure: @escaping () -> Void) {
asyncAfter(deadline: .now() + delay, execute: closure)
}
}`</code></pre>
MultipartFormData
MultipartFormData中关于boundaryType的判断和封装方代码:
<pre><code>`open class MultipartFormData {
// MARK: - Helper Types
struct EncodingCharacters {
static let crlf = "\r\n"
}
struct BoundaryGenerator {
enum BoundaryType {
case initial, encapsulated, final
}
static func randomBoundary() -> String {
return String(format: "alamofire.boundary.%08x%08x", arc4random(), arc4random())
}
static func boundaryData(forBoundaryType boundaryType: BoundaryType, boundary: String) -> Data {
let boundaryText: String
switch boundaryType {
case .initial:
boundaryText = "--\(boundary)\(EncodingCharacters.crlf)"
case .encapsulated:
boundaryText = "\(EncodingCharacters.crlf)--\(boundary)\(EncodingCharacters.crlf)"
case .final:
boundaryText = "\(EncodingCharacters.crlf)--\(boundary)--\(EncodingCharacters.crlf)"
}
return boundaryText.data(using: String.Encoding.utf8, allowLossyConversion: false)!
}
}`</code></pre>
当看到作者append方法中的注释的时候,瞬间对作者肃然起敬,大家自己感受下:
<pre><code>` public func append(_ fileURL: URL, withName name: String, fileName: String, mimeType: String) {
let headers = contentHeaders(withName: name, fileName: fileName, mimeType: mimeType)
//============================================================
// Check 1 - is file URL?
//============================================================
guard fileURL.isFileURL else {
setBodyPartError(withReason: .bodyPartURLInvalid(url: fileURL))
return
}
//============================================================
// Check 2 - is file URL reachable?
//============================================================
do {
let isReachable = try fileURL.checkPromisedItemIsReachable()
guard isReachable else {
setBodyPartError(withReason: .bodyPartFileNotReachable(at: fileURL))
return
}
} catch {
setBodyPartError(withReason: .bodyPartFileNotReachableWithError(atURL: fileURL, error: error))
return
}
//============================================================
// Check 3 - is file URL a directory?
//============================================================
var isDirectory: ObjCBool = false
let path = fileURL.path
guard FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory) && !isDirectory.boolValue else {
setBodyPartError(withReason: .bodyPartFileIsDirectory(at: fileURL))
return
}
//============================================================
// Check 4 - can the file size be extracted?
//============================================================
let bodyContentLength: UInt64
do {
guard let fileSize = try FileManager.default.attributesOfItem(atPath: path)[.size] as? NSNumber else {
setBodyPartError(withReason: .bodyPartFileSizeNotAvailable(at: fileURL))
return
}
bodyContentLength = fileSize.uint64Value
}
catch {
setBodyPartError(withReason: .bodyPartFileSizeQueryFailedWithError(forURL: fileURL, error: error))
return
}
//============================================================
// Check 5 - can a stream be created from file URL?
//============================================================
guard let stream = InputStream(url: fileURL) else {
setBodyPartError(withReason: .bodyPartInputStreamCreationFailed(for: fileURL))
return
}
append(stream, withLength: bodyContentLength, headers: headers)
}`</code></pre>
鉴于本人Swift水平有限,如有不当,欢迎大家多多指正~