Alamofire 2

2022-07-04  本文已影响0人  f8d1cf28626a

Alamofire -2

Alamofire4

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

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)
      }
  // 其他逻辑省略。。。
}

动态适配能力 - RequestAdapter

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

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

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

自定义验证

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
}

重试请求

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

Result

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
}
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? {... }
}
extension Result: CustomStringConvertible {
    public var description: String {
       // 就是返回 "SUCCESS" 和 "FAILURE" 的标识
    }
}
extension Result: CustomDebugStringConvertible {
    public var debugDescription: String {
        // 返回标识的同时,还返回了具体内容   
    }
}

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 
 }
self.queue = {
    let operationQueue = OperationQueue()

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

    return operationQueue
}()
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
   // 省略无关代码,方便阅读
   // 请求完成,队列resume
   queue.isSuspended = false
}
open func resume() {
    if startTime == nil { startTime = CFAbsoluteTimeGetCurrent() }
}
init(session: URLSession, requestTask: RequestTask, error: Error? = nil) {
    self.session = session
       // 省略无关代码,方便阅读
    delegate.queue.addOperation { self.endTime = CFAbsoluteTimeGetCurrent() }
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
    if initialResponseTime == nil { initialResponseTime = CFAbsoluteTimeGetCurrent() }
}
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()
    )
}
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
}

Alamofire5

Response

SessionManager.default.request(urlString).response{
  
    (response) in print(response)

}
init(task: URLSessionTask?) {
    _task = task

    self.queue = {
        let operationQueue = OperationQueue()
        operationQueue.maxConcurrentOperationCount = 1
        operationQueue.isSuspended = true
        operationQueue.qualityOfService = .utility
        return operationQueue
    }()
}

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

四、总结

Alaofire 多表单

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

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

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

Alamofire 多表单源码分析

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

1️⃣:先创造容器

DispatchQueue.global(qos: .utility).async {
    let formData = MultipartFormData()
    multipartFormData(formData)
}
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)!
    }
}

这里是把分隔符分成了三种

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
}
public func append(_ stream: InputStream, withLength length: UInt64, headers: HTTPHeaders) {
    let bodyPart = BodyPart(headers: headers, bodyStream: stream, bodyContentLength: length)
    bodyParts.append(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
}

4️⃣:数据调用

let encodingResult = MultipartFormDataEncodingResult.success(
    request: self.upload(data, with: urlRequestWithContentType),
    streamingFromDisk: false,
    streamFileURL: nil
)

总结

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

上一篇 下一篇

猜你喜欢

热点阅读