Alamofire源碼學習(十一): MultipartFormData與MultipartUpload:多表單數據上傳

往期導航:

Alamofire源碼學習目錄合集html

簡介

須要上傳多表單數據時,須要將data封裝在body中,而且使用分隔符分隔開,Alamofire封裝了MultipartFormData類來操做多表單data的封裝檢測,拼接操做符等操做。
功能:swift

  • 能夠接受:Data類型,Stream, fileurl類型的data,跟name,mimetype一塊兒保存
  • 數據最終都會封裝成InputStream保存,小文件可直接傳入Data類型,大文件須要使用Stream或者fileurl類型,不然會超出內存
  • 能夠之定義數據分隔符,不指定則使用默認分隔符
  • 保存的數據類型爲bodyParts數組,最終編碼入URLRequest對象的操做是在MutipartUpload對象中進行。

MutipartUpload類的做用爲把封裝處理好的MultipartFormData對象中的數據編碼進URLRequest的body中,在編碼時會進行判斷,若是data的大小超過了限制,則會先存入臨時文件,而後把文件url封裝爲UploadRequest.Uploadable回調給UploadRequest初始化使用。數組

MultipartFormData:

首先封裝了幾個輔助類型來處理表單數據:

1.EncodingCharacters:

封裝回車換行字符串:緩存

enum EncodingCharacters {
        static let crlf = "\r\n"
    }
複製代碼
2.BoundaryGenerator:

封裝多表單數據的分隔符,該分隔符須要存放在body頭中,類型分爲:開頭,中間,結尾三種,不一樣類型先後帶有的換行符不一樣。
默認會生成隨機的分隔符,使用時也能夠本身制定分隔符字符串。
輸出格式爲Data,在對錶單數據編碼時,會按順序插入data間隔中。markdown

enum BoundaryGenerator {
        enum BoundaryType {
            case initial//起始: --分隔符\r\n
            case encapsulated//中間: \r\n--分隔符\r\n
            case final//結束: \r\n--分隔符--\r\n
        }
        
        /// 隨機分隔符
        /// 隨機生成兩個32位無符號整數,而後轉成十六進制展現,使用0補足8個字符,加上前綴
        static func randomBoundary() -> String {
            let first = UInt32.random(in: UInt32.min...UInt32.max)
            let second = UInt32.random(in: UInt32.min...UInt32.max)

            return String(format: "alamofire.boundary.%08x%08x", first, second)
        }
        
        //生成分隔符Data, 拼接數據用
        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 Data(boundaryText.utf8)
        }
    }
複製代碼
3.BodyPart類:

封裝了多表單數據中每個表單數據對象,持有數據的頭數據,body的數據長度,使用hasInitialBoundaryhasFinalBoundary來記錄數據先後分隔符的類型。app

class BodyPart {
        // 每一個body的頭
        let headers: HTTPHeaders
        // body數據stream
        let bodyStream: InputStream
        // 數據長度
        let bodyContentLength: UInt64
        
        /// 使用下面兩個變量來控制數據先後分隔符的類型,在最終編碼時,把BodyParts數組的頭尾對應開關打開, 兩個都爲false表明中間表單數據
        // 是否有開始分隔符
        var hasInitialBoundary = false
        // 是否有結尾分隔符
        var hasFinalBoundary = false
        

        init(headers: HTTPHeaders, bodyStream: InputStream, bodyContentLength: UInt64) {
            self.headers = headers
            self.bodyStream = bodyStream
            self.bodyContentLength = bodyContentLength
        }
    }
複製代碼

私有屬性與初始化:

/// 編碼數據時,最大的內存容量,默認10MB,超過則把數據編碼到磁盤臨時文件中
    public static let encodingMemoryThreshold: UInt64 = 10_000_000

    /// 多表單數據的頭部Content-Type, 定義了multipart/form-data與分隔符
    open lazy var contentType: String = "multipart/form-data; boundary=\(self.boundary)"

    /// 全部表單數據部分的data大小, 不包括分隔符
    public var contentLength: UInt64 { bodyParts.reduce(0) { $0 + $1.bodyContentLength } }

    /// 用來分割表單數據的分隔符
    public let boundary: String

    /// 添加fileurl類型的數據時用來操做文件使用, 以及將data寫入臨時文件時用
    let fileManager: FileManager

    /// 保存多表單數據的數組
    private var bodyParts: [BodyPart]
    /// 追加表單數據時出現的錯誤, 會拋給上層
    private var bodyPartError: AFError?
    /// 讀寫IOStream時的buffer大小,默認1024Byte
    private let streamBufferSize: Int
    
    public init(fileManager: FileManager = .default, boundary: String? = nil) {
        self.fileManager = fileManager
        self.boundary = boundary ?? BoundaryGenerator.randomBoundary()
        bodyParts = []

        //
        // The optimal read/write buffer size in bytes for input and output streams is 1024 (1KB). For more
        // information, please refer to the following article:
        // - https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Streams/Articles/ReadingInputStreams.html
        //
        streamBufferSize = 1024
    }
複製代碼

追加表單數據相關

提供了5個方法來追加3中不一樣的表單數據類型:dom

  1. Data+name(直接從內存中讀取,只能用於小文件,可指定文件名字與mime類型)
  2. fileurl+name(未指明文件名字,與mime類型,根據fileurl最後的文件名與擴展名來判斷,而後調用方法3)
  3. fileurl+name+文件名+mime類型(等同於2)
  4. InputStream+data長度+文件名+mime類型(會先把mime類型封裝爲HTTPHeaders而後調用方法5)
  5. InputStream+data長度+HTTPHeaders

上面5個方法中,前4個最終都會調用到第5個,Data與fileurl類型都會轉換成InputStream類型,而後跟表單頭一塊兒封裝成BodyPart類型保存。函數

1.Data+name(可選:+fileName+mime類型)
/// 最終編碼的表單數據格式:
    /// - `前分隔符(如果第一個數據塊, 就沒有前分隔符)`
    /// - `Content-Disposition: form-data; name=#{name}; filename=#{filename}` (表單頭)
    /// - `Content-Type: #{mimeType}` (表單頭)
    /// - `Data`
    /// - `後分隔符(如果最後一個數據塊, 後分隔符是終結分隔符)`
    public func append(_ data: Data, withName name: String, fileName: String? = nil, mimeType: String? = nil) {
        let headers = contentHeaders(withName: name, fileName: fileName, mimeType: mimeType)
        let stream = InputStream(data: data)
        let length = UInt64(data.count)

        append(stream, withLength: length, headers: headers)
    }
複製代碼
2.fileurl+name
/// 最終編碼的表單數據格式:
    /// - `前分隔符(如果第一個數據塊, 就沒有前分隔符)`
    /// - `Content-Disposition: form-data; name=#{name}; filename=#{根據fileurl獲取到的文件名}`
    /// - `Content-Type: #{根據fileurl獲取到的mime類型}`
    /// - fileurl讀取出來的Data
    /// - `後分隔符(如果最後一個數據塊, 後分隔符是終結分隔符)`
    /// 文件名與mime類型是根據fileurl最後path中的文件名與擴展名獲取
    public func append(_ fileURL: URL, withName name: String) {
        // 獲取文件名與mime類型, 若讀取不到就記錄錯誤並return
        let fileName = fileURL.lastPathComponent
        let pathExtension = fileURL.pathExtension

        if !fileName.isEmpty && !pathExtension.isEmpty {
            // 使用輔助函數獲取mime類型字符串
            let mime = mimeType(forPathExtension: pathExtension)
            // 調用下面方法3繼續處理
            append(fileURL, withName: name, fileName: fileName, mimeType: mime)
        } else {
            setBodyPartError(withReason: .bodyPartFilenameInvalid(in: fileURL))
        }
    }
複製代碼
3.fileurl+name+文件名+mime類型
  • 能夠用來上傳大文件,由於最後是使用InputStream來讀取暫存文件。
  • 對fileurl進行了各類判斷,任何一部出錯都會拋出對應的錯誤
/// 最終編碼的表單數據格式:
    /// - `前分隔符(如果第一個數據塊, 就沒有前分隔符)`
    /// - `Content-Disposition: form-data; name=#{name}; filename=#{filename}`
    /// - `Content-Type: #{mimeType}`
    /// - fileurl讀取出來的Data
    /// - `後分隔符(如果最後一個數據塊, 後分隔符是終結分隔符)`
    public func append(_ fileURL: URL, withName name: String, fileName: String, mimeType: String) {
        //封裝表單頭
        let headers = contentHeaders(withName: name, fileName: fileName, mimeType: mimeType)
        // 1.檢測url是否合法, 不合法記錄錯誤並return
        guard fileURL.isFileURL else {
            setBodyPartError(withReason: .bodyPartURLInvalid(url: fileURL))
            return
        }
        // 2.檢測文件url是否能夠訪問
        do {
            let isReachable = try fileURL.checkPromisedItemIsReachable()//這個方法能夠快速檢測文件是否能夠訪問, 當不可訪問並有錯誤時, 會記錄錯誤並return
            guard isReachable else {
                setBodyPartError(withReason: .bodyPartFileNotReachable(at: fileURL))
                return
            }
        } catch {
            // catch異常並記錄錯誤並return
            setBodyPartError(withReason: .bodyPartFileNotReachableWithError(atURL: fileURL, error: error))
            return
        }

        // 3.檢測url是不是目錄, 是目錄直接記錄錯誤並return
        var isDirectory: ObjCBool = false
        let path = fileURL.path

        guard fileManager.fileExists(atPath: path, isDirectory: &isDirectory) && !isDirectory.boolValue else {
            setBodyPartError(withReason: .bodyPartFileIsDirectory(at: fileURL))
            return
        }

        // 4.檢測是否能獲取到文件大小, 沒法獲取文件大小時記錄錯誤並return
        let bodyContentLength: UInt64

        do {
            guard let fileSize = try fileManager.attributesOfItem(atPath: path)[.size] as? NSNumber else {
                setBodyPartError(withReason: .bodyPartFileSizeNotAvailable(at: fileURL))
                return
            }

            bodyContentLength = fileSize.uint64Value
        } catch {
            setBodyPartError(withReason: .bodyPartFileSizeQueryFailedWithError(forURL: fileURL, error: error))
            return
        }

        // 5.檢測可否建立InputStream, 沒法建立InputStream時記錄錯誤並return

        guard let stream = InputStream(url: fileURL) else {
            setBodyPartError(withReason: .bodyPartInputStreamCreationFailed(for: fileURL))
            return
        }
        // 6.調用下面的方法5繼續處理
        append(stream, withLength: bodyContentLength, headers: headers)
    }
複製代碼
4.InputStream+data長度+name+文件名字+mime類型
/// 最終編碼的表單數據格式:
    /// - `前分隔符(如果第一個數據塊, 就沒有前分隔符)`
    /// - `Content-Disposition: form-data; name=#{name}; filename=#{filename}`
    /// - `Content-Type: #{mimeType}`
    /// - fileurl讀取出來的Data
    /// - `後分隔符(如果最後一個數據塊, 後分隔符是終結分隔符)`
    public func append(_ stream: InputStream, withLength length: UInt64, name: String, fileName: String, mimeType: String) {
        //封裝下表單頭
        let headers = contentHeaders(withName: name, fileName: fileName, mimeType: mimeType)
        //使用下面的方法5繼續
        append(stream, withLength: length, headers: headers)
    }
複製代碼
5.InputStream+data長度+表單頭
/// 最終編碼的表單數據格式:
    /// - `前分隔符(如果第一個數據塊, 就沒有前分隔符)`
    /// - `表單頭`
    /// - `表單數據`
    /// - `後分隔符(如果最後一個數據塊, 後分隔符是終結分隔符)`
    public func append(_ stream: InputStream, withLength length: UInt64, headers: HTTPHeaders) {
        //封裝成BodyPart對象
        let bodyPart = BodyPart(headers: headers, bodyStream: stream, bodyContentLength: length)
        //存入數組
        bodyParts.append(bodyPart)
    }
複製代碼

編碼BodyPart對象

Alamofire提供了兩個公開方法來將封裝的BodyPart對象編碼爲Data類型,用來塞入URLRequest的bodydata使用post

  1. 編碼爲Data(全部數據都在內存中,注意文件太大會爆內存)
  2. 使用IOStream來保存到臨時文件中(由於上面封裝完的BodyPart保存的數據都是以InputStream類型,所以只須要建立一個OutputStream對象來寫文件就行。
  • 另外提供了N個私有方法來輔助寫入
1.寫到內存:

公開方法:學習

/// 內存編碼, 編碼爲Data類型, 注意大文件容易爆內存, 大文件使用下面的編碼到fileUrl方法.
    public func encode() throws -> Data {
        // 檢測是否有保存錯誤
        if let bodyPartError = bodyPartError {
            // 有保存錯誤的話, 直接拋出異常
            throw bodyPartError
        }
        
        // 準備追加數據
        var encoded = Data()
        
        // 設置頭尾分隔符
        bodyParts.first?.hasInitialBoundary = true
        bodyParts.last?.hasFinalBoundary = true

        // 遍歷編碼data, 而後追加
        for bodyPart in bodyParts {
            let encodedData = try encode(bodyPart)
            encoded.append(encodedData)
        }

        return encoded
    }
複製代碼

私有方法:用來編碼單個BodyPart數據

// 編碼單個BodyPart數據
    private func encode(_ bodyPart: BodyPart) throws -> Data {
        // 準備追加用的data
        var encoded = Data()
        
        // 先編碼分隔符(要麼是起始分隔符, 要麼是中間分隔符)
        let initialData = bodyPart.hasInitialBoundary ? initialBoundaryData() : encapsulatedBoundaryData()
        encoded.append(initialData)

        // 編碼表單頭
        let headerData = encodeHeaders(for: bodyPart)
        encoded.append(headerData)

        // 編碼表單數據
        let bodyStreamData = try encodeBodyStream(for: bodyPart)
        encoded.append(bodyStreamData)

        // 若是是最後一個表單數據了, 把結束分隔符編碼進去
        if bodyPart.hasFinalBoundary {
            encoded.append(finalBoundaryData())
        }

        return encoded
    }

    // 編碼表單頭
    private func encodeHeaders(for bodyPart: BodyPart) -> Data {
        //格式爲: `表單頭1名字: 表單頭1值\r\n表單頭2名字: 表單頭2值\r\n...\r\n`
        let headerText = bodyPart.headers.map { "\($0.name): \($0.value)\(EncodingCharacters.crlf)" }
            .joined()
            + EncodingCharacters.crlf
        // utf8編碼
        return Data(headerText.utf8)
    }

    // 編碼表單數據
    private func encodeBodyStream(for bodyPart: BodyPart) throws -> Data {
        let inputStream = bodyPart.bodyStream
        // 打開stream
        inputStream.open()
        // 方法結束要關閉stream
        defer { inputStream.close() }

        var encoded = Data()

        // 直接循環讀取
        while inputStream.hasBytesAvailable {
            // buffer,長度爲1024Byte
            var buffer = [UInt8](repeating: 0, count: streamBufferSize)
            // 一次讀取一個1024Byte的數據
            let bytesRead = inputStream.read(&buffer, maxLength: streamBufferSize)
            // 出錯直接拋出錯誤
            if let error = inputStream.streamError {
                throw AFError.multipartEncodingFailed(reason: .inputStreamReadFailed(error: error))
            }
            // 讀到數據就追加, 不然跳出循環
            if bytesRead > 0 {
                encoded.append(buffer, count: bytesRead)
            } else {
                break
            }
        }

        return encoded
    }
複製代碼
2.寫到文件:

使用IOStream往文件寫數據,適合大文件處理 公開方法:

/// 使用IOStream往文件寫數據, 適合處理大文件
    public func writeEncodedData(to fileURL: URL) throws {
        if let bodyPartError = bodyPartError {
            // 1.有錯誤直接拋出
            throw bodyPartError
        }
        
        if fileManager.fileExists(atPath: fileURL.path) {
            // 2.文件已存在拋出錯誤
            throw AFError.multipartEncodingFailed(reason: .outputStreamFileAlreadyExists(at: fileURL))
        } else if !fileURL.isFileURL {
            // 3.url不是文件url拋出錯誤
            throw AFError.multipartEncodingFailed(reason: .outputStreamURLInvalid(url: fileURL))
        }

        guard let outputStream = OutputStream(url: fileURL, append: false) else {
            // 4.建立OutputStream失敗拋出錯誤
            throw AFError.multipartEncodingFailed(reason: .outputStreamCreationFailed(for: fileURL))
        }
        // 打開OutputStream
        outputStream.open()
        // 方法結束關閉OutputStream
        defer { outputStream.close() }
        
        // 設置頭尾分隔符標誌
        bodyParts.first?.hasInitialBoundary = true
        bodyParts.last?.hasFinalBoundary = true
        //遍歷使用私有方法寫數據
        for bodyPart in bodyParts {
            try write(bodyPart, to: outputStream)
        }
    }
複製代碼

中間私有方法(只封裝處理下,還沒有往OStream裏寫數據,真正寫數據的只有兩個方法):

/// 編碼單個BodyPart到OStream, 會派發個四個子方法
    private func write(_ bodyPart: BodyPart, to outputStream: OutputStream) throws {
        // 編碼數據前部分隔符(可能爲起始分隔符, 也可能爲中間分隔符)
        try writeInitialBoundaryData(for: bodyPart, to: outputStream)
        // 編碼表單頭
        try writeHeaderData(for: bodyPart, to: outputStream)
        // 編碼表單數據
        try writeBodyStream(for: bodyPart, to: outputStream)
        // 編碼結束分隔符(只有最後一個數據纔會編碼)
        try writeFinalBoundaryData(for: bodyPart, to: outputStream)
    }
    /// 編碼數據前部分隔符
    private func writeInitialBoundaryData(for bodyPart: BodyPart, to outputStream: OutputStream) throws {
        // 起始分隔符類型(可能爲起始分隔符, 也可能爲中間分隔符)編碼成Data
        let initialData = bodyPart.hasInitialBoundary ? initialBoundaryData() : encapsulatedBoundaryData()
        // 繼續派發
        return try write(initialData, to: outputStream)
    }
    /// 編碼表單頭
    private func writeHeaderData(for bodyPart: BodyPart, to outputStream: OutputStream) throws {
        // 把表單頭編碼成Data
        let headerData = encodeHeaders(for: bodyPart)
        // 繼續派發
        return try write(headerData, to: outputStream)
    }
    /// 編碼表單數據
    private func writeBodyStream(for bodyPart: BodyPart, to outputStream: OutputStream) throws {
        let inputStream = bodyPart.bodyStream
        // 打開IStream
        inputStream.open()
        // 方法結束要關閉IStream
        defer { inputStream.close() }
        // 循環讀取Bytes
        while inputStream.hasBytesAvailable {
            // 緩存, 大小爲1024Byte
            var buffer = [UInt8](repeating: 0, count: streamBufferSize)
            // 一次讀1024Byte
            let bytesRead = inputStream.read(&buffer, maxLength: streamBufferSize)

            if let streamError = inputStream.streamError {
                // 有錯誤就拋出
                throw AFError.multipartEncodingFailed(reason: .inputStreamReadFailed(error: streamError))
            }

            if bytesRead > 0 {
                // 若讀出來的數據小於緩存, 取前面有效數據
                // 上面轉成Data不用這樣處理是由於不須要往文件裏寫
                if buffer.count != bytesRead {
                    buffer = Array(buffer[0..<bytesRead])
                }
                // 繼續派發(把字節數據數組寫入OStream)
                try write(&buffer, to: outputStream)
            } else {
                break
            }
        }
    }
    // 編碼最終分隔符
    private func writeFinalBoundaryData(for bodyPart: BodyPart, to outputStream: OutputStream) throws {
        // 只有最後一個表單數據才編碼最終分隔符
        if bodyPart.hasFinalBoundary {
            //編碼成Data, 而後繼續派發
            return try write(finalBoundaryData(), to: outputStream)
        }
    }
複製代碼

最終往OStream中寫數據的兩個方法(最底層社畜(ಥ▽ಥ)o)

// 以Data格式寫入OStream
    private func write(_ data: Data, to outputStream: OutputStream) throws {
        //拷貝成字節數組
        var buffer = [UInt8](repeating: 0, count: data.count)
        data.copyBytes(to: &buffer, count: data.count)
        // 繼續派發
        return try write(&buffer, to: outputStream)
    }
    // 以字節數組格式寫入OStream
    private func write(_ buffer: inout [UInt8], to outputStream: OutputStream) throws {
        var bytesToWrite = buffer.count
        // 循環往OStream中寫數據
        while bytesToWrite > 0, outputStream.hasSpaceAvailable {
            // 寫數據, 記錄寫了的字節數
            let bytesWritten = outputStream.write(buffer, maxLength: bytesToWrite)

            // 出錯直接拋出
            if let error = outputStream.streamError {
                throw AFError.multipartEncodingFailed(reason: .outputStreamWriteFailed(error: error))
            }
            // 減去寫入的數據
            bytesToWrite -= bytesWritten
            // buffer除去寫入的數據
            if bytesToWrite > 0 {
                buffer = Array(buffer[bytesWritten..<buffer.count])
            }
        }
    }
複製代碼

輔助函數:

輔助處理操做,包括:

  • 根據文件擴展名獲取mime類型字符串
  • 把表單name字段,文件名,mime類型封裝成HTTPHeaders類型
  • 把三種類型的分隔符編碼爲Data格式
  • 保存追加表單數據時出現的錯誤,只會保存第一個出現的錯誤

MultipartUpload內部類

  • Alamofire內部類,用來快速發送上傳請求使用
  • 用來把封裝好的MultipartFormData編碼成Data或者編碼進臨時文件,而後與關聯的URLRequest對象封裝成一個元組返回
  • 實現了UploadConvertible用來建立UploadRequest

屬性與初始化

由於是內部類,全部所有屬性都是intenal的,模塊外部沒法訪問

// 懶加載屬性result, 首次讀取時會調用build方法編碼MultipartFormData數據
    lazy var result = Result { try build() }
    // 是不是後臺任務, 若是是, 就會把表單數據編碼到臨時文件中
    let isInBackgroundSession: Bool
    // 表單數據
    let multipartFormData: MultipartFormData
    // 最大內存開銷, 表單數據大於該值就會被編碼到臨時文件中
    let encodingMemoryThreshold: UInt64
    // 關聯的URLRequestConvertible協議對象
    let request: URLRequestConvertible
    // 操做臨時文件
    let fileManager: FileManager

    init(isInBackgroundSession: Bool, encodingMemoryThreshold: UInt64, request: URLRequestConvertible, multipartFormData: MultipartFormData) {
        self.isInBackgroundSession = isInBackgroundSession
        self.encodingMemoryThreshold = encodingMemoryThreshold
        self.request = request
        fileManager = multipartFormData.fileManager
        self.multipartFormData = multipartFormData
    }
複製代碼

核心方法:build 編碼數據

//編碼數據, 並返回建立的URLRequest與UploadRequest.Uploadable關聯的元組
    func build() throws -> (request: URLRequest, uploadable: UploadRequest.Uploadable) {
        // 建立URLRequest
        var urlRequest = try request.asURLRequest()
        // 設置請求頭的Content-Type字段的, 值爲:`multipart/form-data; boundary={表單分隔符}`
        urlRequest.setValue(multipartFormData.contentType, forHTTPHeaderField: "Content-Type")
        // 編碼後的Uploadable
        let uploadable: UploadRequest.Uploadable
        if multipartFormData.contentLength < encodingMemoryThreshold && !isInBackgroundSession {
            // 表單數據小於設置的內存開銷, 且不能爲後臺Session就直接編碼成Data類型
            let data = try multipartFormData.encode()

            uploadable = .data(data)
        } else {
            // 系統緩存目錄
            let tempDirectoryURL = fileManager.temporaryDirectory
            // 保存臨時表單文件的目錄
            let directoryURL = tempDirectoryURL.appendingPathComponent("org.alamofire.manager/multipart.form.data")
            // 臨時文件名
            let fileName = UUID().uuidString
            // 臨時文件url
            let fileURL = directoryURL.appendingPathComponent(fileName)

            // 建立臨時表單文件目錄
            try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true, attributes: nil)

            do {
                // 把表單數據編碼到臨時文件
                try multipartFormData.writeEncodedData(to: fileURL)
            } catch {
                // 若編碼失敗, 刪除臨時文件, 並拋出異常
                try? fileManager.removeItem(at: fileURL)
                throw error
            }
            // 返回的UploadRequest.Uploadable, 並設置須要完成後刪除臨時文件
            uploadable = .file(fileURL, shouldRemove: true)
        }
        // 返回建立的URLRequest與UploadRequest.Uploadable關聯的元組
        return (request: urlRequest, uploadable: uploadable)
    }
複製代碼

擴展實現UploadConvertible協議用來建立UploadRequest

extension MultipartUpload: UploadConvertible {
    func asURLRequest() throws -> URLRequest {
        try result.get().request
    }

    func createUploadable() throws -> UploadRequest.Uploadable {
        try result.get().uploadable
    }
}
複製代碼

以上純屬我的理解,不免有誤,如發現有錯誤的地方,歡迎評論指出,將第一時間修改,很是感謝~

相關文章
相關標籤/搜索