😊😊😊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度仰望天空,該死!我這無處安放的魅力!