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

說(shuō)明:類的靜態(tài)屬性表示用class修飾的變量,別和用static修飾的搞混了。用static是沒(méi)問(wèn)題的
結(jié)構(gòu)體上場(chǎng)
很多同學(xué)可能很疑惑,什么時(shí)候用結(jié)構(gòu)體,什么時(shí)候用類。這點(diǎn)上可以根據(jù)類的設(shè)計(jì)更側(cè)重于對(duì)功能的封裝,而結(jié)構(gòu)體的設(shè)計(jì)更側(cè)重于對(duì)數(shù)據(jù)的封裝。為了便于代碼組織,一般在結(jié)構(gòu)體的擴(kuò)展里面添加方法比如在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完全滿足對(duì)數(shù)據(jù)的封裝,當(dāng)然這里用類來(lái)封裝這些數(shù)據(jù)其實(shí)也可以,但是就感覺(jué)沒(méi)有那么完美。
同樣在文件中出現(xiàn)的。DataResponse也是結(jié)構(gòu)體。然后通過(guò)擴(kuò)展給結(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")
}
}
注意一下這兩個(gè)協(xié)議CustomStringConvertible, CustomDebugStringConvertible。
DefaultDownloadResponse,DownloadResponseDownloadResponse和上面講的一樣。
Response協(xié)議
這個(gè)協(xié)議里面其實(shí)沒(méi)什么,特別點(diǎn)的就是學(xué)學(xué)協(xié)議里面怎么規(guī)定要實(shí)現(xiàn)的屬性。
這里有個(gè)關(guān)鍵字mutating似乎不是很熟悉,來(lái)看看他的作用:
-
mutating:修飾方法是為了能在該方法中修改
struct或是enum的變量,在設(shè)計(jì)接口的時(shí)候,也要考慮到使用者程序的擴(kuò)展性。所以要多考慮使用mutating來(lái)修飾方法。如果將Response中修飾方法的mutating去掉,編譯器會(huì)報(bào)錯(cuò)說(shuō)沒(méi)有實(shí)現(xiàn)protocol。如果將struct中的mutating去掉,則會(huì)報(bào)錯(cuò)不能改變結(jié)構(gòu)體的成員。
protocol Response {
/// The task metrics containing the request / response statistics.
var _metrics: AnyObject? { get set }
mutating func add(_ metrics: AnyObject?)
}
通過(guò)這樣定義之后,就可以讓結(jié)構(gòu)體實(shí)現(xiàn)這個(gè)協(xié)議,然后修改結(jié)構(gòu)體里面的變量了。
讓我們倆一步一步看看。
通過(guò)擴(kuò)展協(xié)議,在擴(kuò)展里面判斷當(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)體的擴(kuò)展里面實(shí)現(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)體、類使用場(chǎng)景
- 擴(kuò)展是個(gè)好東西,在
Alamofire中,很多地方都用到結(jié)構(gòu)體里面定義數(shù)據(jù)結(jié)構(gòu),在結(jié)構(gòu)體的擴(kuò)展里面定義方法。各司其責(zé),優(yōu)化代碼組織。非常值得學(xué)習(xí)。 - 關(guān)鍵字
mutating和available的作用。
SessionDelegate
看名字剛開(kāi)始還以為這是一個(gè)代理。結(jié)果這是一個(gè)類。前面說(shuō)到類一般是對(duì)功能的封裝。現(xiàn)在就來(lái)看看什么是對(duì)功能的封裝。
這個(gè)類的作用是用閉包(也就是OC中的block)來(lái)替代系統(tǒng)中的代理回調(diào)。大致分三個(gè)部分:
- 1.聲明替代系統(tǒng)代理回調(diào)方法的閉包
- 2.定義需要的屬性及方法。比如lock,sessionManager.
- 3.在類的擴(kuò)展里面實(shí)現(xiàn)系統(tǒng)代理,實(shí)現(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)?
注意這里定的閉包在下面的擴(kuò)展里面講對(duì)系統(tǒng)的代理進(jìn)行包裝一次,然后外面通過(guò)定義的閉包使用。
這一部分能學(xué)到的差不多就是如果對(duì)閉包進(jìn)行聲明吧。順便注意下關(guān)鍵字open
定義需要的屬性及方法
這部分不算多,關(guān)鍵是定義了一個(gè)sessionManager關(guān)于這個(gè)類后面會(huì)說(shuō)到。有一點(diǎn)需要注意就是subscript的使用方式。這里定義了一個(gè)requests字典。通過(guò)subscript來(lái)返回指定key的Request。注意一下用法。關(guān)鍵字defer的作用上一篇已經(jīng)提到過(guò)這里不再重復(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
}
}
在類的擴(kuò)展里面實(shí)現(xiàn)系統(tǒng)代理
這部分比較簡(jiǎn)單,格式就是,實(shí)現(xiàn)代理,在代理方法中調(diào)用定義好的閉包,傳遞參數(shù)。通過(guò)對(duì)系統(tǒng)的代理方法包裝一層,然后外部通過(guò)定義的閉包來(lái)調(diào)用。這樣我想到了OC中的一個(gè)牛逼的第三方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
這個(gè)類就比較重要了,算是包含了上面介紹的大部分東西。
一共分為如下幾個(gè)部分:
- 1.調(diào)用結(jié)果枚舉定義。
Helper - 2.屬性定義。
Properties - 3.生命周期。
Lifecycle - 4.數(shù)據(jù)請(qǐng)求。
Data Request - 5.下載請(qǐng)求。
Download Request - 6.上傳請(qǐng)求。
Upload Request - 7.流式請(qǐng)求。
Stream Request - 8.重試。
Retry Request
有點(diǎn)多,沒(méi)關(guān)系一個(gè)一個(gè)的來(lái)。
調(diào)用結(jié)果枚舉定義
這個(gè)類似于在OC中定義成功回調(diào)和失敗回調(diào)。知識(shí)現(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) }
具體會(huì)在后面用到。
屬性定義
這里需要弄明白有哪幾種屬性。計(jì)算屬性和存儲(chǔ)屬性,然后靜態(tài)屬性和實(shí)力屬性。最開(kāi)始定義的就是所謂的靜態(tài)計(jì)算屬性。如下:
open static let `default`: SessionManager = {
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
return SessionManager(configuration: configuration)
}()
類似的還有defaultHTTPHeaders也是這樣定義的。不僅只有屬性才能用這種形式,局部變量也可以通過(guò)這種中括號(hào)方式定義。比如:
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
}()
屬性這部分新的知識(shí)點(diǎn)不多。個(gè)人覺(jué)得可以看看屬性的get/set方法
open var retrier: RequestRetrier? {
get { return delegate.retrier }
set { delegate.retrier = newValue }
}
這種方式類似于OC中重寫(xiě)屬性的get/set方法。最常見(jiàn)的將model改變和UI綁定在一起。
生命周期
差點(diǎn)忘了一個(gè)非常重要的知識(shí)點(diǎn)就,Swift中屬性在初始化之后必須有值。這點(diǎn)和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?,前者代碼一定會(huì)走的,后者代表可能會(huì)走的初始化方法。除了這兩個(gè)之外,還需要注意一個(gè)deinit。
-
關(guān)于閉包的實(shí)現(xiàn):為了防止循環(huán)引用用了weak。具體實(shí)現(xiàn)可以仿照下面的寫(xiě)法
delegate.sessionDidFinishEventsForBackgroundURLSession = { [weak self] session in guard let strongSelf = self else { return } DispatchQueue.main.async { strongSelf.backgroundCompletionHandler?() } }
數(shù)據(jù)請(qǐng)求
關(guān)鍵字@discardableResult的作用,在上一篇提到過(guò)。表示這個(gè)方法可以不用接受返回值。那就是說(shuō)如果沒(méi)有這個(gè)修飾,如果方法有返回值則必須接收哦。??
這部分在定義方法上可以學(xué)習(xí)一下,具體的內(nèi)容就是先定義一個(gè)最為基礎(chǔ)的方法,參數(shù)比較多但是一定要有默認(rèn)值,然后后續(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))
...
}
在實(shí)際開(kāi)發(fā)中這樣的方式還是比較常用的。那具體來(lái)看看代碼中可以學(xué)到的知識(shí)點(diǎn)。
1.省略外部參數(shù)用
_2.在方法中定義默認(rèn)參數(shù)的方式
-
3.異常捕獲,注意這里的
do catch。try放在你覺(jué)得可以會(huì)拋出異常的地方。比如:do { let originalRequest = try urlRequest.asURLRequest() ... return request } catch { return request(failedWith: error) } 4.方法第一的層次關(guān)系,一個(gè)基本方法參數(shù)帶有默認(rèn)值,同類型的在此基礎(chǔ)上減少參數(shù)。
下載請(qǐng)求、上傳請(qǐng)求、流式請(qǐng)求。
這部分代碼和上面的數(shù)據(jù)請(qǐng)求方式及代碼組織方式一樣,所以沒(méi)必要在說(shuō)一次了。
重試
重試的部分比較簡(jiǎn)單,將請(qǐng)求(request)傳進(jìn)來(lái)。然后取出任務(wù),重新給任務(wù)傳遞所需要的參數(shù),拼裝好之后開(kāi)始任務(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ǎn)單總結(jié)下,大致可以學(xué)到如下知識(shí)點(diǎn)。
- 1.枚舉傳遞參數(shù),如果在代碼中調(diào)用。網(wǎng)絡(luò)請(qǐng)求中,以后就可以直接傳遞枚舉作為返回結(jié)果,并且包含了請(qǐng)求結(jié)果
- 2.屬性定義及相關(guān)概念,靜態(tài)屬性,實(shí)例屬性,計(jì)算屬性,存儲(chǔ)屬性等,及快速返回值的寫(xiě)法。
- 3.初始化方法幾種形式,注意走完初始化方法之后所有屬性必須有值
- 4.方法的參數(shù)形式,什么內(nèi)部外部參數(shù),參數(shù)默認(rèn)值。
- 5.閉包使用方式,如何防止循環(huán)引用。
- 6.如何進(jìn)行異常捕獲。