😊😊😊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 data
  • Content-Disposition: form-data; name="name":Among themContent-dispositionMIMEExtension of protocol,MIMEProtocol indicatesMIMEHow the user agent displays the attached file.Content-dispositionIn 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\nA newline
  • And then there iskeyThe correspondingvalue = LGCooci
  • The garble at the bottom is the pictureThe 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 analysisAlamofireSource 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 thisMultipartFormDataClass contains a nested storage structureEncodingCharactersSave the newline character\r\n
  • BoundaryGeneratorDelimiter 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, splicingContent-DispositionAnd then setfileNameComplete the whole section after settingmimeType
  • ourvalueThat isLGCoociThe data ofStreamPackaging, save memory
  • Get data lengthUInt64(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 principlesBodyPartIn terms of transmission
  • throughbodyPartsCollect one by oneBodyPart

3️ retail: data integration

let data = try formData.encode()

And then by traversingbodyPartsEncapsulate it into an appropriate format and return itdataAssigned 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 rowdataDeterministic separator
  • Concatenated field header:encodeHeaders
  • Read the dataData
  • Whether to concatenate the end separator
  • Finally, all the data are sequentially spliced intodata

4️ retail: data call

let encodingResult = MultipartFormDataEncodingResult.success(
    request: self.upload(data, with: urlRequestWithContentType),
    streamingFromDisk: false,
    streamFileURL: nil
)
Copy the code
  • Pass intouploadRequestIn the requestor of
  • The invocation is determined by the data type passedURLSessionThe method of
  • Then throughSessionDelegateAccept 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 upbodyPart, collected through a binding containerbodyParts
  • All wrapped up, traversalbodyPartsDetailed coding
  • First concatenate delimiters, concatenate fixed format header information, and then passstreamRead specific! Value,
  • throughdataPass in, callURLSessionThe way to respond,
  • throughSessionDelegateAccept upload agent – send lastUploadTaskDelegateFinally 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!