【Alamofire源码解析】07 - SessionManager

SessionManager的作用是用于创建各种请求。

1. MultipartFormDataEncodingResult辅助类型

MultipartFormDataEncodingResult用于表示编码多表单的结果,是一个枚举,并关联了一些相关信息。

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 = {
}()

// 多表单编码时使用的内存临界值, `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。如果设置了这个handler,SessionDelegate的 `sessionDidFinishEventsForBackgroundURLSession`会自动执行这个handler;
// 如果想要在这个handler执行前去处理自己的event,
// 我们要重写 `sessionDidFinishEventsForBackgroundURLSession`,然后手动调用这个handler
open var backgroundCompletionHandler: (() -> Void)?

// 执行队列
let queue = DispatchQueue(label: "org.alamofire.session-manager." + UUID().uuidString)

3. 初始化

// 传入`configuration`和`delegate`,创建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`,创建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里面没有用到,为了代码简洁,其实应该用`_`代替的
    delegate.sessionDidFinishEventsForBackgroundURLSession = { [weak self] session in
        guard let strongSelf = self else { return }
        DispatchQueue.main.async { strongSelf.backgroundCompletionHandler?() }
    }
}

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

4. 数据请求

// 提供`URLConvertible`,创建数据请求
@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`,把参数编码到请求上
        let encodedURLRequest = try encoding.encode(originalRequest!, with: parameters)
        return request(encodedURLRequest)
    } catch {
        return request(originalRequest, failedWith: error)
    }
}

// 提供`URLRequestConvertible`,创建数据请求
@discardableResult
open func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
    var originalRequest: URLRequest?

    do {
        originalRequest = try urlRequest.asURLRequest()
        let originalTask = DataRequest.Requestable(urlRequest: originalRequest!)
        
        // 这里其实是用传进来的urlRequest,创建了一个dataTask
        let task = try originalTask.task(session: session, adapter: adapter, queue: queue)
        // 创建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`,创建数据请求
// 解决url或者参数处理过程可能会跑出错误的情况
private func request(_ urlRequest: URLRequest?, failedWith error: Error) -> DataRequest {
    // 首先声明一个关联值为空的data类型的RequestTask
    var requestTask: Request.RequestTask = .data(nil, nil)

    // 如果urlRequest不为空,创建一个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是适配过程中出现的error,那么允许重试
    if let retrier = retrier, error is AdaptError {
        allowRetrier(retrier, toRetry: request, with: underlyingError)
    } else {
        if startRequestsImmediately { request.resume() }
    }

    return request
}

关于Swift的下标特性,可以访问【Swift 3.1】12 - 下标 (Subscripts)

5. 下载请求 & 上传请求 & Stream请求

这三个请求与数据请求的代码大同小异,大家可以自己深入看看。如果有不懂的,欢迎留言,我看到了会尽力解答。

6. 重试请求

// 重试请求是否成功
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.retryCount += 1
        request.startTime = CFAbsoluteTimeGetCurrent()
        request.endTime = nil

        task.resume()

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

// 实现重试
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开发群:536353151,本群只讨论Swift相关内容。

原创文章,转载请注明出处。谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,163评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,301评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,089评论 0 352
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,093评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,110评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,079评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,005评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,840评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,278评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,497评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,667评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,394评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,980评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,628评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,796评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,649评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,548评论 2 352

推荐阅读更多精彩内容