Alamofire源码分析(9)——多表单上传

2020-12-22  本文已影响0人  无悔zero

之前在网络学习(三)中总结过一些多表单上传的基础知识,今天就来探索一下Alamofire的多表单上传,平常的上传文件或图片都属于多表单上传,看看下面例子:

Alamofire.upload(multipartFormData: { (mutilPartData) in
        mutilPartData.append("123".data(using: .utf8)!, withName: "id")
        mutilPartData.append("2020-12-12".data(using: .utf8)!, withName: "time")
        mutilPartData.append("你的名字".data(using: .utf8)!, withName: "name")
            
        mutilPartData.append(data as! Data, withName: "file")
}, to: urlString) { (result) in
        print(result)
}
  1. 直接从upload进去:
open func upload(
    multipartFormData: @escaping (MultipartFormData) -> Void,
    usingThreshold encodingMemoryThreshold: UInt64 = SessionManager.multipartFormDataEncodingMemoryThreshold,
    to url: URLConvertible,
    method: HTTPMethod = .post,
    headers: HTTPHeaders? = nil,
    queue: DispatchQueue? = nil,
    encodingCompletion: ((MultipartFormDataEncodingResult) -> Void)?)
{
    do {
        let urlRequest = try URLRequest(url: url, method: method, headers: headers)

        return upload(
            multipartFormData: multipartFormData,
            usingThreshold: encodingMemoryThreshold,
            with: urlRequest,
            queue: queue,
            encodingCompletion: encodingCompletion
        )
    } catch { ... }
}
  1. 首先会创建MultipartFormData,通过multipartFormData闭包传到外面:
open class SessionManager {
    ...
    open func upload(
        multipartFormData: @escaping (MultipartFormData) -> Void,
        usingThreshold encodingMemoryThreshold: UInt64 = SessionManager.multipartFormDataEncodingMemoryThreshold,
        with urlRequest: URLRequestConvertible,
        queue: DispatchQueue? = nil,
        encodingCompletion: ((MultipartFormDataEncodingResult) -> Void)?)
    {
        DispatchQueue.global(qos: .utility).async {
            let formData = MultipartFormData()
            multipartFormData(formData)//外面添加数据

            var tempFileURL: URL?

            do {
                ...
                if formData.contentLength < encodingMemoryThreshold && !isBackgroundSession {
                    let data = try formData.encode()//拼接处理
                    //上传
                    let encodingResult = MultipartFormDataEncodingResult.success(
                        request: self.upload(data, with: urlRequestWithContentType),
                        streamingFromDisk: false,
                        streamFileURL: nil
                    )
                    //结果回调
                    (queue ?? DispatchQueue.main).async { encodingCompletion?(encodingResult) }
                } else {
                    //如果传输超出阀值或者后台下载,创建文件,通过文件上传/下载
                    ...
                    //写入
                    try formData.writeEncodedData(to: fileURL)
                    //上传文件(URL)
                    let upload = self.upload(fileURL, with: urlRequestWithContentType)
                    ...
                    }
                }
            } catch { ... }
        }
    }
    ...
}
  1. 然后进行数据添加:
.upload(multipartFormData: { (mutilPartData) in
        mutilPartData.append("123".data(using: .utf8)!, withName: "id")
        mutilPartData.append("2020-12-12".data(using: .utf8)!, withName: "time")
        mutilPartData.append("你的名字".data(using: .utf8)!, withName: "name")
            
        mutilPartData.append(data as! Data, withName: "file")
}, to: urlString) 
  1. MultipartFormData添加数据时,会在内部转为流数据:
open class MultipartFormData {
    ...
    public func append(_ data: Data, withName name: String) {
        let headers = contentHeaders(withName: name)
        let stream = InputStream(data: data)//因为data类型占内存大,所以转化为流数据,内部经过压缩可以节省内存
        let length = UInt64(data.count)

        append(stream, withLength: length, headers: headers)
    }
    ...
}
  1. 然后把数据按照各部分保存为BodyPart,添加到数组中:
open class MultipartFormData {
    ...
    public func append(_ stream: InputStream, withLength length: UInt64, headers: HTTPHeaders) {
        let bodyPart = BodyPart(headers: headers, bodyStream: stream, bodyContentLength: length)
        bodyParts.append(bodyPart)
    }
    ...
}
open class MultipartFormData {
    ...
    class BodyPart {
        ...
        init(headers: HTTPHeaders, bodyStream: InputStream, bodyContentLength: UInt64) {
            self.headers = headers
            self.bodyStream = bodyStream
            self.bodyContentLength = bodyContentLength
        }
    }
    ...
}
  1. 接着回到第2步,调用formData.encode()BodyPart进行处理:
open class MultipartFormData {
    ...
    public func encode() throws -> Data {
        if let bodyPartError = bodyPartError { ... }

        var encoded = Data()
        //标记
        bodyParts.first?.hasInitialBoundary = true
        bodyParts.last?.hasFinalBoundary = true
        
        for bodyPart in bodyParts {
            let encodedData = try encode(bodyPart)//拼接处理
            encoded.append(encodedData)
        }

        return encoded
    }
    ...
}

最终会变成格式化的多表单数据:

  1. 数据处理完后,回到第2步,下一步是创建URLSessionTask进行上传:
open class SessionManager {
    ...
    @discardableResult
    open func upload(_ data: Data, with urlRequest: URLRequestConvertible) -> UploadRequest {
        do {
            let urlRequest = try urlRequest.asURLRequest()
            return upload(.data(data, urlRequest))
        } catch {
            return upload(nil, failedWith: error)
        }
    }
    ...
    private func upload(_ uploadable: UploadRequest.Uploadable) -> UploadRequest {
        do {
            let task = try uploadable.task(session: session, adapter: adapter, queue: queue)//创建task
            let upload = UploadRequest(session: session, requestTask: .upload(uploadable, task))
            ...
            if startRequestsImmediately { upload.resume() }//开始上传

            return upload
        } catch { ... }
    }
    ...
}
open class UploadRequest: DataRequest {

    enum Uploadable: TaskConvertible {
        ...
        func task(session: URLSession, adapter: RequestAdapter?, queue: DispatchQueue) throws -> URLSessionTask {
            do {
                let task: URLSessionTask

                switch self {
                case let .data(data, urlRequest):
                    let urlRequest = try urlRequest.adapt(using: adapter)
                    task = queue.sync { session.uploadTask(with: urlRequest, from: data) }//创建task
                case let .file(url, urlRequest): ...
                case let .stream(_, urlRequest): ...
                }

                return task
            } catch { ... }
        }
    }
    ...
}
  1. 上传完成后回到第2步,通过encodingCompletion闭包回调结果:
Alamofire.upload(multipartFormData: { ... }, to: urlString) { (result) in
    print(result)
}

多表单上传处理数据很麻烦,但是Alamofire已经帮我们做了封装。
1.用户只需传递数据,Alamofire内部把数据包装成一个个BodyPart
2.然后进行详细编码,拼接 分隔符 和 固定格式头信息 ,通过InputStream读取具体值
3.最后创建task进行上传
4.其中通过SessionDelegate接受上传代理,最后下发给UploadTaskDelegate返回上传情况

上一篇 下一篇

猜你喜欢

热点阅读