😊😊😊Alamofire专题目录,欢迎及时反馈交流 😊😊😊安全
Alamofire 目录直通车 --- 和谐学习,不急不躁!网络
实际开发过程当中,多表单上传是很是重要的一种请求!服务端一般是根据请求头
(headers)
中的Content-Type
字段来获知请求中的消息主体是用何种方式编码,再对主体进行解析。 因此说到POST
提交数据方案,包含了Content-Type
和消息主体编码方式两部分。 这个篇章咱们来探索一下 多表单上传文件 ~session
下面我经过 Charles 抓包上传图片的接口闭包
--alamofire.boundary.4e076f46186e231d:
是分隔符,为了方便读取数据Content-Disposition: form-data; name="name":
其中 Content-disposition
是 MIME
协议的扩展,MIME
协议指示 MIME
用户代理如何显示附加的文件。Content-disposition
其实能够控制用户请求所得的内容存为一个文件的时候提供一个默认的文件名,这里就是添加了一个 key = name
\r\n
换行符key
对应的 value = LGCooci
data数据
Multipart 格式显示整个数据就相似字典的 key-valueapp
init() {
self.boundary = NSUUID().uuidString
}
复制代码
extension CharacterSet {
static func MIMECharacterSet() -> CharacterSet {
let characterSet = CharacterSet(charactersIn: "\"\n\r")
return characterSet.inverted
}
}
复制代码
public func appendFormData(_ name: String, content: Data, fileName: String, contentType: String) {
let contentDisposition = "Content-Disposition: form-data; name=\"\(self.encode(name))\"; filename=\"\(self.encode(fileName))\""
let contentTypeHeader = "Content-Type: \(contentType)"
let data = self.merge([
self.toData(contentDisposition),
MutlipartFormCRLFData,
self.toData(contentTypeHeader),
MutlipartFormCRLFData,
MutlipartFormCRLFData,
content,
MutlipartFormCRLFData
])
self.fields.append(data)
}
复制代码
public extension URLRequest {
mutating func setMultipartBody(_ data: Data, boundary: String) {
self.httpMethod = "POST"
self.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
self.httpBody = data
self.setValue(String( data.count ), forHTTPHeaderField: "Content-Length")
self.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
}
}
复制代码
public extension URLRequest {
mutating func setMultipartBody(_ data: Data, boundary: String) {
self.httpMethod = "POST"
self.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
self.httpBody = data
self.setValue(String( data.count ), forHTTPHeaderField: "Content-Length")
self.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
}
}
// 换行符处理
extension CharacterSet {
static func MIMECharacterSet() -> CharacterSet {
let characterSet = CharacterSet(charactersIn: "\"\n\r")
return characterSet.inverted
}
}
// 多表单工厂器
struct LGMultipartDataBuilder{
var fields: [Data] = []
public let boundary: String
// 初始化 - 分隔符建立
init() {
self.boundary = NSUUID().uuidString
}
// 全部数据格式处理
func build() -> Data? {
let data = NSMutableData()
for field in self.fields {
data.append(self.toData("--\(self.boundary)"))
data.append(MutlipartFormCRLFData)
data.append(field)
}
data.append(self.toData("--\(self.boundary)--"))
data.append(MutlipartFormCRLFData)
return (data.copy() as! Data)
}
// 数据格式key value拼接
mutating public func appendFormData(_ key: String, value: String) {
let content = "Content-Disposition: form-data; name=\"\(encode(key))\""
let data = self.merge([
self.toData(content),
MutlipartFormCRLFData,
MutlipartFormCRLFData,
self.toData(value),
MutlipartFormCRLFData
])
self.fields.append(data)
}
// 格式拼接
mutating public func appendFormData(_ name: String, content: Data, fileName: String, contentType: String) {
let contentDisposition = "Content-Disposition: form-data; name=\"\(self.encode(name))\"; filename=\"\(self.encode(fileName))\""
let contentTypeHeader = "Content-Type: \(contentType)"
let data = self.merge([
self.toData(contentDisposition),
MutlipartFormCRLFData,
self.toData(contentTypeHeader),
MutlipartFormCRLFData,
MutlipartFormCRLFData,
content,
MutlipartFormCRLFData
])
self.fields.append(data)
}
// 数据编码
fileprivate func encode(_ string: String) -> String {
let characterSet = CharacterSet.MIMECharacterSet()
return string.addingPercentEncoding(withAllowedCharacters: characterSet)!
}
// 转成data 方便拼接 处理
fileprivate func toData(_ string: String) -> Data {
return string.data(using: .utf8)!
}
// 合并单个数据
fileprivate func merge(_ chunks: [Data]) -> Data {
let data = NSMutableData()
for chunk in chunks {
data.append(chunk)
}
return data.copy() as! Data
}
}
// 整个数据的调用使用
fileprivate func dealwithRequest(urlStr:String) -> URLRequest{
var request = URLRequest(url: URL(string: urlStr)!)
var builder = LGMultipartDataBuilder()
let data = self.readLocalData(fileNameStr: "Cooci", type: "jpg")
builder.appendFormData("filedata",content:data as! Data , fileName: "fileName", contentType: "image/jpeg")
request.setMultipartBody(builder.build()!, boundary: builder.boundary)
return request
}
复制代码
很显然,若是每一次咱们上传文件,都这么处理那是很是恶心的!因此封装对于开发来讲是多么的重要!这里咱们能够自定义封装,根据本身公司需求包装格式!可是有不少公司是不须要关系太多的,直接默认操做就OK,只要字段匹配,那么 Alamofire
这个时候就很明显感觉到了舒服 👍👍👍dom
Alamofire 处理多表单的方式有三种,根据 URLSession 的三个方法封装而来async
// 1:上传data格式
session.uploadTask(with: urlRequest, from: data)
// 2: 上传文件地址
session.uploadTask(with: urlRequest, fromFile: url)
// 3:上传stream流数据
session.uploadTask(withStreamedRequest: urlRequest)
复制代码
🌰 具体使用以下:🌰源码分析
//MARK: - alamofire上传文件 - 其余方法
func alamofireUploadFileOtherMethod(){
// 1: 文件上传
// file 的路径
let path = Bundle.main.path(forResource: "Cooci", ofType: "jpg");
let url = URL(fileURLWithPath: path!)
SessionManager.default.upload(url, to: jianshuUrl).uploadProgress(closure: { (progress) in
print("上传进度:\(progress)")
}).response { (response) in
print(response)
}
// 2: data上传
let data = self.readLocalData(fileNameStr: "Cooci", type: "jpg")
SessionManager.default.upload(data as! Data, to: jianshuUrl, method: .post, headers: ["":""]).validate().responseJSON { (DataResponse) in
if DataResponse.result.isSuccess {
print(String.init(data: DataResponse.data!, encoding: String.Encoding.utf8)!)
}
if DataResponse.result.isFailure {
print("上传失败!!!")
}
}
// 3: stream上传
let inputStream = InputStream(data: data as! Data)
SessionManager.default.upload(inputStream, to: jianshuUrl, method: .post, headers: ["":""]).response(queue: DispatchQueue.main) { (DDataRespose) in
if let acceptData = DDataRespose.data {
print(String.init(data: acceptData, encoding: String.Encoding.utf8)!)
}
if DDataRespose.error != nil {
print("上传失败!!!")
}
}
// 4: 多表单上传
SessionManager.default
.upload(multipartFormData: { (mutilPartData) in
mutilPartData.append("cooci".data(using: .utf8)!, withName: "name")
mutilPartData.append("LGCooci".data(using: .utf8)!, withName: "username")
mutilPartData.append("123456".data(using: .utf8)!, withName: "PASSWORD")
mutilPartData.append(data as! Data, withName: "fileName")
}, to: urlString) { (result) in
print(result)
switch result {
case .failure(let error):
print(error)
case .success(let upload,_,_):
upload.response(completionHandler: { (response) in
print("****:\(response) ****")
})
}
}
}
复制代码
Alamofire
源码,方便咱们更加深刻了解 Alamofire!
⚠️ 源码前面分析的代码就不贴出来,你们能够自行跟源码 ⚠️post
DispatchQueue.global(qos: .utility).async {
let formData = MultipartFormData()
multipartFormData(formData)
}
复制代码
MultipartFormData
类里面嵌套一个储存结构体 EncodingCharacters
保存换行符 \r\n
BoundaryGenerator
分隔符处理 = String(format: "alamofire.boundary.%08x%08x", arc4random(), arc4random()
是一个固定字段拼接随机字段static func boundaryData(forBoundaryType boundaryType: BoundaryType, boundary: String) -> Data {
let boundaryText: String
switch boundaryType {
case .initial:
boundaryText = "--\(boundary)\(EncodingCharacters.crlf)"
case .encapsulated:
boundaryText = "\(EncodingCharacters.crlf)--\(boundary)\(EncodingCharacters.crlf)"
case .final:
boundaryText = "\(EncodingCharacters.crlf)--\(boundary)--\(EncodingCharacters.crlf)"
}
return boundaryText.data(using: String.Encoding.utf8, allowLossyConversion: false)!
}
}
复制代码
“--”
字符串multipartFormData(formData)
接下来调用外界闭包,准备条件完成,开始填充数据mutilPartData.append("LGCooci".data(using: .utf8)!, withName: "username")
学习
内部调用就是获取数据信息
public func append(_ data: Data, withName name: String) {
let headers = contentHeaders(withName: name)
let stream = InputStream(data: data)
let length = UInt64(data.count)
append(stream, withLength: length, headers: headers)
}
// 内容头格式拼接
private func contentHeaders(withName name: String, fileName: String? = nil, mimeType: String? = nil) -> [String: String] {
var disposition = "form-data; name=\"\(name)\""
if let fileName = fileName { disposition += "; filename=\"\(fileName)\"" }
var headers = ["Content-Disposition": disposition]
if let mimeType = mimeType { headers["Content-Type"] = mimeType }
return headers
}
复制代码
Content-Disposition
而后设置 fileName
完成以后整段设置 mimeType
value
也就是 LGCooci
的数据经过 Stream
包装,节省内存UInt64(data.count)
public func append(_ stream: InputStream, withLength length: UInt64, headers: HTTPHeaders) {
let bodyPart = BodyPart(headers: headers, bodyStream: stream, bodyContentLength: length)
bodyParts.append(bodyPart)
}
复制代码
BodyPart
方面传输bodyParts
集合收集一个个 BodyPart
let data = try formData.encode()
接下来经过遍历 bodyParts
封装成合适的格式返回出 data
赋值给 httpBody
// 遍历bodyParts
for bodyPart in bodyParts {
let encodedData = try encode(bodyPart)
encoded.append(encodedData)
}
// 统一编码
private func encode(_ bodyPart: BodyPart) throws -> Data {
var encoded = Data()
// 判断是不是第一行data肯定分隔符
let initialData = bodyPart.hasInitialBoundary ? initialBoundaryData() : encapsulatedBoundaryData()
encoded.append(initialData)
// 拼接字段头:encodeHeaders
let headerData = encodeHeaders(for: bodyPart)
encoded.append(headerData)
// 读取数据 Data
let bodyStreamData = try encodeBodyStream(for: bodyPart)
encoded.append(bodyStreamData)
// 是否拼接结束分割符
if bodyPart.hasFinalBoundary {
encoded.append(finalBoundaryData())
}
return encoded
}
复制代码
data
肯定分隔符encodeHeaders
Data
data
中let encodingResult = MultipartFormDataEncodingResult.success(
request: self.upload(data, with: urlRequestWithContentType),
streamingFromDisk: false,
streamFileURL: nil
)
复制代码
uploadRequest
的请求器里面URLSession
的方法SessionDelegate
接受上传代理 - 最后下发给UploadTaskDelegate
bodyPart
,经过一个结合容器收集bodyParts
bodyParts
进行详细编码stream
读取具体!值,data
传进,调用 URLSession
响应的方法,SessionDelegate
接受上传代理 - 最后下发给UploadTaskDelegate
最终返回上传状况到这里这个
多表单处理
篇章就写完了!若有什么疑问,能够直接评论区交流讨论!前段时间一直在忙公司周年庆的事情,博客落下了很多,不过这段时间我会一一补回来,谢谢,你们寄来的祝福!就问此时此刻还有谁?45度仰望天空,该死!我这无处安放的魅力!