二、詳解Alamofire(4.0.0)

Alamofire(4.0.0是基于Swift3.0語法)是Swift中最常用的網(wǎng)絡(luò)框架。

1. AFError

Swift中的枚舉比較特殊,可以在枚舉中定義方法,但是不能定義變量。

enum CompassPoint{
    case North
    case Sourth
    case East
    case West
    //枚舉中 可以定義方法
    func show(){
        print(self)
    }
}
// 定義枚舉變量
var p = CompassPoint.North
// 類型標(biāo)注之后 可以使用點(diǎn)來獲取枚舉值
var p2 : CompassPoint = .Sourth
p.show()
p2.show()

在Alamofire中的枚舉更加有個性,可以在枚舉中定義枚舉,并且枚舉的case可以傳遞參數(shù)。異常類型可以直接通過throw拋出。

public enum AFError: Error {
  
    public enum ParameterEncodingFailureReason {
        case missingURL
        case jsonEncodingFailed(error: Error)
        case propertyListEncodingFailed(error: Error)
    }

    public enum MultipartEncodingFailureReason {
        ...
    }

    public enum ResponseValidationFailureReason {
        ...
    }

    public enum ResponseSerializationFailureReason {
        ...
    }

    case invalidURL(url: URLConvertible)
    case parameterEncodingFailed(reason: ParameterEncodingFailureReason)
    case multipartEncodingFailed(reason: MultipartEncodingFailureReason)
    case responseValidationFailed(reason: ResponseValidationFailureReason)
    case responseSerializationFailed(reason: ResponseSerializationFailureReason)
}

Swift中的枚舉非常強(qiáng)大,可以對枚舉進(jìn)行擴(kuò)展,在擴(kuò)展中可以定義屬性。如下代碼所示,Alamofire中可以在AFError的擴(kuò)展中定義方法來判斷當(dāng)前的錯誤屬于哪一類錯誤。

    extension AFError {
    public var isInvalidURLError: Bool {
        if case .invalidURL = self { return true }
        return false
    }

     ......

    public var isResponseSerializationError: Bool {
        if case .responseSerializationFailed = self { return true }
        return false
    }
}

由于擴(kuò)展可以定義多個,所以便可以將一類功能歸類到同一個擴(kuò)展中,如Alamofire中定義了一個便捷屬性擴(kuò)展。

// MARK: - Convenience Properties

extension AFError {
    public var urlConvertible: URLConvertible? {
        switch self {
        case .invalidURL(let url):
            return url
        default:
            return nil
        }
    }

    ......

    public var failedStringEncoding: String.Encoding? {
        switch self {
        case .responseSerializationFailed(let reason):
            return reason.failedStringEncoding
        default:
            return nil
        }
    }
}

Alamofire中枚舉使用的最牛逼的就是為AFError內(nèi)部枚舉添加相應(yīng)擴(kuò)展。

extension AFError.ParameterEncodingFailureReason {
    var underlyingError: Error? {
        switch self {
        case .jsonEncodingFailed(let error), .propertyListEncodingFailed(let error):
            return error
        default:
            return nil
        }
    }
} 

......

然后最上層直接調(diào)用underlyingError得到error

  • Summary

1.枚舉添加訪問修飾符,并且可以實(shí)現(xiàn)協(xié)議。比如。public enum AFError: Error。這里的Error其實(shí)是一個協(xié)議public protocol Error。
2.枚舉內(nèi)部可以再定義枚舉。相當(dāng)于聲明枚舉,后面還是通過case的方式使用。并且可以傳遞參數(shù)。
3.通過擴(kuò)展給枚舉挺添加便捷屬性。
4.按照不同功能給擴(kuò)展分組。讓代碼更便于閱讀。

2. Notifications

定義的通知是一件比較簡單的事情。一般情況下,在OC中我們會直接定義一個字符串來表示某種通知。通常情況下也沒怎么把通知管理起來。

在Alamofire中定義的通知就感覺很正式了。首先是成了一個擴(kuò)展的形式,把相關(guān)的通知都寫在里面。然后使用結(jié)構(gòu)體來包裝通知。代碼如下:

extension Notification.Name {
    public struct Task {
        public static let DidResume = Notification.Name(rawValue: "org.alamofire.notification.name.task.didResume")

        public static let DidSuspend = Notification.Name(rawValue: "org.alamofire.notification.name.task.didSuspend")

        public static let DidCancel = Notification.Name(rawValue: "org.alamofire.notification.name.task.didCancel")

        public static let DidComplete = Notification.Name(rawValue: "org.alamofire.notification.name.task.didComplete")
    }
}
  • 注意定義的都是靜態(tài)常量

相當(dāng)于擴(kuò)展了系統(tǒng)通知name。把通知名稱都定義在里面。然后通過不同的結(jié)構(gòu)體定義不同用途的通知。其實(shí)在OC中也可以這樣做。只是平時很少這樣寫,這樣寫之后代碼組織就更加優(yōu)雅了。
Swift中Notification的API如下:

public struct Notification : ReferenceConvertible, Equatable, Hashable {

    public typealias ReferenceType = NSNotification

    public var name: Notification.Name

    public var object: Any?

    public var userInfo: [AnyHashable : Any]?

    public init(name: Notification.Name, object: Any? = default, userInfo: [AnyHashable : Any]? = default)

    public var hashValue: Int { get }

    public var description: String { get }

    public var debugDescription: String { get }

    public typealias Name = NSNotification.Name

    public static func ==(lhs: Notification, rhs: Notification) -> Bool
}
  • Summary :

1.通過擴(kuò)展Notification.Name來定義通知名稱。讓代碼組織更加優(yōu)雅。
2.使用結(jié)構(gòu)體來區(qū)分不同功能的通知。在結(jié)構(gòu)體下定義靜態(tài)常量定義通知名稱。

3. ParameterEncoding

詳細(xì)代碼見ParameterEncoding.swift類。

(1)枚舉繼承的類就是case所對應(yīng)的類型。比如

public enum HTTPMethod: String {
    case options = "OPTIONS"
    case get     = "GET"
    case head    = "HEAD"
    case post    = "POST"
    case put     = "PUT"
    case patch   = "PATCH"
    case delete  = "DELETE"
    case trace   = "TRACE"
    case connect = "CONNECT"
}

(2)typealias可以給一個類型取一個別名

public typealias Parameters = [String: Any]

Parameters就是一個字典,key為字符串,value可以是任意類型

4. Request

Request.swift這個類中有三個協(xié)議(RequestAdapter、RequestRetrier、TaskConvertible)、五個類、六個擴(kuò)展。

四個類及之間的繼承關(guān)系如下:

open class Request
open class DataRequest: Request
open class DownloadRequest: Request
open class UploadRequest: DataRequest
open class StreamRequest: Request

一開始先定義一套協(xié)議,和類型別名(類型別名)。如下:

public protocol RequestAdapter {
    func adapt(_ urlRequest: URLRequest) throws -> URLRequest
}

// 類似于block
public typealias RequestRetryCompletion = (_ shouldRetry: Bool, _ timeDelay: TimeInterval) -> Void

public protocol RequestRetrier {
    func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion)
}

protocol TaskConvertible {
    func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask
} 

defer關(guān)鍵字:表示在執(zhí)行完方法最后的時候調(diào)用。比如文件打開后最后需要關(guān)閉。
internal(set):表示set方法只有在內(nèi)部模塊才能訪問。get方法是都能訪問的

open internal(set) var delegate: TaskDelegate {
    get {
        taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() }
        return taskDelegate
    }
    set {
        taskDelegateLock.lock() ; defer { taskDelegateLock.unlock() }
        taskDelegate = newValue
    }
}

Swift 3.0 中方法的返回值必須有接收否則會報警告,當(dāng)然其實(shí)主要目的是為了避免開發(fā)人員忘記接收返回值的情況,但是有些情況下確實(shí)不需要使用返回值可以使用"_"接收來忽略返回值。當(dāng)然你也可以增加@discardableResult聲明,告訴編譯器此方法可以不用接收返回值。如Request.Swift類的第187行:

@discardableResult
open func authenticate(usingCredential credential: URLCredential) -> Self {
     delegate.credential = credential
     return self
}

@noescape: 用來標(biāo)記一個閉包, 用法如下func hostFunc(@noescape closure: () -> ()) -> Void
@noescape字面意思是無法逃脫. closure 被@noescape修飾, 則聲明 closure 的生命周期不能超過 hostFunc, 并且, closure不能被hostFunc中的其他閉包捕獲(也就是強(qiáng)持有)

func hostFunc(@noescape closure: () -> ()) -> Void {
    //以下編譯出錯, closure 被修飾后, 不能被其他異步線程捕獲
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
        closure()
    }
}

5. Response

5.1 結(jié)構(gòu)體

在OC中,我們在寫代碼的時候很少用到結(jié)構(gòu)體。但是在Swift的中,結(jié)構(gòu)體出現(xiàn)的頻率相當(dāng)高。在文件Response.swift中沒有定義一個類,全是結(jié)構(gòu)體。

先來回顧一下Swift中結(jié)構(gòu)體和類的關(guān)系:

a.都可以有屬性和方法;
b.都有構(gòu)造器;
c.都支持附屬腳本;
d.都支持?jǐn)U展;
e.都支持協(xié)議。

然后我們來看看他們的不同之處:

a.類有繼承;
b.結(jié)構(gòu)體有一個自動生成的逐一初始化構(gòu)造器;
c.在做賦值操作時,結(jié)構(gòu)體總是被拷貝(Array有特殊處理);
d.結(jié)構(gòu)體可以聲明靜態(tài)的屬性和方法;
e.從設(shè)計模式的角度來分析,類的設(shè)計更側(cè)重于對功能的封裝,而結(jié)構(gòu)體的設(shè)計更側(cè)重于對數(shù)據(jù)的封裝

類的設(shè)計更側(cè)重于對功能的封裝,而結(jié)構(gòu)體的設(shè)計更側(cè)重于對數(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?
    public let timeline: Timeline
    var _metrics: AnyObject?

    public init(
        request: URLRequest?,
        response: HTTPURLResponse?,
        data: Data?,
        error: Error?,
        timeline: Timeline = Timeline(),
        metrics: AnyObject? = nil)
    {
        self.request = request
        self.response = response
        self.data = data
        self.error = error
        self.timeline = timeline
    }
}

結(jié)構(gòu)體DefaultDataResponse完全滿足對數(shù)據(jù)的封裝,當(dāng)然這里用類來封裝這些數(shù)據(jù)其實(shí)也可以,但是就感覺沒有那么完美。
同樣在文件中出現(xiàn)的DataResponse也是結(jié)構(gòu)體。然后通過擴(kuò)展給結(jié)構(gòu)體添加方法,或者應(yīng)該算是屬性。

5.2 Response協(xié)議

protocol Response {
    var _metrics: AnyObject? { get set }
    mutating func add(_ metrics: AnyObject?)
}

mutating:修飾方法是為了能在該方法中修改struct 或是 enum的變量,在設(shè)計接口的時候,也要考慮到使用者程序的擴(kuò)展性。所以要多考慮使用mutating來修飾方法。如果將Response中修飾方法的mutating去掉,編譯器會報錯說沒有實(shí)現(xiàn)protocol。如果將struct中的mutating去掉,則會報錯不能改變結(jié)構(gòu)體的成員。

通過這樣定義之后,就可以讓結(jié)構(gòu)體實(shí)現(xiàn)這個協(xié)議,然后修改結(jié)構(gòu)體里面的變量了。

6. Result

這個類是一個泛型枚舉,通過對結(jié)果的封裝可以直接獲取到更加詳細(xì)的信息。來看代碼:

public enum Result<Value> {
    case success(Value)
    case failure(Error)

    // 對結(jié)果信息進(jìn)一步處理,可以馬上返回成功或者失敗。
    public var isSuccess: Bool {
        switch self {
        case .success:
            return true
        case .failure:
            return false
        }
    }

    public var isFailure: Bool {
        return !isSuccess
    }

    /// 對結(jié)果信息進(jìn)一步處理,還可以直接返回成功的值。
    public var value: Value? {
        switch self {
        case .success(let value):
            return value
        case .failure:
            return nil
        }
    }

    public var error: Error? {
        switch self {
        case .success:
            return nil
        case .failure(let error):
            return error
        }
    }
}
  • CustomStringConvertible,CustomDebugStringConvertible接口:這兩個接口都是自定義輸出的。之前如果要達(dá)到同樣的效果就重寫toString。

7. SessionDelegate

SessionDelegate看名字以為它是一個代理,其實(shí)它是一個類,前面說過類一般是對功能的封裝。這個類的作用是用閉包(也就是OC中的block)來替代系統(tǒng)中的代理回調(diào)。大致分三個部分:

(1)聲明替代系統(tǒng)代理回調(diào)方法的閉包

注意這里定的閉包在下面的擴(kuò)展里面將對系統(tǒng)的代理進(jìn)行包裝一次,然后外面通過定義的閉包使用。

2017-06-15 下午3.22.57.png

(2)定義需要的屬性及方法。比如lock,sessionManager.

    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
        }
    }

(3)在類的擴(kuò)展里面實(shí)現(xiàn)系統(tǒng)代理,實(shí)現(xiàn)自定義閉包代替系統(tǒng)回調(diào)代理。

這部分比較簡單,基本思路是,實(shí)現(xiàn)代理,在代理方法中調(diào)用定義好的閉包,傳遞參數(shù)。通過對系統(tǒng)的代理方法包裝一層,然后外部通過定義的閉包來調(diào)用。OC中的第三方BlockKit的底層原理和此類似。

2017-06-15 下午3.41.01.png

8. SessionManager

這個類非常重要,包含以下8個方面:

(1)調(diào)用結(jié)果枚舉定義。Helper

這個類似于在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) }

(2)屬性定義。Properties
屬性分為:計算屬性、存儲屬性、靜態(tài)屬性和實(shí)例屬性

靜態(tài)計算屬性:

open static let `default`: SessionManager = {
    let configuration = URLSessionConfiguration.default
    configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders

    return SessionManager(configuration: configuration)
}()

屬性的get/set方法

open var retrier: RequestRetrier? {
    get { return delegate.retrier }
    set { delegate.retrier = newValue }
}

這種方式類似于OC中重寫屬性的get/set方法。最常見的將model改變和UI綁定在一起。

(3)生命周期。Lifecycle

Swift中屬性在初始化之后必須有值。
關(guān)于初始方法,Swift3中有init 和 init?,前者代碼一定會走的,后者代表可能會走的初始化方法。

(4)數(shù)據(jù)請求。Data Request

這部分在定義方法上可以學(xué)習(xí)一下,具體的內(nèi)容就是先定義一個最為基礎(chǔ)的方法,參數(shù)比較多但是一定要有默認(rèn)值,然后后續(xù)的方法在參數(shù)上做減法,最終都是調(diào)用最為基本的方法

(5)下載請求。Download Request
(6)上傳請求。Upload Request
(7)流式請求。Stream Request
(8)重試。Retry Request

9. TaskDelegate

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

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

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