美文网首页
Alamofire 2

Alamofire 2

作者: f8d1cf28626a | 来源:发表于2022-07-04 03:21 被阅读0次

Alamofire -2

Alamofire4

SessionDelegate 是事件总响应者,我们根据不同的需求 (DataTaskDelegate、DownloadTaskDelegate、UploadTaskDelegate、TaskDelegate),响应总代理然后根据需求的不同交给专业的人去做专业的事。耦合性大大降低,架构的分层更加明显! 这个篇章我要介绍 Alamofire 一些非常好用的小细节

SessionDelegate的对外闭包

  • SessionDelegate不光是代理的总称,同时也是我们对外逻辑强力输出口,针对我们的代理响应提供了非常之多的闭包
// 接受到挑战回调
open var sessionDidReceiveChallengeWithCompletion: ((URLSession, URLAuthenticationChallenge, @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) -> Void)?
// 后台事件完成的回调闭包
open var sessionDidFinishEventsForBackgroundURLSession: ((URLSession) -> Void)?
// 任务完成的闭包
open var taskDidComplete: ((URLSession, URLSessionTask, Error?) -> Void)?
// 下载读写的进度闭包
open var downloadTaskDidWriteData: ((URLSession, URLSessionDownloadTask, Int64, Int64, Int64) -> Void)?
// 接收到事件任务数据的闭包
open var dataTaskDidReceiveData: ((URLSession, URLSessionDataTask, Data) -> Void)?
// 接收到响应的闭包
open var dataTaskDidReceiveResponse: ((URLSession, URLSessionDataTask, URLResponse) -> URLSession.ResponseDisposition)?
  • 上面只是列举了一些闭包,但是你可以通过闭包的名字可以非常清晰感知作用
  • 下面举个🌰
// 创建request
SessionManager.default.request(urlStr)
// 监听任务回调完成状态
SessionManager.default.delegate.taskDidComplete = { (session,task,error) in
    print("任务完成了")
}

// 背后的逻辑
open func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
    /// Executed after it is determined that the request is not going to be retried
    let completeTask: (URLSession, URLSessionTask, Error?) -> Void = { [weak self] session, task, error in
        guard let strongSelf = self else { return }
        // 回调完成闭包
        strongSelf.taskDidComplete?(session, task, error)
      }
  // 其他逻辑省略。。。
}
  • 可以看到我们是可以直接从 SessionManager.default.delegate 直接对 taskDidComplete 的闭包声明
  • 其实可以看到在 SessionDelegate 的代理响应里面执行 taskDidComplete 闭包
  • 这样对外提供闭包的本质:就是对外提供能力,让开发人员更加自如,方便

动态适配能力 - RequestAdapter

这个功能特别好用,能够提供下面两种能力。

  • 1: request 处理
  • 2: request 重定向

下面我们开始来玩玩这个适配能力

class LGAdapter: RequestAdapter{
    func adapt(_ urlRequest: URLRequest) throws -> URLRequest {
        // token
        // 1: request 处理
        // 2: request 重定向
        var request = urlRequest
        request.setValue("lgcoociToken", forHTTPHeaderField: "lgtoken")
        let newUrlRequest = URLRequest.init(url: URL(string: "http://www.douban.com/j/app/radio/channels")!)
        return newUrlRequest
    }
}
  • 实现 RequestAdapter 协议的 adapt 方法
  • 对提供的 urlRequest 进行处理,比如统一配置 token
  • 对urlRequest 重定向,换一个新的 request 请求.
  • 记住一定要配置:SessionManager.default.adapter = LGAdapter()

自定义验证

  • 我们请求网络习惯性 响应状态码200多就是正确,其实我们可以根据自己公司的特性自定义处理验证操作,更加符合实际开发
SessionManager.default.request(urlStr, method: .get, parameters: ["username":"Roc","password":"888888"])
    .response { (response) in
        debugPrint(response)
    }.validate { (request, response, data) -> Request.ValidationResult in
        guard let _ = data else{
            return .failure(NSError.init(domain: "lgcooci", code: 10089, userInfo: nil))
        }
        let code = response.statusCode
        if code == 404 {
            return .failure(NSError.init(domain: "lgcooci", code: 100800, userInfo: nil))
        }
        return .success
}
  • 这段代码里面我们在后面链式添加 validate 方法
  • 在闭包里面添加自己验证方式
  • 比如没有 数据data 我就返回 10089 错误
  • 状态码 == 404 的时候,我们返回 100800 错误

重试请求

  • 重试请求的操作可能大家平时在开发里面运用不多,但是我觉得也是有需求场景的。
if let retrier = retrier, let error = error {
    retrier.should(sessionManager, retry: request, with: error) { [weak self] shouldRetry, timeDelay in
        guard shouldRetry else { completeTask(session, task, error) ; return }

        DispatchQueue.utility.after(timeDelay) { [weak self] in
            guard let strongSelf = self else { return }

            let retrySucceeded = strongSelf.sessionManager?.retry(request) ?? false

            if retrySucceeded, let task = request.task {
                strongSelf[task] = request
                return
            } else {
                completeTask(session, task, error)
            }
        }
    }
}
  • 当SessionDelegate 完成请求的时候,判断重试闭包是否存在,还有注意一定是错误的情况,没有错误没有必要重连。这里也透露出 retrier 搭配 validate 更美哦
  • retrier 也是继承协议的处理方式,操作参考 adapter
extension LGAdapter: RequestRetrier{
    func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: @escaping RequestRetryCompletion) {
        print("manager = \(manager)")
        print("request = \(request)")
        print("error = \(error)")
        completion(true,1)
        // 一定要有出口,不然持续递归就会发生很严重的影响
        completion(false,0)
    }
}
  • 实现 RequestRetrier 协议的 should
  • 记得使用:SessionManager.default.retrier = LGAdapter().

Result

  • Alamofire 在请求数据会有一个 Response 但是这个不是我们最终的结果,还需要进过一层序列化。
public func response<T: DataResponseSerializerProtocol>(
    queue: DispatchQueue? = nil,
    responseSerializer: T,
    completionHandler: @escaping (DataResponse<T.SerializedObject>) -> Void)
    -> Self
{
    delegate.queue.addOperation {
        let result = responseSerializer.serializeResponse(
            self.request,
            self.response,
            self.delegate.data,
            self.delegate.error
        )

        var dataResponse = DataResponse<T.SerializedObject>(
            request: self.request,
            response: self.response,
            data: self.delegate.data,
            result: result,
            timeline: self.timeline
        )
    }
    return self
}
  • result 是经过了 responseSerializer.serializeResponse 序列化处理的结果
  • result 的结果最终传入到了 dataResponse
public enum Result<Value> {
    case success(Value)
    case failure(Error)
    
   // 提供成功还有失败的校验
    public var isSuccess: Bool {... }
    public var isFailure: Bool {...}
    public var value: Value? {...}
    public var error: Error? {... }
}
  • 结果只有成功和失败,设计成了枚举,Swift 枚举非常强大 🤙🤙🤙,这个地方也得以体现
  • 当然,为了打印更加详细的信息,使Result实现了CustomStringConvertibleCustomDebugStringConvertible协议
extension Result: CustomStringConvertible {
    public var description: String {
       // 就是返回 "SUCCESS" 和 "FAILURE" 的标识
    }
}
extension Result: CustomDebugStringConvertible {
    public var debugDescription: String {
        // 返回标识的同时,还返回了具体内容   
    }
}
  • 下面还有很多其他方法的拓展,不是重点大家自己看看就OK

Timeline 时间轴

  • 强大的Alamofire👍👍👍为了方便我们的开发还给我们提供了Timeline时间轴, 大家可以通过 Timeline 快速得到这个请求的时间数据,从而判断请求是否合理,是否需要优化....
timeline: Timeline: { 
"Request Start Time": 588099247.070,
"Initial Response Time": 588099272.474, 
"Request Completed Time": 588099272.475, 
"Serialization Completed Time": 588099272.475, 
"Latency": 25.404 secs, 
"Request Duration": 25.405 secs, 
"Serialization Duration": 0.000 secs, 
"Total Duration": 25.405 secs 
 }
  • 时间轴的数据得到,这里我们的Alamofire设计了一个队列
self.queue = {
    let operationQueue = OperationQueue()

    operationQueue.maxConcurrentOperationCount = 1
    operationQueue.isSuspended = true
    operationQueue.qualityOfService = .utility

    return operationQueue
}()
  • 同步队列为了让流程顺序执行。
  • 在刚初始化的时候当前队列是挂起的 operationQueue.isSuspended = true
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
   // 省略无关代码,方便阅读
   // 请求完成,队列resume
   queue.isSuspended = false
}
  • 请求完成,队列 resume

  • 看到这里也说明了加入这个队列的任务必然在请求完成之后

  • 1:请求开始时间

open func resume() {
    if startTime == nil { startTime = CFAbsoluteTimeGetCurrent() }
}
  • 2:添加请求完成时间记录
init(session: URLSession, requestTask: RequestTask, error: Error? = nil) {
    self.session = session
       // 省略无关代码,方便阅读
    delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent() }
}
  • 3:初始化响应时间
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
    if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
}
  • 4:时间轴设置
var timeline: Timeline {
    let requestStartTime = self.startTime ?? CFAbsoluteTimeGetCurrent()
    let requestCompletedTime = self.endTime ?? CFAbsoluteTimeGetCurrent()
    let initialResponseTime = self.delegate.initialResponseTime ?? requestCompletedTime

    return Timeline(
        requestStartTime: requestStartTime,
        initialResponseTime: initialResponseTime,
        requestCompletedTime: requestCompletedTime,
        serializationCompletedTime: CFAbsoluteTimeGetCurrent()
    )
}
  • 5:初始化记录时间以及计算时间
public init(
    requestStartTime: CFAbsoluteTime = 0.0,
    initialResponseTime: CFAbsoluteTime = 0.0,
    requestCompletedTime: CFAbsoluteTime = 0.0,
    serializationCompletedTime: CFAbsoluteTime = 0.0)
{
    self.requestStartTime = requestStartTime
    self.initialResponseTime = initialResponseTime
    self.requestCompletedTime = requestCompletedTime
    self.serializationCompletedTime = serializationCompletedTime

    self.latency = initialResponseTime - requestStartTime
    self.requestDuration = requestCompletedTime - requestStartTime
    self.serializationDuration = serializationCompletedTime - requestCompletedTime
    self.totalDuration = serializationCompletedTime - requestStartTime
}
  • 看到这里你也就知道为什么这些时间能够记录,是因为不断通过队列同步控制,在一些核心的点保存当前时间! 比如:endTime
  • 还有一些关键核心时间比如:startTime , initialResponseTime 就是在相关代理里面设置的!
  • 如果没有设置值,那么就在当时调用的时候重置当前时间
  • Timeline 的其他时间就是通过已知的 initialResponseTime 和 requestStartTime、requestCompletedTime 、serializationCompletedTime 计算得出!

Alamofire5

  • Alamofire 请求数据之后,就会回调响应,底层是如何保证响应必然在请求之后呢?以及respose到底是什么东西

Response

  • 1:response的执行顺序 首先我们先来看这段代码
SessionManager.default.request(urlString).response{
  
    (response) in print(response)

}
  • Alamofire 一个非常关键的类就是 Request ,请看下面这段代码是链式调用,但是怎么保证 response 在 request 之后呢?
  • 我们response的任务是加入到了delegate.queue.addOperation
  • 交付给了主队列,毕竟这里的 response 是给用户对外提供的,用户可以直接UI操作
  • 然后回到闭包出去
init(task: URLSessionTask?) {
    _task = task

    self.queue = {
        let operationQueue = OperationQueue()
        operationQueue.maxConcurrentOperationCount = 1
        operationQueue.isSuspended = true
        operationQueue.qualityOfService = .utility
        return operationQueue
    }()
}
  • 这个队列的并发数为 1
  • 初始化出来是默认挂起状态
  • 请求完成的时候:把队列的挂起状态取消了,那么这个时候就可以正常执行任务
  • 刚刚在加入到这个队列里面的任务就可以在请求完成的时候顺序执行 Soga

response的作用

response 分为四种

DefaultDataResponse
DataResponse
DefaultDownloadResponse
DownloadResponse

这里可以看到并没有 upload 相关的,为什么?那是因为 upload 返回的就是普通数据,就没有必要重新封装

其中 Default开头就是返回原始数据,没有经过其他处理,不加 Default 可以通过序列化器处理!

其实如果细心的你,应该很容易可以得出,其实这里封装 Response 和我们传统的 Response 不是同一个。里封装 Response 是一个数据储存模型 ,里面保存对外所需要的数据

self.request = request
self.response = response
self.data = data
self.error = error
self.timeline = timeline

序列化器

就拿我们最熟悉的 json 序列化器来给大家一起讨论

public func responseJSON(
    queue: DispatchQueue? = nil,
    options: JSONSerialization.ReadingOptions = .allowFragments,
    completionHandler: @escaping (DataResponse<Any>) -> Void)
    -> Self
{
    return response(
        queue: queue,
        responseSerializer: DataRequest.jsonResponseSerializer(options: options),
        completionHandler: completionHandler
    )
}
  • 这里封装了一个 response 的方法
  • 第三个参数是序列化器的初始化
public static func jsonResponseSerializer(
    options: JSONSerialization.ReadingOptions = .allowFragments)
    -> DataResponseSerializer<Any>
{
    return DataResponseSerializer { _, response, data, error in
        return Request.serializeResponseJSON(options: options, response: response, data: data, error: error)
    }
}
  • 这里返回的就是 DataResponseSerializer 类型的序列化器
  • 其中参数就是一个闭包,这个闭包带有一个返回值类型 Result: Request.serializeResponseJSON
  • 之前上面就是对这个初始化出来的DataResponseSerializer 的参数闭包的调用: DataRequest.jsonResponseSerializer(options: options)
  public static func serializeResponseJSON(
        options: JSONSerialization.ReadingOptions,
        response: HTTPURLResponse?,
        data: Data?,
        error: Error?)
        -> Result<Any>
    {
       // 省略了一些不重要的代码
        do {
            let json = try JSONSerialization.jsonObject(with: validData, options: options)
            return .success(json)
        } catch {
            return .failure(AFError.responseSerializationFailed(reason: .jsonSerializationFailed(error: error)))
        }
    }
  • 很简单的封装验证了一些数据
  • 然后就是非常熟悉的json序列化器: JSONSerialization.jsonObject
  • 根据序列化的结果返回 : .success(json) 或者 .failure(error)

四、总结

  • 创建一个序列化结构体
  • 通过序列化结构体 - 发起序列化响应闭包
  • 把外界就是 taskDelegate 里面的数据 -> 传到我们外界的闭包 - 交给我们自定义的序列或者系统帮我们实现的序列化器实现
  • response 验证 - response.statusCode 判断 - 发出 result
  • result就是我们的序列化器的返回值
  • 同步 operation 把 result 交给 response 结构体
  • data/downloadResponse 储存数据
  • response回调 返回 response响应数据

Alaofire 多表单

实际开发过程中,多表单上传是非常重要的一种请求!服务端通常是根据请求头(headers)中的 Content-Type 字段来获知请求中的消息主体是用何种方式编码,再对主体进行解析。 所以说到 POST 提交数据方案,包含了 Content-Type 和消息主体编码方式两部分

下面我通过 Charles 抓包上传图片的接口

  • --alamofire.boundary.4e076f46186e231d: 是分隔符,为了方便读取数据
  • Content-Disposition: form-data; name="name": 其中 Content-disposition 是 MIME 协议的扩展,MIME 协议指示 MIME 用户代理如何显示附加的文件。Content-disposition 其实可以控制用户请求所得的内容存为一个文件的时候提供一个默认的文件名,这里就是添加了一个 key = name
  • 接在后面就是 \r\n 换行符
  • 然后就是 key 对应的 value = Roc
  • 最下面的乱码是图片data数据

Multipart 格式显示整个数据就类似字典的 key-value

name Rc
username Roc
PASSWORD 123456
fileName 一堆乱码

我们通过URLSeesion去请求多表单

1️⃣:分隔符初始化 利用 NSUUID().uuidString 设定为分隔符

init() {
 self.boundary = NSUUID().uuidString
}

2️⃣:换行符号

extension CharacterSet {
    static func MIMECharacterSet() -> CharacterSet {
        let characterSet = CharacterSet(charactersIn: "\"\n\r")
        return characterSet.inverted
    }
}

3️⃣: 数据格式处理&拼接

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)
}

4️⃣:数据处理完毕,然后设置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")
    }
}

5️⃣:多表单格式封装,以及使用

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: "Roc_Roc_Roc", type: "jpg")
    builder.appendFormData("filedata",content:data as! Data , fileName: "fileName", contentType: "image/jpeg")
    request.setMultipartBody(builder.build()!, boundary: builder.boundary)
    return request
}

Alamofire 表单数据上传

Alamofire 处理多表单的方式有三种,根据 URLSession 的三个方法封装而来

// 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: "Roc_Roc", 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: "Roc_Roc", 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("Roc_Roc".data(using: .utf8)!, withName: "name")
            mutilPartData.append("Roc_Roc_Roc".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) ****")
                })
            }
    }
}
  • 如果你只是想使用,但这里就OK!
  • 接下来我们开始展开分析 Alamofire 源码,方便我们更加深入了解 Alamofire!

Alamofire 多表单源码分析

源码前面分析的代码就不贴出来,大家可以自行跟源码

1️⃣:先创造容器

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) 接下来调用外界闭包,准备条件完成,开始填充数据

2️⃣:填充数据

mutilPartData.append("Roc_roc".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 也就是 Roc_roc 的数据通过 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

3️⃣:数据整合

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 中

4️⃣:数据调用

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 最终返回上传情况

到这里这个 多表单处理 篇章就写完了

相关文章

网友评论

      本文标题:Alamofire 2

      本文链接:https://www.haomeiwen.com/subject/oazlbrtx.html