之前在网络学习(三)中总结过一些多表单上传的基础知识,今天就来探索一下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)
}
- 直接从
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 { ... }
}
- 首先会创建
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 { ... }
}
}
...
}
- 然后进行数据添加:
.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)
-
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)
}
...
}
- 然后把数据按照各部分保存为
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
}
}
...
}
- 接着回到第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
}
...
}
最终会变成格式化的多表单数据:
- 数据处理完后,回到第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 { ... }
}
}
...
}
- 上传完成后回到第2步,通过
encodingCompletion
闭包回调结果:
Alamofire.upload(multipartFormData: { ... }, to: urlString) { (result) in
print(result)
}
- 总结
多表单上传处理数据很麻烦,但是Alamofire已经帮我们做了封装。
1.用户只需传递数据,Alamofire内部把数据包装成一个个BodyPart
2.然后进行详细编码,拼接 分隔符 和 固定格式头信息 ,通过InputStream
读取具体值
3.最后创建task
进行上传
4.其中通过SessionDelegate
接受上传代理,最后下发给UploadTaskDelegate
返回上传情况
网友评论