【Alamofire源碼解析】07 - SessionManager

SessionManager的作用是用于創(chuàng)建各種請求女仰。

1. MultipartFormDataEncodingResult輔助類型

MultipartFormDataEncodingResult用于表示編碼多表單的結(jié)果,是一個枚舉,并關(guān)聯(lián)了一些相關(guān)信息硬耍。

public enum MultipartFormDataEncodingResult {
    case success(request: UploadRequest, streamingFromDisk: Bool, streamFileURL: URL?)
    case failure(Error)
}

2. 屬性

// 默認的SessionManager
open static let `default`: SessionManager = {
    let configuration = URLSessionConfiguration.default
    configuration.httpAdditionalHeaders = SessionManager.defaultHTTPHeaders
    
    return SessionManager(configuration: configuration)
}()

// 默認請求頭
open static let defaultHTTPHeaders: HTTPHeaders = {
}()

// 多表單編碼時使用的內(nèi)存臨界值, `10_000_000`下劃線使得讀者更容易讀
open static let multipartFormDataEncodingMemoryThreshold: UInt64 = 10_000_000

// 底層的URLSession
open let session: URLSession

// 底層URLSession的代理,session的所有代理都由它來處理
open let delegate: SessionDelegate

// 是否馬上開啟請求米罚,默認是true
open var startRequestsImmediately: Bool = true

// 請求適配器
open var adapter: RequestAdapter?

// 請求重試器浮定,由代理提供(如果想要請求失敗的時候重試相满,我們需要定義一個請求重試器)
open var retrier: RequestRetrier? {
    get { return delegate.retrier }
    set { delegate.retrier = newValue }
}

// 默認是nil层亿。如果設(shè)置了這個handler,SessionDelegate的 `sessionDidFinishEventsForBackgroundURLSession`會自動執(zhí)行這個handler立美;
// 如果想要在這個handler執(zhí)行前去處理自己的event匿又,
// 我們要重寫 `sessionDidFinishEventsForBackgroundURLSession`,然后手動調(diào)用這個handler
open var backgroundCompletionHandler: (() -> Void)?

// 執(zhí)行隊列
let queue = DispatchQueue(label: "org.alamofire.session-manager." + UUID().uuidString)

3. 初始化

// 傳入`configuration`和`delegate`悯辙,創(chuàng)建SessionManager
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)
}

// 傳入`session`和`delegate`琳省,創(chuàng)建SessionManager(注意:session的delegate和傳入的delegate必須是同一個)
public init?(
    session: URLSession,
    delegate: SessionDelegate,
    serverTrustPolicyManager: ServerTrustPolicyManager? = nil)
{
    guard delegate === session.delegate else { return nil }

    self.delegate = delegate
    self.session = session

    commonInit(serverTrustPolicyManager: serverTrustPolicyManager)
}

// 上面兩個初始化器相同的一些初始化
private func commonInit(serverTrustPolicyManager: ServerTrustPolicyManager?) {
    session.serverTrustPolicyManager = serverTrustPolicyManager

    delegate.sessionManager = self

    // 把`backgroundCompletionHandler`傳給`delegate.sessionDidFinishEventsForBackgroundURLSession`
    // `[weak self] session`這里的`session`在closure里面沒有用到迎吵,為了代碼簡潔躲撰,其實應(yīng)該用`_`代替的
    delegate.sessionDidFinishEventsForBackgroundURLSession = { [weak self] session in
        guard let strongSelf = self else { return }
        DispatchQueue.main.async { strongSelf.backgroundCompletionHandler?() }
    }
}

// 被回收了之后,使session失效
deinit {
    session.invalidateAndCancel()
}

4. 數(shù)據(jù)請求

// 提供`URLConvertible`击费,創(chuàng)建數(shù)據(jù)請求
@discardableResult
open func request(
    _ url: URLConvertible,
    method: HTTPMethod = .get,
    parameters: Parameters? = nil,
    encoding: ParameterEncoding = URLEncoding.default,
    headers: HTTPHeaders? = nil)
    -> DataRequest
{
    var originalRequest: URLRequest?

    do {
        originalRequest = try URLRequest(url: url, method: method, headers: headers)
        // 使用指定的`encoding`拢蛋,把參數(shù)編碼到請求上
        let encodedURLRequest = try encoding.encode(originalRequest!, with: parameters)
        return request(encodedURLRequest)
    } catch {
        return request(originalRequest, failedWith: error)
    }
}

// 提供`URLRequestConvertible`,創(chuàng)建數(shù)據(jù)請求
@discardableResult
open func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
    var originalRequest: URLRequest?

    do {
        originalRequest = try urlRequest.asURLRequest()
        let originalTask = DataRequest.Requestable(urlRequest: originalRequest!)
        
        // 這里其實是用傳進來的urlRequest蔫巩,創(chuàng)建了一個dataTask
        let task = try originalTask.task(session: session, adapter: adapter, queue: queue)
        // 創(chuàng)建DataRequest
        let request = DataRequest(session: session, requestTask: .data(originalTask, task))
        
        // 因為delegate可能要處理多個請求谆棱,作者使用Swift的下標特性把請求記錄在delegate的requests字典
        delegate[task] = request

        if startRequestsImmediately { request.resume() }

        return request
    } catch {
        return request(originalRequest, failedWith: error)
    }
}

// 提供`URLRequest`和`error`,創(chuàng)建數(shù)據(jù)請求
// 解決url或者參數(shù)處理過程可能會跑出錯誤的情況
private func request(_ urlRequest: URLRequest?, failedWith error: Error) -> DataRequest {
    // 首先聲明一個關(guān)聯(lián)值為空的data類型的RequestTask
    var requestTask: Request.RequestTask = .data(nil, nil)

    // 如果urlRequest不為空圆仔,創(chuàng)建一個data類型的RequestTask
    if let urlRequest = urlRequest {
        let originalTask = DataRequest.Requestable(urlRequest: urlRequest)
        requestTask = .data(originalTask, nil)
    }

    let underlyingError = error.underlyingAdaptError ?? error
    let request = DataRequest(session: session, requestTask: requestTask, error: underlyingError)

    // 如果自定了以重試器垃瞧,并且error是適配過程中出現(xiàn)的error,那么允許重試
    if let retrier = retrier, error is AdaptError {
        allowRetrier(retrier, toRetry: request, with: underlyingError)
    } else {
        if startRequestsImmediately { request.resume() }
    }

    return request
}

關(guān)于Swift的下標特性坪郭,可以訪問【Swift 3.1】12 - 下標 (Subscripts)个从。

5. 下載請求 & 上傳請求 & Stream請求

這三個請求與數(shù)據(jù)請求的代碼大同小異,大家可以自己深入看看歪沃。如果有不懂的嗦锐,歡迎留言,我看到了會盡力解答沪曙。

6. 重試請求

// 重試請求是否成功
func retry(_ request: Request) -> Bool {
    // 如果請求沒有任務(wù)奕污,則重試失敗
    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.retryCount += 1
        request.startTime = CFAbsoluteTimeGetCurrent()
        request.endTime = nil

        task.resume()

        return true
    } catch {
        request.delegate.error = error.underlyingAdaptError ?? error
        return false
    }
}

// 實現(xiàn)重試
private func allowRetrier(_ retrier: RequestRetrier, toRetry request: Request, with error: Error) {
    // 需要重試的請求可能很多,使用異步隊列
    DispatchQueue.utility.async { [weak self] in
        guard let strongSelf = self else { return }

        retrier.should(strongSelf, retry: request, with: error) { shouldRetry, timeDelay in
            guard let strongSelf = self else { return }

            guard shouldRetry else {
                if strongSelf.startRequestsImmediately { request.resume() }
                return
            }

            DispatchQueue.utility.after(timeDelay) {
                guard let strongSelf = self else { return }
                
                // 是否重試成功
                let retrySucceeded = strongSelf.retry(request)
                
                // 如果重試成功液走,更新delegate的requests字典
                if retrySucceeded, let task = request.task {
                    strongSelf.delegate[task] = request
                } else {
                    if strongSelf.startRequestsImmediately { request.resume() }
                }
            }
        }
    }
}

有任何問題碳默,歡迎大家留言!

歡迎加入我管理的Swift開發(fā)群:536353151缘眶,本群只討論Swift相關(guān)內(nèi)容腻窒。

原創(chuàng)文章,轉(zhuǎn)載請注明出處磅崭。謝謝儿子!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市砸喻,隨后出現(xiàn)的幾起案子柔逼,更是在濱河造成了極大的恐慌蒋譬,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,406評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件愉适,死亡現(xiàn)場離奇詭異犯助,居然都是意外死亡,警方通過查閱死者的電腦和手機维咸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評論 3 398
  • 文/潘曉璐 我一進店門剂买,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人癌蓖,你說我怎么就攤上這事瞬哼。” “怎么了租副?”我有些...
    開封第一講書人閱讀 167,815評論 0 360
  • 文/不壞的土叔 我叫張陵坐慰,是天一觀的道長。 經(jīng)常有香客問我用僧,道長结胀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,537評論 1 296
  • 正文 為了忘掉前任责循,我火速辦了婚禮糟港,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘院仿。我一直安慰自己秸抚,他們只是感情好,可當我...
    茶點故事閱讀 68,536評論 6 397
  • 文/花漫 我一把揭開白布意蛀。 她就那樣靜靜地躺著耸别,像睡著了一般。 火紅的嫁衣襯著肌膚如雪县钥。 梳的紋絲不亂的頭發(fā)上秀姐,一...
    開封第一講書人閱讀 52,184評論 1 308
  • 那天,我揣著相機與錄音若贮,去河邊找鬼省有。 笑死,一個胖子當著我的面吹牛谴麦,可吹牛的內(nèi)容都是我干的蠢沿。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼匾效,長吁一口氣:“原來是場噩夢啊……” “哼舷蟀!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,668評論 0 276
  • 序言:老撾萬榮一對情侶失蹤野宜,失蹤者是張志新(化名)和其女友劉穎扫步,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體匈子,經(jīng)...
    沈念sama閱讀 46,212評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡河胎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,299評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了虎敦。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片游岳。...
    茶點故事閱讀 40,438評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖其徙,靈堂內(nèi)的尸體忽然破棺而出胚迫,到底是詐尸還是另有隱情,我是刑警寧澤擂橘,帶...
    沈念sama閱讀 36,128評論 5 349
  • 正文 年R本政府宣布晌区,位于F島的核電站摩骨,受9級特大地震影響通贞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜恼五,卻給世界環(huán)境...
    茶點故事閱讀 41,807評論 3 333
  • 文/蒙蒙 一昌罩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧灾馒,春花似錦茎用、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至容达,卻和暖如春古涧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背花盐。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評論 1 272
  • 我被黑心中介騙來泰國打工羡滑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人算芯。 一個月前我還...
    沈念sama閱讀 48,827評論 3 376
  • 正文 我出身青樓柒昏,卻偏偏與公主長得像,于是被迫代替她去往敵國和親熙揍。 傳聞我的和親對象是個殘疾皇子职祷,可洞房花燭夜當晚...
    茶點故事閱讀 45,446評論 2 359

推薦閱讀更多精彩內(nèi)容