😊😊😊Alamofire thematic directory. Welcome timely feedback and exchange
- Alamofire (1) – URLSession prerequisite skill
- Alamofire (2) — Background download
- Alamofire (3) — Request
- Alamofire (4) — Details you need to know
- Alamofire (5) — Response
- Alamofire (6) — Multiple form uploads
- Alamofire (7) — Safety certification
- Alamofire (8) — Final Chapter (Network Monitoring & Notifications & Downloader Packaging)
Alamofire Directory through train — Harmonious learning, not impatient!
In the actual development process, multi-form uploads are very important requests! The server usually uses the Content-Type field in the headers to determine how the body of the message in the request is encoded and then parses the body. So when it comes to POST submission data scheme, there are two parts: Content-Type and message body encoding. In this chapter we will explore the multi-form upload file ~
Multiform format
Below, I use the interface of Charles to capture packages and upload pictures
--alamofire.boundary.4e076f46186e231d:
Is a delimiter for easy reading of dataContent-Disposition: form-data; name="name":
Among themContent-disposition
是MIME
Extension of protocol,MIME
Protocol indicatesMIME
How the user agent displays the attached file.Content-disposition
In fact, it is possible to control the user to provide a default file name when the requested content is saved as a filekey = name
- It comes right after that
\r\n
A newline - And then there is
key
The correspondingvalue = LGCooci
- The garble at the bottom is the picture
The data of data
The Multipart format displays the entire data as a key-value in a dictionary
Second, we request multiple forms through URLSeesion
1️ retail: delimiter initialization
init() {
self.boundary = NSUUID().uuidString
}
Copy the code
- Use NSUUID().uuidString as separator
2️ one: line break symbol
extension CharacterSet {
static func MIMECharacterSet(a) -> CharacterSet {
let characterSet = CharacterSet(charactersIn: "\"\n\r")
return characterSet.inverted
}
}
Copy the code
3️ retail: Data format processing & splicing
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)
}
Copy the code
4️ data processing completed, and then set httpBody
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")}}Copy the code
5️ retail: multi-form format packaging, and use
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")}}// newline processing
extension CharacterSet {
static func MIMECharacterSet(a) -> CharacterSet {
let characterSet = CharacterSet(charactersIn: "\"\n\r")
return characterSet.inverted
}
}
// Multi-form factory
struct LGMultipartDataBuilder{
var fields: [Data] = []
public let boundary: String
// initializer - delimiter created
init() {
self.boundary = NSUUID().uuidString
}
// All data format processing
func build(a) -> Data? {
let data = NSMutableData(a)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)}// Data format key value stitching
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)
}
// Format splicing
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)
}
// Data encoding
fileprivate func encode(_ string: String) -> String {
let characterSet = CharacterSet.MIMECharacterSet(a)return string.addingPercentEncoding(withAllowedCharacters: characterSet)!
}
// Convert to data for easy splicing
fileprivate func toData(_ string: String) -> Data {
return string.data(using: .utf8)!
}
// Merge single data
fileprivate func merge(_ chunks: [Data]) -> Data {
let data = NSMutableData(a)for chunk in chunks {
data.append(chunk)
}
return data.copy() as! Data}}// Use the whole data call
fileprivate func dealwithRequest(urlStr:String) -> URLRequest{
var request = URLRequest(url: URL(string: urlStr)!)
var builder = LGMultipartDataBuilder(a)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
}
Copy the code
summary
Obviously, it would be gross if we did this every time we uploaded a file! So encapsulation is so important for development! Here we can customize packaging, according to their own company needs packaging format! However, many companies do not need too much relationship, and the default operation is OK directly. As long as the field matches, Alamofire will obviously feel comfortable at this time 👍👍👍
Alamofire form data upload
Alamofire handles multiple forms in three ways, encapsulated by the three methods of URLSession
// 1: upload data format
session.uploadTask(with: urlRequest, from: data)
// 2: upload file address
session.uploadTask(with: urlRequest, fromFile: url)
// 3: uploads stream data
session.uploadTask(withStreamedRequest: urlRequest)
Copy the code
🌰 is used as 🌰
//MARK: -Alamofire uploads files - other methods
func alamofireUploadFileOtherMethod(a){
// 1: upload a file
// The path of 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("Upload progress:\(progress)")
}).response { (response) in
print(response)
}
// 2: data upload
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("Upload failed!!")}}// 3: stream upload
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("Upload failed!!")}}// 4: multiple form uploads
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)* * * *")})}}}Copy the code
- If you just want to use it, but here’s OK!
- So let’s start with the analysis
Alamofire
Source code, convenient for us to understand more deeplyAlamofire!
Alamofire multi-form source code analysis
⚠️ source code analysis in front of the code will not be posted, you can follow the source code ⚠️
1️ retail: First create containers
DispatchQueue.global(qos: .utility).async {
let formData = MultipartFormData()
multipartFormData(formData)
}
Copy the code
- In this
MultipartFormData
Class contains a nested storage structureEncodingCharacters
Save the newline character\r\n
BoundaryGenerator
Delimiter handling= String(format: "alamofire.boundary.%08x%08x", arc4random(), arc4random()
Is a fixed field splicing random field
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)! }}Copy the code
- There are three types of separators
- First: initial delimiter (no concatenated newline)
- Second: intermediate content directly delimiter (front concatenate newline + last concatenate newline)
- The third type: the end separator (front concatenate newline + last concatenate newline) is less than the second type
"-"
string - You can compare it carefully, and then compare it with the captured packet data, and you can see why it’s so divided. Right
multipartFormData(formData)
Next, call the external closure, prepare the condition, and start filling in the data
2️ retail: filling data
mutilPartData.append("LGCooci".data(using: .utf8)! , withName: "username")
The internal call is to get the data information
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)
}
// Content header format concatenation
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
}
Copy the code
- Content header fixed format processing, splicing
Content-Disposition
And then setfileName
Complete the whole section after settingmimeType
- our
value
That isLGCooci
The data ofStream
Packaging, save memory - Get data length
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)
}
Copy the code
- The cluttered data is encapsulated by object-oriented design principles
BodyPart
In terms of transmission - through
bodyParts
Collect one by oneBodyPart
3️ retail: data integration
let data = try formData.encode()
And then by traversingbodyParts
Encapsulate it into an appropriate format and return itdata
Assigned tohttpBody
/ / traverse bodyParts
for bodyPart in bodyParts {
let encodedData = try encode(bodyPart)
encoded.append(encodedData)
}
// unified encoding
private func encode(_ bodyPart: BodyPart) throws -> Data {
var encoded = Data(a)// Determine if the first line data determines the delimiter
let initialData = bodyPart.hasInitialBoundary ? initialBoundaryData() : encapsulatedBoundaryData()
encoded.append(initialData)
// 拼接字段头:encodeHeaders
let headerData = encodeHeaders(for: bodyPart)
encoded.append(headerData)
// Read Data Data
let bodyStreamData = try encodeBodyStream(for: bodyPart)
encoded.append(bodyStreamData)
// Whether to concatenate the end separator
if bodyPart.hasFinalBoundary {
encoded.append(finalBoundaryData())
}
return encoded
}
Copy the code
- Check if it’s the first row
data
Deterministic separator - Concatenated field header:
encodeHeaders
- Read the data
Data
- Whether to concatenate the end separator
- Finally, all the data are sequentially spliced into
data
中
4️ retail: data call
let encodingResult = MultipartFormDataEncodingResult.success(
request: self.upload(data, with: urlRequestWithContentType),
streamingFromDisk: false,
streamFileURL: nil
)
Copy the code
- Pass into
uploadRequest
In the requestor of - The invocation is determined by the data type passed
URLSession
The method of - Then through
SessionDelegate
Accept upload agent – send lastUploadTaskDelegate
conclusion
- The data is initialized by the format container
- The user then passes in the data that needs to be uploaded and fills it in
- Package up
bodyPart
, collected through a binding containerbodyParts
- All wrapped up, traversal
bodyParts
Detailed coding - First concatenate delimiters, concatenate fixed format header information, and then pass
stream
Read specific! Value, - through
data
Pass in, callURLSession
The way to respond, - through
SessionDelegate
Accept upload agent – send lastUploadTaskDelegate
Finally returns the upload status
That’s the end of the multi-form processing chapter! If you have any questions, you can directly comment on the discussion area! Recently, I have been busy with the anniversary of the company, so I have missed a lot of blogs. However, I will make up for them one by one during this period. Thank you for your wishes!
Just ask who else is there right now? 45 degrees up in the sky, damn it! My charm with no place to put it!