建議先看跟著Alamofire(4.0.0)學(xué)Swift3(一)仁锯。再看本文。
在跟著Alamofire(4.0.0)學(xué)Swift3(一)中翔悠,分析到了類(lèi)Request(請(qǐng)求)业崖。今天主要分析下面幾個(gè):
- 1.Response
- 2.SessionDelegate
- 3.SessionManager
Response
類(lèi)和結(jié)構(gòu)體的使用
這個(gè)類(lèi)給自己最大的啟發(fā)就是在Swift中類(lèi)和結(jié)構(gòu)體的使用。大家對(duì)結(jié)構(gòu)體和類(lèi)應(yīng)該并不陌生蓄愁,但是在OC
中双炕,我們?cè)趯?xiě)代碼的時(shí)候很少用到結(jié)構(gòu)體±缘牵或許是個(gè)人的習(xí)慣雄家,就我身邊的同事大部分是這樣的效诅。但是在Swift的中胀滚,結(jié)構(gòu)體出現(xiàn)的頻率相當(dāng)高。在文件Response.swift
中沒(méi)有定義一個(gè)類(lèi)乱投,全是結(jié)構(gòu)體咽笼。
先來(lái)回顧一下Swift中結(jié)構(gòu)體和類(lèi)的關(guān)系:
- 1.都可以有屬性和方法;
- 2.都有構(gòu)造器戚炫;
- 3.都支持附屬腳本剑刑;
- 4.都支持?jǐn)U展;
- 5.都支持協(xié)議双肤。
然后我們來(lái)看看他們的不同之處:
- 1.類(lèi)有繼承施掏;
- 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)分析蔑赘,類(lèi)的設(shè)計(jì)更側(cè)重于對(duì)功能的封裝狸驳,而結(jié)構(gòu)體的設(shè)計(jì)更側(cè)重于對(duì)數(shù)據(jù)的封裝预明。
關(guān)于屬性的對(duì)比可以參考下面這張圖。
說(shuō)明:類(lèi)的靜態(tài)屬性表示用class修飾的變量耙箍,別和用static修飾的搞混了撰糠。用static是沒(méi)問(wèn)題的
結(jié)構(gòu)體上場(chǎng)
很多同學(xué)可能很疑惑,什么時(shí)候用結(jié)構(gòu)體辩昆,什么時(shí)候用類(lèi)阅酪。這點(diǎn)上可以根據(jù)類(lèi)的設(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
完全滿(mǎn)足對(duì)數(shù)據(jù)的封裝,當(dāng)然這里用類(lèi)來(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)體和類(lèi)的區(qū)別
- 結(jié)構(gòu)體中剩、類(lèi)使用場(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è)類(lèi)。前面說(shuō)到類(lèi)一般是對(duì)功能的封裝」蹋現(xiàn)在就來(lái)看看什么是對(duì)功能的封裝焦蘑。
這個(gè)類(lèi)的作用是用閉包(也就是OC中的block)來(lái)替代系統(tǒng)中的代理回調(diào)。大致分三個(gè)部分:
- 1.聲明替代系統(tǒng)代理回調(diào)方法的閉包
- 2.定義需要的屬性及方法盒发。比如lock,sessionManager.
- 3.在類(lèi)的擴(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è)類(lèi)后面會(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
}
}
在類(lèi)的擴(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
蟹漓。原理和這里有些類(lèi)似
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è)類(lèi)就比較重要了炕横,算是包含了上面介紹的大部分東西。
一共分為如下幾個(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è)類(lèi)似于在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)
}()
類(lèi)似的還有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 }
}
這種方式類(lèi)似于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)值盟劫,同類(lèi)型的在此基礎(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)行異常捕獲渣触。