建議先看跟著Alamofire(4.0.0)學(xué)Swift3(一)。再看本文。
在跟著Alamofire(4.0.0)學(xué)Swift3(一)中,分析到了類Request(請求)。今天主要分析下面幾個:
- 1.Response
- 2.SessionDelegate
- 3.SessionManager
Response
類和結(jié)構(gòu)體的使用
這個類給自己最大的啟發(fā)就是在Swift中類和結(jié)構(gòu)體的使用。大家對結(jié)構(gòu)體和類應(yīng)該并不陌生,但是在OC中,我們在寫代碼的時候很少用到結(jié)構(gòu)體?;蛟S是個人的習(xí)慣,就我身邊的同事大部分是這樣的。但是在Swift的中,結(jié)構(gòu)體出現(xiàn)的頻率相當(dāng)高。在文件Response.swift中沒有定義一個類,全是結(jié)構(gòu)體。
先來回顧一下Swift中結(jié)構(gòu)體和類的關(guān)系:
- 1.都可以有屬性和方法;
- 2.都有構(gòu)造器;
- 3.都支持附屬腳本;
- 4.都支持擴展;
- 5.都支持協(xié)議。
然后我們來看看他們的不同之處:
- 1.類有繼承;
- 2.結(jié)構(gòu)體有一個自動生成的逐一初始化構(gòu)造器;
- 3.在做賦值操作時,結(jié)構(gòu)體總是被拷貝(Array有特殊處理);
- 4.結(jié)構(gòu)體可以聲明靜態(tài)的屬性和方法;
- 5.從設(shè)計模式的角度來分析,類的設(shè)計更側(cè)重于對功能的封裝,而結(jié)構(gòu)體的設(shè)計更側(cè)重于對數(shù)據(jù)的封裝。
關(guān)于屬性的對比可以參考下面這張圖。

說明:類的靜態(tài)屬性表示用class修飾的變量,別和用static修飾的搞混了。用static是沒問題的
結(jié)構(gòu)體上場
很多同學(xué)可能很疑惑,什么時候用結(jié)構(gòu)體,什么時候用類。這點上可以根據(jù)類的設(shè)計更側(cè)重于對功能的封裝,而結(jié)構(gòu)體的設(shè)計更側(cè)重于對數(shù)據(jù)的封裝。為了便于代碼組織,一般在結(jié)構(gòu)體的擴展里面添加方法比如在Response.swift中:
public struct DefaultDataResponse {
public let request: URLRequest?
public let response: HTTPURLResponse?
public let data: Data?
public let error: Error?
var _metrics: AnyObject?
init(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) {
self.request = request
self.response = response
self.data = data
self.error = error
}
}
結(jié)構(gòu)體DefaultDataResponse完全滿足對數(shù)據(jù)的封裝,當(dāng)然這里用類來封裝這些數(shù)據(jù)其實也可以,但是就感覺沒有那么完美。
同樣在文件中出現(xiàn)的。DataResponse也是結(jié)構(gòu)體。然后通過擴展給結(jié)構(gòu)體添加方法,或者應(yīng)該算是屬性
extension DataResponse: CustomStringConvertible, CustomDebugStringConvertible {
/// The textual representation used when written to an output stream, which includes whether the result was a
/// success or failure.
public var description: String {
return result.debugDescription
}
/// The debug textual representation used when written to an output stream, which includes the URL request, the URL
/// response, the server data, the response serialization result and the timeline.
public var debugDescription: String {
var output: [String] = []
output.append(request != nil ? "[Request]: \(request!)" : "[Request]: nil")
output.append(response != nil ? "[Response]: \(response!)" : "[Response]: nil")
output.append("[Data]: \(data?.count ?? 0) bytes")
output.append("[Result]: \(result.debugDescription)")
output.append("[Timeline]: \(timeline.debugDescription)")
return output.joined(separator: "\n")
}
}
注意一下這兩個協(xié)議CustomStringConvertible, CustomDebugStringConvertible。
DefaultDownloadResponse,DownloadResponseDownloadResponse和上面講的一樣。
Response協(xié)議
這個協(xié)議里面其實沒什么,特別點的就是學(xué)學(xué)協(xié)議里面怎么規(guī)定要實現(xiàn)的屬性。
這里有個關(guān)鍵字mutating似乎不是很熟悉,來看看他的作用:
-
mutating:修飾方法是為了能在該方法中修改
struct或是enum的變量,在設(shè)計接口的時候,也要考慮到使用者程序的擴展性。所以要多考慮使用mutating來修飾方法。如果將Response中修飾方法的mutating去掉,編譯器會報錯說沒有實現(xiàn)protocol。如果將struct中的mutating去掉,則會報錯不能改變結(jié)構(gòu)體的成員。
protocol Response {
/// The task metrics containing the request / response statistics.
var _metrics: AnyObject? { get set }
mutating func add(_ metrics: AnyObject?)
}
通過這樣定義之后,就可以讓結(jié)構(gòu)體實現(xiàn)這個協(xié)議,然后修改結(jié)構(gòu)體里面的變量了。
讓我們倆一步一步看看。
通過擴展協(xié)議,在擴展里面判斷當(dāng)前系統(tǒng)版本環(huán)境。代碼如下:
extension Response {
mutating func add(_ metrics: AnyObject?) {
#if !os(watchOS)
guard #available(iOS 10.0, macOS 10.12, tvOS 10.0, *) else { return }
guard let metrics = metrics as? URLSessionTaskMetrics else { return }
_metrics = metrics
#endif
}
}
在結(jié)構(gòu)體的擴展里面實現(xiàn)協(xié)議。
@available(iOS 10.0, macOS 10.12, tvOS 10.0, *)
extension DefaultDataResponse: Response {
#if !os(watchOS)
/// The task metrics containing the request / response statistics.
public var metrics: URLSessionTaskMetrics? { return _metrics as? URLSessionTaskMetrics }
#endif
}
這里需要注意一下@available關(guān)鍵字。不用我講,也知道他的作用吧。
Summary
- 結(jié)構(gòu)體和類的區(qū)別
- 結(jié)構(gòu)體、類使用場景
- 擴展是個好東西,在
Alamofire中,很多地方都用到結(jié)構(gòu)體里面定義數(shù)據(jù)結(jié)構(gòu),在結(jié)構(gòu)體的擴展里面定義方法。各司其責(zé),優(yōu)化代碼組織。非常值得學(xué)習(xí)。 - 關(guān)鍵字
mutating和available的作用。
SessionDelegate
看名字剛開始還以為這是一個代理。結(jié)果這是一個類。前面說到類一般是對功能的封裝?,F(xiàn)在就來看看什么是對功能的封裝。
這個類的作用是用閉包(也就是OC中的block)來替代系統(tǒng)中的代理回調(diào)。大致分三個部分:
- 1.聲明替代系統(tǒng)代理回調(diào)方法的閉包
- 2.定義需要的屬性及方法。比如lock,sessionManager.
- 3.在類的擴展里面實現(xiàn)系統(tǒng)代理,實現(xiàn)自定義閉包代替系統(tǒng)回調(diào)代理。
聲明替代系統(tǒng)代理回調(diào)方法的閉包
// MARK: URLSessionDelegate Overrides
/// Overrides default behavior for URLSessionDelegate method `urlSession(_:didBecomeInvalidWithError:)`.
open var sessionDidBecomeInvalidWithError: ((URLSession, Error?) -> Void)?
/// Overrides default behavior for URLSessionDelegate method `urlSession(_:didReceive:completionHandler:)`.
open var sessionDidReceiveChallenge: ((URLSession, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))?
...
/// Overrides default behavior for URLSessionStreamDelegate method `urlSession(_:streamTask:didBecome:outputStream:)`.
open var streamTaskDidBecomeInputAndOutputStreams: ((URLSession, URLSessionStreamTask, InputStream, OutputStream) -> Void)?
注意這里定的閉包在下面的擴展里面講對系統(tǒng)的代理進行包裝一次,然后外面通過定義的閉包使用。
這一部分能學(xué)到的差不多就是如果對閉包進行聲明吧。順便注意下關(guān)鍵字open
定義需要的屬性及方法
這部分不算多,關(guān)鍵是定義了一個sessionManager關(guān)于這個類后面會說到。有一點需要注意就是subscript的使用方式。這里定義了一個requests字典。通過subscript來返回指定key的Request。注意一下用法。關(guān)鍵字defer的作用上一篇已經(jīng)提到過這里不再重復(fù)了。
var retrier: RequestRetrier?
weak var sessionManager: SessionManager?
private var requests: [Int: Request] = [:]
private let lock = NSLock()
/// 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
}
}
在類的擴展里面實現(xiàn)系統(tǒng)代理
這部分比較簡單,格式就是,實現(xiàn)代理,在代理方法中調(diào)用定義好的閉包,傳遞參數(shù)。通過對系統(tǒng)的代理方法包裝一層,然后外部通過定義的閉包來調(diào)用。這樣我想到了OC中的一個牛逼的第三方BlockKit。原理和這里有些類似
extension SessionDelegate: URLSessionDelegate {
/// Tells the delegate that the session has been invalidated.
///
/// - parameter session: The session object that was invalidated.
/// - parameter error: The error that caused invalidation, or nil if the invalidation was explicit.
open func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
// 調(diào)用自己的閉包
sessionDidBecomeInvalidWithError?(session, error)
}
...
open func urlSession(
_ session: URLSession,
streamTask: URLSessionStreamTask,
didBecome inputStream: InputStream,
outputStream: OutputStream)
{
streamTaskDidBecomeInputAndOutputStreams?(session, streamTask, inputStream, outputStream)
}
}
SessionManager
這個類就比較重要了,算是包含了上面介紹的大部分東西。
一共分為如下幾個部分:
- 1.調(diào)用結(jié)果枚舉定義。
Helper - 2.屬性定義。
Properties - 3.生命周期。
Lifecycle - 4.數(shù)據(jù)請求。
Data Request - 5.下載請求。
Download Request - 6.上傳請求。
Upload Request - 7.流式請求。
Stream Request - 8.重試。
Retry Request
有點多,沒關(guān)系一個一個的來。
調(diào)用結(jié)果枚舉定義
這個類似于在OC中定義成功回調(diào)和失敗回調(diào)。知識現(xiàn)在把回調(diào)放到了枚舉里面,這樣更加合理。這種方式得益于case可以傳遞參數(shù)。
public enum MultipartFormDataEncodingResult {
case success(request: UploadRequest, streamingFromDisk: Bool, streamFileURL: URL?)
case failure(Error)
}
那后面怎么使用這種回調(diào)的方式呢。
let encodingResult = MultipartFormDataEncodingResult.success(
request: self.upload(data, with: urlRequestWithContentType),
streamingFromDisk: false,
streamFileURL: nil
)
DispatchQueue.main.async { encodingCompletion?(encodingResult) }
具體會在后面用到。
屬性定義
這里需要弄明白有哪幾種屬性。計算屬性和存儲屬性,然后靜態(tài)屬性和實力屬性。最開始定義的就是所謂的靜態(tài)計算屬性。如下:
open static let `default`: SessionManager = {
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
return SessionManager(configuration: configuration)
}()
類似的還有defaultHTTPHeaders也是這樣定義的。不僅只有屬性才能用這種形式,局部變量也可以通過這種中括號方式定義。比如:
let osNameVersion: String = {
let version = ProcessInfo.processInfo.operatingSystemVersion
let versionString = "\(version.majorVersion).\(version.minorVersion).\(version.patchVersion)"
let osName: String = {
#if os(iOS)
return "iOS"
#elseif os(watchOS)
return "watchOS"
#elseif os(tvOS)
return "tvOS"
#elseif os(macOS)
return "OS X"
#elseif os(Linux)
return "Linux"
#else
return "Unknown"
#endif
}()
屬性這部分新的知識點不多。個人覺得可以看看屬性的get/set方法
open var retrier: RequestRetrier? {
get { return delegate.retrier }
set { delegate.retrier = newValue }
}
這種方式類似于OC中重寫屬性的get/set方法。最常見的將model改變和UI綁定在一起。
生命周期
差點忘了一個非常重要的知識點就,Swift中屬性在初始化之后必須有值。這點和OC不一樣。所以在init方法中做了如下事情:
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)
}
關(guān)于初始方法,Swift3中有init 和 init?,前者代碼一定會走的,后者代表可能會走的初始化方法。除了這兩個之外,還需要注意一個deinit。
-
關(guān)于閉包的實現(xiàn):為了防止循環(huán)引用用了weak。具體實現(xiàn)可以仿照下面的寫法
delegate.sessionDidFinishEventsForBackgroundURLSession = { [weak self] session in guard let strongSelf = self else { return } DispatchQueue.main.async { strongSelf.backgroundCompletionHandler?() } }
數(shù)據(jù)請求
關(guān)鍵字@discardableResult的作用,在上一篇提到過。表示這個方法可以不用接受返回值。那就是說如果沒有這個修飾,如果方法有返回值則必須接收哦。??
這部分在定義方法上可以學(xué)習(xí)一下,具體的內(nèi)容就是先定義一個最為基礎(chǔ)的方法,參數(shù)比較多但是一定要有默認值,然后后續(xù)的方法在參數(shù)上做減法,最終都是調(diào)用最為基本的方法。具體的例子如:
基本的方法定義
open func request(
_ url: URLConvertible,
method: HTTPMethod = .get,
parameters: Parameters? = nil,
encoding: ParameterEncoding = URLEncoding.default,
headers: HTTPHeaders? = nil)
-> DataRequest {
...
}
省略了部分參數(shù)的方法。
open func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
...
let request = DataRequest(session: session, requestTask: .data(originalTask, task))
...
}
在實際開發(fā)中這樣的方式還是比較常用的。那具體來看看代碼中可以學(xué)到的知識點。
1.省略外部參數(shù)用
_2.在方法中定義默認參數(shù)的方式
-
3.異常捕獲,注意這里的
do catch。try放在你覺得可以會拋出異常的地方。比如:do { let originalRequest = try urlRequest.asURLRequest() ... return request } catch { return request(failedWith: error) } 4.方法第一的層次關(guān)系,一個基本方法參數(shù)帶有默認值,同類型的在此基礎(chǔ)上減少參數(shù)。
下載請求、上傳請求、流式請求。
這部分代碼和上面的數(shù)據(jù)請求方式及代碼組織方式一樣,所以沒必要在說一次了。
重試
重試的部分比較簡單,將請求(request)傳進來。然后取出任務(wù),重新給任務(wù)傳遞所需要的參數(shù),拼裝好之后開始任務(wù)。具體代碼如下:
func retry(_ request: Request) -> Bool {
guard let originalTask = request.originalTask else { return false }
do {
let task = try originalTask.task(session: session, adapter: adapter, queue: queue)
request.delegate.task = task // resets all task delegate data
request.startTime = CFAbsoluteTimeGetCurrent()
request.endTime = nil
task.resume()
return true
} catch {
request.delegate.error = error
return false
}
}
Suammary
這部分比較多,簡單總結(jié)下,大致可以學(xué)到如下知識點。
- 1.枚舉傳遞參數(shù),如果在代碼中調(diào)用。網(wǎng)絡(luò)請求中,以后就可以直接傳遞枚舉作為返回結(jié)果,并且包含了請求結(jié)果
- 2.屬性定義及相關(guān)概念,靜態(tài)屬性,實例屬性,計算屬性,存儲屬性等,及快速返回值的寫法。
- 3.初始化方法幾種形式,注意走完初始化方法之后所有屬性必須有值
- 4.方法的參數(shù)形式,什么內(nèi)部外部參數(shù),參數(shù)默認值。
- 5.閉包使用方式,如何防止循環(huán)引用。
- 6.如何進行異常捕獲。