跟著Alamofire(4.0.0)學(xué)Swift3(二)

建議先看跟著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)鍵字mutatingavailable的作用。

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)返回指定keyRequest。注意一下用法。關(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)行異常捕獲。

參考

站在OC的基礎(chǔ)上快速理解Swift的類與結(jié)構(gòu)體

Swift靜態(tài)屬性

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容